Reset to Initial State with React Hooks
Asked Answered
C

15

114

I'm currently working on a signup form and the following is a snippet of my code:

const Signup = () => {
    const [username, setUsername] = useState('')
    const [email, setEmail] = useState('')
    const [password, setPassword] = useState('')
    const [passwordConfirmation, setPasswordConfirmation] = useState('')

    const clearState = () => {
        setUsername('')
        setEmail('')
        setPassword('')
        setPasswordConfirmation('')
    }

    const handleSubmit = signupUser => e => {
        e.preventDefault()
        signupUser().then(data => {
            console.log(data)
            clearState() // <-----------
        })
    }

    return <JSX />
}

export default Signup

Each piece of state is used for a controlled input for the form.

Essentially what I want to do is after the user has successfully signed up, I want the state to go back to the initial state with the fields cleared.

It's quite imperative to manually set each piece of state back to empty strings inclearState I was wondering if there is a method or function that comes with React that resets the state back to its initial values?

Canfield answered 26/2, 2019 at 23:39 Comment(1)
For context, here is a thread in the ReactJS repo on this subject. (see my answer below for a couple of the solutions offered there)Apotropaic
C
122

There is no built-in way to set the state to its initial value, sadly.

Your code looks good, but if you want to decrease the functions needed you can put your entire form state in a single state variable object and reset to the initial object.

Example

const { useState } = React;

function signupUser() {
  return new Promise(resolve => {
    setTimeout(resolve, 1000);
  });
}

const initialState = {
  username: "",
  email: "",
  password: "",
  passwordConfirmation: ""
};

const Signup = () => {
  const [
    { username, email, password, passwordConfirmation },
    setState
  ] = useState(initialState);

  const clearState = () => {
    setState({ ...initialState });
  };

  const onChange = e => {
    const { name, value } = e.target;
    setState(prevState => ({ ...prevState, [name]: value }));
  };

  const handleSubmit = e => {
    e.preventDefault();
    signupUser().then(clearState);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          Username:
          <input value={username} name="username" onChange={onChange} />
        </label>
      </div>
      <div>
        <label>
          Email:
          <input value={email} name="email" onChange={onChange} />
        </label>
      </div>
      <div>
        <label>
          Password:
          <input
            value={password}
            name="password"
            type="password"
            onChange={onChange}
          />
        </label>
      </div>
      <div>
        <label>
          Confirm Password:
          <input
            value={passwordConfirmation}
            name="passwordConfirmation"
            type="password"
            onChange={onChange}
          />
        </label>
      </div>
      <button>Submit</button>
    </form>
  );
};

