Reset to Initial State with React Hooks
Solution 1:
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>
Solution 2:
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.
Solution 3:
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>} />
Solution 4:
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>
Solution 5:
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>