redux state value changed at second click - javascript
I m new at redux and I have integrated it with my react app , but I have a note about a small test .
At the next example I see that the value of user added on my second click.
the reducer:
const initialState = {
user: '',
password: ''
}
export const admin = (state = initialState, action) => {
switch (action.type) {
case 'admin':
return state = {
user: action.user,
password: action.password
}
default:
return initialState;
}
}
action :
const adminAction = (user, password) => {
return {
type: 'admin',
user: user,
password: password
}
}
export default adminAction;
changing state in store.
const admin = useSelector(state => state.admin);
const dispatch = useDispatch();
var user,pass = '';
const adminChangeUsername = (event) => {
user = event.target.value;
}
const adminChangePassword = (event) => {
pass = event.target.value;
}
const click= (event) => {
event.preventDefault();
dispatch(adminAction(user,pass));
console.log(store.getState())
console.log(admin.user)
}
the click void associated to a button .
when doing the first click this is what happens :
the value has changed in the store but the admin.user value is still empty.
when clicking a second time :
The store values have updated
The admin value has been added .
My question is why does only the second click trigger the retrieval of the value from store?
The reason why you are seeing the old value of admin in the click function is due to closures which cause the stale props and state problem. How do JavaScript closures work?
Every function in JavaScript maintains a reference to its outer
lexical environment. This reference is used to configure the execution
context created when a function is invoked. This reference enables
code inside the function to "see" variables declared outside the
function, regardless of when and where the function is called.
In a React function component, each time the component re-renders, the closures are re-created, so values from one render don't leak into a different render. It's for this same reason why any state you want should be within useState or useReducer hooks because they will remain past re-renders.
More information on the react side of it in React's FAQ
In addition to the main answer for the question, you should useState for the values of user and pass in your component. Otherwise the values don't stay between re-renders.
You could otherwise go without using controlled components, but not use a weird mix of both.
Working example below:
const initialstate = {
user: '',
password: '',
};
const adminReducer = (state = initialstate, action) => {
switch (action.type) {
case 'admin':
return (state = {
user: action.user,
password: action.password,
});
default:
return initialstate;
}
};
const adminAction = (user, password) => {
return {
type: 'admin',
user: user,
password: password,
};
};
const store = Redux.createStore(Redux.combineReducers({ admin: adminReducer }));
const App = () => {
const store = ReactRedux.useStore();
const admin = ReactRedux.useSelector((state) => state.admin);
const dispatch = ReactRedux.useDispatch();
const [user, setUser] = React.useState('');
const [pass, setPass] = React.useState('');
const adminChangeUsername = (event) => {
setUser(event.target.value);
};
const adminChangePassword = (event) => {
setPass(event.target.value);
};
const click = (event) => {
event.preventDefault();
dispatch(adminAction(user, pass));
console.log('store has the correct value here', store.getState());
console.log('admin is the previous value of admin due to closures:', admin);
};
return (
<div>
<input
onChange={adminChangeUsername}
value={user}
placeholder="username"
/>
<input
onChange={adminChangePassword}
value={pass}
type="password"
placeholder="password"
/>
<button onClick={click}>submit</button>
<p>This shows the current value of user and password in the store</p>
<p>User: {admin.user}</p>
<p>Pass: {admin.password}</p>
</div>
);
};
ReactDOM.render(
<ReactRedux.Provider store={store}>
<App />
</ReactRedux.Provider>,
document.querySelector('#root')
);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js" integrity="sha256-7nQo8jg3+LLQfXy/aqP5D6XtqDQRODTO18xBdHhQow4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js" integrity="sha256-JuJho1zqwIX4ytqII+qIgEoCrGDVSaM3+Ul7AtHv2zY=" crossorigin="anonymous"></script>
<div id="root"/>
Example with original vars instead of useState. You'll notice in this case by using the vars and keeping the value prop on the inputs, it prevents entry to the password input. The reason why it "works" on the user input is because the user variable started off as undefined rather than an empty string.
const initialstate = {
user: '',
password: '',
};
const adminReducer = (state = initialstate, action) => {
switch (action.type) {
case 'admin':
return (state = {
user: action.user,
password: action.password,
});
default:
return initialstate;
}
};
const adminAction = (user, password) => {
return {
type: 'admin',
user: user,
password: password,
};
};
const store = Redux.createStore(Redux.combineReducers({ admin: adminReducer }));
const App = () => {
const store = ReactRedux.useStore();
const admin = ReactRedux.useSelector((state) => state.admin);
const dispatch = ReactRedux.useDispatch();
var user,pass='';
const adminChangeUsername = (event) => {
user = event.target.value;
};
const adminChangePassword = (event) => {
pass = event.target.value;
};
const click = (event) => {
event.preventDefault();
dispatch(adminAction(user, pass));
console.log('store has the correct value here', store.getState());
console.log('admin is the previous value of admin due to closures:', admin);
};
return (
<div>
<input
onChange={adminChangeUsername}
value={user}
placeholder="username"
/>
<input
onChange={adminChangePassword}
value={pass}
type="password"
placeholder="password"
/>
<button onClick={click}>submit</button>
<p>This shows the current value of user and password in the store</p>
<p>User: {admin.user}</p>
<p>Pass: {admin.password}</p>
</div>
);
};
ReactDOM.render(
<ReactRedux.Provider store={store}>
<App />
</ReactRedux.Provider>,
document.querySelector('#root')
);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js" integrity="sha256-7nQo8jg3+LLQfXy/aqP5D6XtqDQRODTO18xBdHhQow4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js" integrity="sha256-JuJho1zqwIX4ytqII+qIgEoCrGDVSaM3+Ul7AtHv2zY=" crossorigin="anonymous"></script>
<div id="root"/>
Example with fully uncontrolled inputs. This doesn't need the vars at all:
const initialstate = {
user: '',
password: '',
};
const adminReducer = (state = initialstate, action) => {
switch (action.type) {
case 'admin':
return (state = {
user: action.user,
password: action.password,
});
default:
return initialstate;
}
};
const adminAction = (user, password) => {
return {
type: 'admin',
user: user,
password: password,
};
};
const store = Redux.createStore(Redux.combineReducers({ admin: adminReducer }));
const App = () => {
const store = ReactRedux.useStore();
const admin = ReactRedux.useSelector((state) => state.admin);
const dispatch = ReactRedux.useDispatch();
const click = (event) => {
event.preventDefault();
const user = event.currentTarget.user.value;
const pass = event.currentTarget.pass.value;
dispatch(adminAction(user, pass));
console.log('store has the correct value here', store.getState());
console.log('admin is the previous value of admin due to closures:', admin);
};
return (
<div>
<form onSubmit={click}>
<input name="user" placeholder="username" />
<input name="pass" type="password" placeholder="password" />
<button type="submit">submit</button>
</form>
<p>This shows the current value of user and password in the store</p>
<p>User: {admin.user}</p>
<p>Pass: {admin.password}</p>
</div>
);
};
ReactDOM.render(
<ReactRedux.Provider store={store}>
<App />
</ReactRedux.Provider>,
document.querySelector('#root')
);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js" integrity="sha256-7nQo8jg3+LLQfXy/aqP5D6XtqDQRODTO18xBdHhQow4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js" integrity="sha256-JuJho1zqwIX4ytqII+qIgEoCrGDVSaM3+Ul7AtHv2zY=" crossorigin="anonymous"></script>
<div id="root"/>
Why closures are relevant to the post
Because of the synchronous nature of the original code, it's possible to say that the reason the admin.user value that's logged isn't the new value is simply because the component hadn't re-rendered yet. However, the point remains that the admin value for that render is set in stone and will never change due to a closure. Even if React rendered synchronously with the redux state updating, admin wouldn't change.
It's for reasons like this that it's important to think about the closures and how everything fully works even in the simpler cases so you don't mess up in the more complicated ones.
For example of how incorrect mental models can hinder you, imagine that you assume that the reason the console.log(admin.user) doesn't show the correct value in the original example is solely because the component hadn't re-rendered yet. You might assume that putting a timeout to wait for the re-render would let you see the new value.
Imagine trying to add an auto-save functionality where you want to save the current value of admin to localstorage or an API every 10 seconds. You might put an effect with an interval of 10 seconds that logs admin with an empty dependency array because you want it to not reset until the component unmounts. This is clearly incorrect because it doesn't include the admin value in the dependencies, but it will highlight that no matter how many times you submit the values and change admin in the redux state, the value in this effect never changes because this effect will always be running from the initial value of admin on the first render.
useEffect(()=>{
const intervalId = setInterval(()=>{console.log(admin)},10000);
return ()=>clearInterval(intervalId);
},[])
Closures are the overall issue and you can easily get in trouble if you have the wrong mental model for how things work.
More information on closures:
A closure is the combination of a function bundled together (enclosed)
with references to its surrounding state (the lexical environment). In
other words, a closure gives you access to an outer function’s scope
from an inner function. In JavaScript, closures are created every time
a function is created, at function creation time.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
As mentioned in the MDN web docs, every time a function is created, the function gains permanent access to the variables in the surrounding code. Essentially, the functions get access to everything within scope for them.
In react function components, each render creates new functions.
In this example, when App is rendered:
const App = ()=>{
const admin = useSelector(state => state.admin);
const dispatch = useDispatch();
var user,pass = '';
const adminChangeUsername = (event) => {
user = event.target.value;
}
const adminChangePassword = (event) => {
pass = event.target.value;
}
const click= (event) => {
event.preventDefault();
dispatch(adminAction(user,pass));
console.log(store.getState())
console.log(admin.user)
}
}
the admin variable is initialized into the scope of App based on the current state of the redux store (i.e. { user: '', password: ''}).
The dispatch variable is initialized to be redux's store.dispatch
user is initialized to undefined
pass is initialized to ''
the adminChangeUsername and adminChangePassword functions are initialized. They have access to (and use) user and pass from the closure that they create.
click is initialized. It has access to (and uses) user, pass, store, and admin from it's closure.
click always only has access to the admin value from the current re-render when it was created because the click function created a closure around it when it was created. Once the click event occurs, and adminAction is dispatched, click won't change which render it was from, so it will have access to the admin variable that was initialized when it was. And each render, the admin variable is re-initialized and is thus a different variable.
Also the main issue here not just the asynchronous nature but the fact
that state values are used by functions based on their current
closures and state updates will reflect in the next re-render by which
the existing closures are not affected but new ones are created. Now
in the current state the values within hooks are obtained by existing
closures and when a re-render happens the closures are updated based
on whether function is recreated again or not
usestate-set-method-not-reflecting-change-immediately - Shubham Khatri
No matter if you put a timeout in the click handler, the admin value will never be the new value because of that closure. Even if React happens to re-render immediately after calling dispatch.
You can use a useRef to create a mutatable value who's current property will remain the same between re-renders, and at that point, the asynchronous nature of React's re-renders actually comes into play because you'd still have the old value for a few more miliseconds before react re-renders the component.
In this example https://codesandbox.io/s/peaceful-neumann-0t9nw?file=/src/App.js, you can see that the admin value logged in the timeout still has the old value, but the adminRef.current value logged in the timeout has the new value. The reason for that is because admin points to the variable initialized in the previous render, while adminRef.current stays the same variable between re-renders. Example of the logs from the code sandbox below:
The only thing that might really surprise you about the order of events in this is that useSelector is called with the new data before the re-render occurs. The reason for this is that react-redux calls the function whenever the redux store is changed in order to determine if the component needs to be re-rendered due to the change. It would be entirely possible to set the adminRef.current property within the useSelector to always have access to the latest value of admin without store.getState().
In this case, it's easy enough to just use store.getState() to get the current redux state if you need access to the latest state outside of the hook's closure. I often tend to use things like that in longer-running effects because store doesn't trigger re-renders when changed and can help performance in performance critical locations. Though, it should be used carefully for the same reason of it being able to always get you access to the latest state.
I used a timeout in this example because that's the best way to highlight the actual nature of the closure's effect in this. Imagine if you will that the timeout is some asynchronous data call that takes a bit of time to accomplish.
The admin object will be fed after the next component rerendering process, after the whole click callback has been executed.
Indeed, it is based on the value provided by useSelector that is triggered ONCE during a rendering.
admin.user being checked in the click callback, the component has not been reredenring yet, thus showing the current empty string value the first crossing.
1) Component is rendered for the first time.
2) useSelector is called.
3) Click is made.
4) Action is dispatched.
5) admin.user is still empty since 2) is has not been run again yet.
6) After click callback finishes, a rerendering is about to be made so useSelector is triggered again, grabbing the admin value!
Moral of the story:
Don't expect value provided from useSelector to be immediately synchronised within the click callback.
It needs rerendering process to happen.
Besides, and just to improve your code, you can replace:
return state = {
user: action.user,
password: action.password
}
by this:
return {
user: action.user,
password: action.password
}
Related
Mystery Parameter in setState, why does it work?
Going through a TypeScript + React course and building a todo list. My question is about a react feature though. In the handler for adding a Todo, there is this function declared in setState const App: React.FC= () => { const [todos, setTodos] = useState<Todo[]>([]) const todoAddHandler = (text: string) => { // when its called.... where does the prevTodos state come from? setTodos(prevTodos => [...prevTodos, {id: Math.random().toString(), text: text}]) } return ( <div className="App"> <NewTodo onAddTodo={todoAddHandler}/> <TodoList items={todos}></TodoList> </div> ); } export default App; When the function is called in setState, it automatically calls the current state with it. Is this just a feature of setState? That if you declare a function within it the parameter will always be the current state when the function is called? Was very confused when this parameter just... worked. :#
TL;DR Is this just a feature of setState? - Yes useState is a new way to use the exact same capabilities that this.state provides in a class Meaning that its core still relies on old this.setState({}) functionality. If you remember using this.setState(), you will know that it has a callback function available, which can be used like this: this.setState((currentState) => { /* do something with current state */ }) This has now been transfered to useState hook's second destructured item [item, setItem] setItem, thus it has the same capability: setItem((currentState) => { /* do something with current state */ }) You can read more about it here
With hooks, React contains an internal mapping of each state name to its current value. With const [todos, setTodos] = useState<Todo[]>([]) Whenever setTodos is called and todos state is set again, React will update the internal state for todos to the new value. It will also return the current internal state for a variable when useState is called. You could think of it a bit like this: // React internals let internalState; const setState = (param) => { if (typeof param !== 'function') { internalState = param; } else { param(internalState); } }; const useState = initialValue => { internalState ??= initialValue; return [internalState, setState]; } Then, when you call the state setter, you can either pass it a plain value (updating internalState), or you can pass it a function that, when invoked, is passed the current internal state as the first parameter. Note that the prevTodos parameter will contain the current state including intermediate updates. Eg, if you call setTodos twice synchronously before a re-render occurs, you'll need to use the callback form the second time in order to "see" the changes done by the first call of setTodos.
Too many re-render problems with React
I have some cards in my application that can lead to another pages through clicks. So I have a main component that contains a button like this: function MainComponent(props) { . . . const handleClick = (key) => { history.push("/exampleurl/" + key); }; Then according to the key passed, I have to make a request that gives me some information required to display it. As default I have my initial state as null, and when it completes the request, it changes to the object I got. But as soon as I click on the card, I get the re-render error. function MyComponent(props) { let { key } = useParams(); const [myObject, setMyObject] = React.useState(null) useEffect(() => { axios.get('/myendpoint/' + key).then( response => { let myObject = response.data setMyObject(myObject) }) }, [key]) I suppose that the solution is avoiding the key value to update when it changes the state. But i am not finding the solution to this trouble. Edit: The route that leads to the components: <Route path="/inbox"> <MainComponent /> </Route> <Route path="/exampleurl/:key"> <NewComponent /> </Route>
I think the problem is related to the handleClick function. Every time this method is called, you push a new entry to the history stack. Which analyze your defined routes and render the linked component. In your case, it is the same component, but I am not sure if the router is capable to determine it, therefore I would expect a re-render. Maybe a solution would be to include another state which is responsible to inform the component of the current obj being displayed on the screen. So key will be responsible only for the route parameter and this new state will be responsible for the internal navigation. function MyComponent(props) { let { key } = useParams(); const [myObject, setMyObject] = React.useState(null) const [displayedObj, setDisplayedObj] = React.useState(''); useEffect(() => { axios.get('/myendpoint/' + key).then( response => { let myObject = response.data setMyObject(myObject) setDisplayedObj(key) }) }, [key, displayedObj]) // we listen for displayedObj too and then in the handleClick we update this new state. This will trigger the useEffect and therefore update the myObject state to the new value: const handleClick = (key) => { setDisplayedObj(key); // This will trigger the useEffect and refresh // the data displayed without reloading the page };
React: how can I force state to update in a functional component?
This function component has a template method that calls onChangeHandler, which accepts a select value and updates state. The problem is, state does not update until after the render method is called a second time, which means the value of selected option is one step ahead of the state value of selectedRouteName. I know there are lifecycle methods in class components that I could use to force a state update, but I would like to keep this a function component, if possible. As noted in the code, the logged state of selectedRouteDirection is one value behind the selected option. How can I force the state to update to the correct value in a functional component? This question is not the same as similarly named question because my question asks about the actual implementation in my use case, not whether it is possible. import React, { useState, Fragment, useEffect } from 'react'; const parser = require('xml-js'); const RouteSelect = props => { const { routes } = props; const [selectedRouteName, setRouteName] = useState(''); const [selectedRouteDirection, setRouteDirection] = useState(''); //console.log(routes); const onChangeHandler = event => { setRouteName({ name: event.target.value }); if(selectedRouteName.name) { getRouteDirection(); } } /* useEffect(() => { if(selectedRouteName) { getRouteDirection(); } }); */ const getRouteDirection = () => { const filteredRoute = routes.filter(route => route.Description._text === selectedRouteName.name); const num = filteredRoute[0].Route._text; let directions = []; fetch(`https://svc.metrotransit.org/NexTrip/Directions/${num}`) .then(response => { return response.text(); }).then(response => { return JSON.parse(parser.xml2json(response, {compact: true, spaces: 4})); }).then(response => { directions = response.ArrayOfTextValuePair.TextValuePair; // console.log(directions); setRouteDirection(directions); }) .catch(error => { console.log(error); }); console.log(selectedRouteDirection); // This logged state is one value behind the selected option } const routeOptions = routes.map(route => <option key={route.Route._text}>{route.Description._text}</option>); return ( <Fragment> <select onChange={onChangeHandler}> {routeOptions} </select> </Fragment> ); }; export default RouteSelect;
Well, actually.. even though I still think effects are the right way to go.. your console.log is in the wrong place.. fetch is asynchronous and your console.log is right after the fetch instruction. As #Bernardo states.. setState is also asynchronous so at the time when your calling getRouteDirection();, selectedRouteName might still have the previous state. So to make getRouteDirection(); trigger after the state was set. You can use the effect and pass selectedRouteName as second parameter (Which is actually an optimization, so the effect only triggers if selectedRouteName has changed) So this should do the trick: useEffect(() => { getRouteDirection(); }, [selectedRouteName]); But tbh.. if you can provide a Stackblitz or similar, where you can reproduce the problem. We can definitely help you better.
setState is asynchronous! Many times React will look like it changes the state of your component in a synchronous way, but is not that way.
How do props work when used by a component declared inside another component?
I am making a multi-stage form in react. The overall component dependency structure is as follows: MainForm SubForm1 SubForm2 SubForm3 The MainForm component has two states called step and formData, and methods called handleNext, handleBack which modify the state step. It also has a method called handleChange, which reads the value from input fields present in SubForm* and update the state formData, so that on clicking back and next the formData stays there until a final API call has been made on the last SubForm3. Upon which the MainForm component is unmounted. The MainForm uses switch case to render a particular SubForm using the state step as the decision variable. I am passing the following to SubForms as props: formData handleNext handlePrev handleChange In SubForm1 I have the following piece of code: import React from 'react'; const SubForm1 = ({ formData, handleNext, handlePrev, handleChange, }) => { const FormInput = ({ attr }) => <input name={attr} onChange={handleChange} value={formData[attr]} />; return ( <FormContainer> <form> <input name='fullName' onChange={handleChange} value={field_values.fullName} /> <FormInput attr="employee_id" /> </form> <button onClick={prevStep}>Back</Button> <button onClick={nextStep}>Next</button> </FormContainer> ); } The handleChange method captures the user input via the onChange event and upadates the corresponding field. It is declared in MainForm.jsx as: // MainForm.jsx import React, { useState } from 'react'; const MainForm = () => { const [step, setStep] = useState(0); const [formData, setFormData] = useState({ fullName: '', employee_id: '' }); const handleChange = (event) => { event.preventDefault(); event.persist(); setFormData(prevState => ({ ...prevState, [event.target.name]: event.target.value, })); }; const handleNext = () => { setStep(old => (old + 1)); }; const handleBack = () => { setStep(old => (old - 1)); }; It works smoothly in the input field (fullName) - The value updates as the user types and the formData state remains intact on traversing through subforms. But for the employee_id input, following happens: A character is entered in the employee_id input field. The employee_id input field immediately looses focus. The user has to click on the employee_id input field again to gain focus (of course without which he/she cannot type into the field) Go To step 1. The state formData is however updated since on going to next/previous SubForm, the state formData remains intact. I feel that it has something to do with how I have declared the FormInput component. I am not passing handleChange as a prop to it from SubForm1, but instead relying on the scope. After googling a lot, I couldn't get an answer for my question, since search engines confused my question with answers relating to Component composition on any query containing 'declaring a component inside another component` etc. PS. I am relatively new to react and javascript, so please feel free to suggest any better ways to implement the above. Any help is highly appreciated.
sometimes event.persist(); is causing problem. If you want your event.target.name and event.target.value then just create two variables which will store these values. and remove event.persist(). just try with below code : const handleChange = (event) => { event.preventDefault(); // event.persist(); //comment out this line const name = event.target.name; const value = event.target.value; setFormData(prevState => ({ ...prevState, [name]: value, })); };
Updating and merging state object using React useState() hook
I'm finding these two pieces of the React Hooks docs a little confusing. Which one is the best practice for updating a state object using the state hook? Imagine a want to make the following state update: INITIAL_STATE = { propA: true, propB: true } stateAfter = { propA: true, propB: false // Changing this property } OPTION 1 From the Using the React Hook article, we get that this is possible: const [count, setCount] = useState(0); setCount(count + 1); So I could do: const [myState, setMyState] = useState(INITIAL_STATE); And then: setMyState({ ...myState, propB: false }); OPTION 2 And from the Hooks Reference we get that: Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax: setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; }); As far as I know, both works. So, what is the difference? Which one is the best practice? Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)?
Both options are valid, but just as with setState in a class component you need to be careful when updating state derived from something that already is in state. If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state. const { useState } = React; function App() { const [count, setCount] = useState(0); function brokenIncrement() { setCount(count + 1); setCount(count + 1); } function increment() { setCount(count => count + 1); setCount(count => count + 1); } return ( <div> <div>{count}</div> <button onClick={brokenIncrement}>Broken increment</button> <button onClick={increment}>Increment</button> </div> ); } ReactDOM.render(<App />, 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>
If anyone is searching for useState() hooks update for object Through Input const [state, setState] = useState({ fName: "", lName: "" }); const handleChange = e => { const { name, value } = e.target; setState(prevState => ({ ...prevState, [name]: value })); }; <input value={state.fName} type="text" onChange={handleChange} name="fName" /> <input value={state.lName} type="text" onChange={handleChange} name="lName" /> Through onSubmit or button click setState(prevState => ({ ...prevState, fName: 'your updated value here' }));
The best practice is to use separate calls: const [a, setA] = useState(true); const [b, setB] = useState(true); Option 1 might lead to more bugs because such code often end up inside a closure which has an outdated value of myState. Option 2 should be used when the new state is based on the old one: setCount(count => count + 1); For complex state structure consider using useReducer For complex structures that share some shape and logic you can create a custom hook: function useField(defaultValue) { const [value, setValue] = useState(defaultValue); const [dirty, setDirty] = useState(false); const [touched, setTouched] = useState(false); function handleChange(e) { setValue(e.target.value); setTouched(true); } return { value, setValue, dirty, setDirty, touched, setTouched, handleChange } } function MyComponent() { const username = useField('some username'); const email = useField('some#mail.com'); return <input name="username" value={username.value} onChange={username.handleChange}/>; }
Which one is the best practice for updating a state object using the state hook? They are both valid as other answers have pointed out. what is the difference? It seems like the confusion is due to "Unlike the setState method found in class components, useState does not automatically merge update objects", especially the "merge" part. Let's compare this.setState & useState class SetStateApp extends React.Component { state = { propA: true, propB: true }; toggle = e => { const { name } = e.target; this.setState( prevState => ({ [name]: !prevState[name] }), () => console.log(`this.state`, this.state) ); }; ... } function HooksApp() { const INITIAL_STATE = { propA: true, propB: true }; const [myState, setMyState] = React.useState(INITIAL_STATE); const { propA, propB } = myState; function toggle(e) { const { name } = e.target; setMyState({ [name]: !myState[name] }); } ... } Both of them toggles propA/B in toggle handler. And they both update just one prop passed as e.target.name. Check out the difference it makes when you update just one property in setMyState. Following demo shows that clicking on propA throws an error(which occurs setMyState only), You can following along Warning: A component is changing a controlled input of type checkbox to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. It's because when you click on propA checkbox, propB value is dropped and only propA value is toggled thus making propB's checked value as undefined making the checkbox uncontrolled. And the this.setState updates only one property at a time but it merges other property thus the checkboxes stay controlled. I dug thru the source code and the behavior is due to useState calling useReducer Internally, useState calls useReducer, which returns whatever state a reducer returns. https://github.com/facebook/react/blob/2b93d686e3/packages/react-reconciler/src/ReactFiberHooks.js#L1230 useState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] { currentHookNameInDev = 'useState'; ... try { return updateState(initialState); } finally { ... } }, where updateState is the internal implementation for useReducer. function updateState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] { return updateReducer(basicStateReducer, (initialState: any)); } useReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: I => S, ): [S, Dispatch<A>] { currentHookNameInDev = 'useReducer'; updateHookTypesDev(); const prevDispatcher = ReactCurrentDispatcher.current; ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV; try { return updateReducer(reducer, initialArg, init); } finally { ReactCurrentDispatcher.current = prevDispatcher; } }, If you are familiar with Redux, you normally return a new object by spreading over previous state as you did in option 1. setMyState({ ...myState, propB: false }); So if you set just one property, other properties are not merged.
One or more options regarding state type can be suitable depending on your usecase Generally you could follow the following rules to decide the sort of state that you want First: Are the individual states related If the individual state that you have in your application are related to one other then you can choose to group them together in an object. Else its better to keep them separate and use multiple useState so that when dealing with specific handlers you are only updating the relavant state property and are not concerned about the others For instance, user properties such as name, email are related and you can group them together Whereas for maintaining multiple counters you can make use of multiple useState hooks Second: Is the logic to update state complex and depends on the handler or user interaction In the above case its better to make use of useReducer for state definition. Such kind of scenario is very common when you are trying to create for example and todo app where you want to update, create and delete elements on different interactions Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)? state updates using hooks are also batched and hence whenever you want to update state based on previous one its better to use the callback pattern. The callback pattern to update state also comes in handy when the setter doesn't receive updated value from enclosed closure due to it being defined only once. An example of such as case if the useEffect being called only on initial render when adds a listener that updates state on an event.
Both are perfectly fine for that use case. The functional argument that you pass to setState is only really useful when you want to conditionally set the state by diffing the previous state (I mean you can just do it with logic surrounding the call to setState but I think it looks cleaner in the function) or if you set state in a closure that doesn't have immediate access to the freshest version of previous state. An example being something like an event listener that is only bound once (for whatever reason) on mount to the window. E.g. useEffect(function() { window.addEventListener("click", handleClick) }, []) function handleClick() { setState(prevState => ({...prevState, new: true })) } If handleClick was only setting the state using option 1, it would look like setState({...prevState, new: true }). However, this would likely introduce a bug because prevState would only capture the state on initial render and not from any updates. The function argument passed to setState would always have access to the most recent iteration of your state.
Both options are valid but they do make a difference. Use Option 1 (setCount(count + 1)) if Property doesn't matter visually when it updates browser Sacrifice refresh rate for performance Updating input state based on event (ie event.target.value); if you use Option 2, it will set event to null due to performance reasons unless you have event.persist() - Refer to event pooling. Use Option 2 (setCount(c => c + 1)) if Property does matter when it updates on the browser Sacrifice performance for better refresh rate I noticed this issue when some Alerts with autoclose feature that should close sequentially closed in batches. Note: I don't have stats proving the difference in performance but its based on a React conference on React 16 performance optimizations.
I find it very convenient to use useReducer hook for managing complex state, instead of useState. You initialize state and updating function like this: const initialState = { name: "Bob", occupation: "builder" }; const [state, updateState] = useReducer( (state, updates) => {...state, ...updates}, initialState ); And then you're able to update your state by only passing partial updates: updateState({ occupation: "postman" })
The solution I am going to propose is much simpler and easier to not mess up than the ones above, and has the same usage as the useState API. Use the npm package use-merge-state (here). Add it to your dependencies, then, use it like: const useMergeState = require("use-merge-state") // Import const [state, setState] = useMergeState(initial_state, {merge: true}) // Declare setState(new_state) // Just like you set a new state with 'useState' Hope this helps everyone. :)