ReactDOM.render(<Signup />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
Chiarra answered 26/2, 2019 at 23:52 Comment(0)
C
67

I think the voted answer is still correct, but recently React released the new built-in useReducer which, in their own words, is

handy for resetting the state later in response to an action

https://reactjs.org/docs/hooks-reference.html#usereducer

Also it states that it's usually preferable useReducer when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

Using the same sample on the voted answer, you could use useReducer like this:

Javascript

import React, { useReducer } from "react";

const initialState = {
    username: "",
    email: "",
    password: "",
    passwordConfirmation: "",
};

const reducer = (state, action) => {
    if (action.type === "reset") {
        return initialState;
    }

    const result = { ...state };
    result[action.type] = action.value;
    return result;
};

const Signup = () => {
    const [state, dispatch] = useReducer(reducer, initialState);
    const { username, email, password, passwordConfirmation } = state;

    const handleSubmit = e => {
        e.preventDefault();

        /* fetch api */

        /* clear state */
        dispatch({ type: "reset" });
    };

    const onChange = e => {
        const { name, value } = e.target;
        dispatch({ type: name, value });
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>
                    Username:
                    <input value={username} name="username" onChange={onChange} />
                </label>
            </div>
            <div>
                <label>
                    Email:
                    <input value={email} name="email" onChange={onChange} />
                </label>
            </div>
            <div>
                <label>
                    Password:
                    <input
                        value={password}
                        name="password"
                        type="password"
                        onChange={onChange}
                    />
                </label>
            </div>
            <div>
                <label>
                    Confirm Password:
                    <input
                        value={passwordConfirmation}
                        name="passwordConfirmation"
                        type="password"
                        onChange={onChange}
                    />
                </label>
            </div>
            <button>Submit</button>
        </form>
    );
};

export default Signup;

Typescript

import React, { FC, Reducer, useReducer } from "react";

interface IState {
    email: string;
    password: string;
    passwordConfirmation: string;
    username: string;
}

interface IAction {
    type: string;
    value?: string;
}

const initialState: IState = {
    email: "",
    password: "",
    passwordConfirmation: "",
    username: "",
};

const reducer = (state: IState, action: IAction) => {
    if (action.type === "reset") {
        return initialState;
    }

    const result: IState = { ...state };
    result[action.type] = action.value;
    return result;
};

export const Signup: FC = props => {
    const [state, dispatch] = useReducer<Reducer<IState, IAction>, IState>(reducer, initialState, () => initialState);
    const { username, email, password, passwordConfirmation } = state;

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();

        /* fetch api */

        /* clear state */
        dispatch({ type: "reset" });
    };

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        dispatch({ type: name, value });
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>
                    Username:
                    <input value={username} name="username" onChange={onChange} />
                </label>
            </div>
            <div>
                <label>
                    Email:
                    <input value={email} name="email" onChange={onChange} />
                </label>
            </div>
            <div>
                <label>
                    Password:
                    <input
                        value={password}
                        name="password"
                        type="password"
                        onChange={onChange}
                    />
                </label>
            </div>
            <div>
                <label>
                    Confirm Password:
                    <input
                        value={passwordConfirmation}
                        name="passwordConfirmation"
                        type="password"
                        onChange={onChange}
                    />
                </label>
            </div>
            <button>Submit</button>
        </form>
    );
};

Notice that I created this reducer function const to be as generic as possible, but you can completely change it and test different action types (other than simply state property names) and perform complex calculations before returning the state modified. There are some examples in the link provided above.

Campanulaceous answered 16/9, 2019 at 10:52 Comment(1)
I've been looking for a Typescript version of a generic handleChange method and this fits just perfect. Great example @CampanulaceousReprieve
P
59

Short answer

This has a very simple solution. You can change the key prop where the rendering component. e.g when we have a component for editing we can pass a different key to clear previous states.

return <Component key={<different key>} />
Pervade answered 7/11, 2020 at 10:32 Comment(4)
Thanks @Masih, the quick solution and works perfect.Televisor
Beware: If you are depending on all usages of <Component /> to pass the key prop as a means to reset internal state, you may be surprised when you or someone else uses the component and forgets to include key. I know this is an official strategy of the react docs, but it's easy to make a mistake here.Calamitous
Simple, excellent solution. I have a simple functional component that needed state reset, and all of my solutions were way too verbose. Thanks!Chuckwalla
I thought this was borderline a hack (although working very well), but then I saw KFunk's comment saying that this is actually an official strategy: I think your answer should state that important fact.Aconcagua
S
7

If you want a quick n' dirty method you could try just changing the component's key which will cause React to unmount your old component instance and mount a fresh one.

I am using Lodash here to generate a unique throwaway ID but you could also probably get away with Date.now() or similar, assuming the time resolution needed is above 1 millisecond.

I am passing the key a second time as debugKey to make it easier to see what's going on but this is not neccessary.

const StatefulComponent = ({ doReset, debugKey }) => {
  const [counter, setCounter] = React.useState(0);
  const increment = () => setCounter(prev => prev + 1); 
  return (
    <React.Fragment>
      <p>{`Counter: ${counter}`}</p>
      <p>{`key=${debugKey}`}</p>
      <button onClick={increment}>Increment counter</button>
      <button onClick={doReset}>Reset component</button>
    </React.Fragment>
  );
};

const generateUniqueKey = () => `child_${_.uniqueId()}`;

const App = () => {
  const [childKey, setChildKey] = React.useState(generateUniqueKey());
  const doReset = () => setChildKey(generateUniqueKey());
  return (
    <div className="App">
      <StatefulComponent key={childKey} debugKey={childKey} doReset={doReset} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>


<div id="root"></div>
Seldun answered 15/5, 2020 at 17:25 Comment(3)
This is definitely very dirty Eliot and I won't recommend anybody using this method as long as there are way around. Just my 2 cents.Linker
There's a time and a place for quick n' dirty. This method has come in handy in the past and I wanted to make sure it was fully documented here as there are pros and cons to any approach. This one is simple, no external dependencies, and works with functional and class based components even if it feels a bit hacky.Seldun
To be fair, React documentation does recommend this method to reset the state: linkHeterophyllous
D
3

You could have used useRef in hooks something like this

 const myForm = useRef(null)

 const submit = () => {

   myForm.current.reset(); // will reset the entire form :)

   }

  <form ref={myForm} onSubmit={submit}>

   <input type="text" name="name" placeholder="John Doe">

     <input type="email" name="name" placeholder="[email protected]">

     <button type="submit">Submit</button>

 </form>
Discordance answered 23/1, 2020 at 10:55 Comment(2)
Interesting answer. Does the submit perform a complete default page resubmit, or will the myForm.current.reset() work with an event.preventDefault flag to just update the appropriate portion of the DOM? (i.e. will the user see a screen 'flash' and a complete page reload?)Everlasting
Been playing with this... and this works great. I confirm, just the key element of the DOM is updated. See codesandbox here.Everlasting
A
2

You could use one state variable as described in the FAQ here: https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

It depends on your use case of course.

Rekeying the component from the parent container would also reset it automatically of course.

Aguirre answered 26/2, 2019 at 23:47 Comment(2)
Hi, thank you for the answer, I was wondering what do you mean by "rekeying"?Canfield
@Canfield If you change the key on a component react will unmount it and mount a it as new componet. Not sure if it's the best approach in this case since you might loose focus etc.Aguirre
K
2

Alongside the other answers, I'd recommend picking up a helper library like this, or making your own abstraction on top of hooks, if this is something you'll be doing often.

useState and friends are really just low-level primitives for you, the user, to build more useful hooks on top of it. I have projects where raw useState calls are actually fairly uncommon.

Karakul answered 26/2, 2019 at 23:53 Comment(0)
P
2

I just wrote a custom hook that returns the actual hooks, along with a resetState function.

Usage:

const [{
    foo: [foo, setFoo],
    bar: [bar, setBar],
  },
  resetState,
] = useStateWithReset({
  foo: null,
  bar: [],
})

// - OR -

const [
    [foo, setFoo],
    [bar, setBar],
  ],
  resetState,
] = useStateWithReset([
  null,
  [],
])

The latter is less readable but the former duplicates the keys, so there isn't a perfect solution.

The code:

const useStateWithReset = initialState => {
  const hooksArray = Object.fromEntries(
    Object.entries(initialState).map(([k, v]) => {
      return [k, useState(v)]
    })
  );
  const resetState = () =>
    Object.entries(initialState).map(
      ([k, v]) => hooksArray[k][1](v)
    );
  return [hooksArray, resetState];
};
Potpourri answered 25/2, 2021 at 12:51 Comment(0)
H
1

I had a similar use case. Completelty unrelated from a Login, Signup mechanism but I changed it to be related to your use case.

An easy way to solve this is with a parent component in my opinion.

const initUser = {
  name: '',
  email: '',
  password: '',
  passwordConfirmation: ''      
}

const LoginManager = () => {
  const [user, setUser] = useState(initUser)

  return <Signup user={user} resetUser={setUser} />
}

const Signup = ({user, resetUser}) => {
    const [username, setUsername] = useState(user.name)
    const [email, setEmail] = useState(user.email)
    const [password, setPassword] = useState(user.password)
    const [passwordConfirmation, setPasswordConfirmation] = useState(user.passwordConfirmation)


    const handleSubmit = signupUser => e => {
        e.preventDefault()
        signupUser().then(data => {
            console.log(data)
            resetUser(initUser) // <-----------
        })
    }

    return <JSX />
}

export default Signup
Housekeeping answered 12/6, 2019 at 13:22 Comment(1)
Add a comment if you downvote to improve this answer at least in your opinionHousekeeping
A
1

One way to achieve this "reset states to initial" is by using the use-state-with-deps package.

Example:

import {useStateWithDeps} from "use-state-with-deps";

const Signup = () => {
    const [generation, setGeneration] = useState(0);

    const [username, setUsername] = useStateWithDeps("", [generation])
    const [email, setEmail] = useStateWithDeps("", [generation])
    const [password, setPassword] = useStateWithDeps("", [generation])
    const [passwordConfirmation, setPasswordConfirmation] = useStateWithDeps("", [generation])

    const clearState = () => {
        setGeneration(generation + 1);
    }

    const handleSubmit = signupUser => e => {
        e.preventDefault()
        signupUser().then(data => {
            console.log(data)
            clearState()
        })
    }

    return <JSX />
}

export default Signup

If you don't want to pull in a new dependency, you can find other solutions in this thread, which are short enough to just include directly in your project (eg. in a "utils" file). For example, this solution is only 20 lines long.

Apotropaic answered 17/2, 2022 at 17:51 Comment(0)
P
0

As I know (by reading react docs) - there is no way to do so yet.

Placket answered 26/2, 2019 at 23:46 Comment(0)
P
0

You can 'wrap' your useState in another use[Whatever name you want] and include a reset function - i.e. like a custom hook as suggested by Augustin in his answer.

Taking the example of an input form, as there is a good real example you can use and view the source of as noted below, you would use the custom hook similar to this:

function ContactForm(props) {
  const [state, handleSubmit, reset] = useForm("contactForm");

  const clearForm = e => {
    e.preventDefault();
    reset();  // <---- the extra reset function
    // Any other code you want like hiding 
    // or removing the form div from the 
    // DOM etc.
  }

  if (state.succeeded) {
      return (
        <React.Fragment>
          <p>Thanks fro your input!</p>
          <button className="default-button" onClick={clearForm}>Ok</button>
        </React.Fragment>
      );
  }
  return (
      <form onSubmit={handleSubmit}> // <-- the standard setSate type function
      <label htmlFor="email" className="form-element">
        Email Address
      </label>
      <input
        id="email"
        type="email"
        name="email"
        className="form-element"
      />
      // etc - Your form code...
      <button className="default-button" type="submit" disabled={state.submitting}>
        Submit
      </button>
    </form>
  );
}

You can see this in action in the fomrspree git respoitory react examples (at the time of writing) - the function is defined in the useForm source and there is an example of its use in 'useForm.test.js':

Portable answered 12/1, 2022 at 9:6 Comment(0)
D
0

There is a lot of hooks on GitHub and NPM, here's some of them:

I just listed a lot of hooks collections and still cannot find one with "is changed" flag.

Dichroism answered 28/1, 2023 at 14:31 Comment(0)
M
-1

This is how you can reset input values(from object) in hooks after form submission.

You can define multiple input values in same useState like firstName, lastName, etc...

const [state, setState] = React.useState({ firstName: "", lastName: "" });

Sample code.

export default function App() {
  const [state, setState] = React.useState({ firstName: "", lastName: "" });
  const handleSubmit = e => {
    e.preventDefault();
    setState({firstName:'',lastName:''})
  };
  const handleChange = e => {
    const { name, value } = e.target;
    setState({ ...state, [name]: value });
  };
  console.log(state)
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="firstName"
        placeholder="Enter first name"
        value={state.firstName}
        onChange={handleChange}
      />
      <input
        type="text"
        name="lastName"
        placeholder="Enter last name"
        value={state.lastName}
        onChange={handleChange}
      />
      <input type="submit" value="Submit" />
    </form>
  );
}

If you want multiple input to define in object instead of declaring seperately.

Maidservant answered 8/1, 2020 at 9:31 Comment(0)
I
-5
const handleSubmit = e => {
    e.preventDefault();
    reset();
}
Illyria answered 8/5, 2022 at 14:22 Comment(1)
Your answer could be improved by explaining why this may solve the OP's problem.Deicer

© 2022 - 2024 — McMap. All rights reserved.