I have some data (objects) that live in their own file (Origin.js).
This data is being exported using the spread operator within another object named OriginState:
Origin.js
//info
const info = {
title: '',
year: '',
};
//images
const images = {
logo: '',
heroImage: '',
};
//text
const text = {
header: '',
body: '',
};
export const OriginState = {
...info,
...images,
...text,
};
I am importing my OriginState object in another file and using it as state for my app like this:
OtherFile.js
import { OriginState } from './Origin.js';
const [state, setState] = useState({
...OriginState,
});
Here is an example handler where I am using this state to update some specified state values in an input later:
const handleChange = (e) => {
const { name, value } = e.target;
setState((state) => ({
...state,
[name]: value,
}));
};
Now, my question is... Is it incorrect to store state like this?
Additionally, am I using setState incorrectly in my handler function?
In most cases I've seen state declared and updated like this which is obviously easier to read:
const [count, setCount] = useState(0);
setCount(count + 1)
But I have a lot of state and didn't think it would be a good idea to have multiple setState hooks.
Is there a better way to do this? What I currently have just feels wrong.
Is it incorrect to store state like this?
const handleChange = (e) => {
const { name, value } = e.target;
setState((state) => ({
...state,
[name]: value,
}));
};
Nope, not at all, in fact, it is often the preferable pattern for state updates.
Any time your state update depends on the previous state, i.e. the classic counter example, or in your case, when there is nested state, you should use a functional state update to update from the previous state instead of the state from the previous render cycle.
Additionally, am I using setState incorrectly in my handler function?
In most cases I've seen state declared and updated like this which is
obviously easier to read:
const [count, setCount] = useState(0);
setCount(count + 1)
I see no issue with your state update logic in the handler. In this count example it would (should) be considered incorrect to update a count like this. See this codesandbox demo that attempts to show the issue between non-functional and functional state updates.
The correct state update should be setCount(count => count + 1)
But I have a lot of state and didn't think it would be a good idea to
have multiple setState hooks.
Is there a better way to do this? What I currently have just feels
wrong.
When it comes to form inputs and state I think it makes sense to have a single flat object. There isn't really a right or wrong answer in general though when it comes to using a single useState hook with "complex" state shape, or to use a single useState hook for each "chunk" of state. It's an opinionated answer, mostly do what makes sense for a specific use-case.
Generally though I'd say if a set of values are even loosely related then perhaps it makes sense to store them in a common object, but this is my opinion.
A potential issue I see with your imported data though is the chance that you may inadvertently overwrite some key-value pairs by the use of the Spread syntax.
export const OriginState = {
...info,
...images, // <-- could overwrite values from info
...text, // <-- could overwrite values from info and images
};
Your approach seems quite legit, and this is one of the best practices that if your newState is depend on the oldState use setState callback and get the old state from callback input because otherwise as you showed above if you use it like this:
const [count, setCount] = useState(0);
setCount(count + 1)
you may increase the chance to get stale data which will increase the potential for bug
Related
Probably it is a classic issue with useState which is not updating.
So there is a tree with some checkboxes, some of them are already checked as they map some data from an endpoint.
The user has the possibility to check/uncheck them. There is a "cancel" button that should reset them to the original form.
Here is the code:
const [originalValues, setOriginalValues] = useState<string[]>([]);
...
const handleCancel = () => {
const originalValues = myData || []; //myData is the original data stored in a const
setOriginalValues(() => [...myData]);
};
...
useEffect(() => {
setOriginalValues(originalValues);
}, [originalValues]);
However, it is not working, the tree is not updating as it should. Is it something wrong here?
Just do the following, no need for ()=> the state will update inside the hook if called, plus change the constant it will cause confusion inside your code and protentional name clash later on, with the current state variable name, and also make sure your data are there and you are not injection empty array !!!! which could be the case as well !.
// Make sure data are available
console.log(myData)
// Then change the state
setOriginalValues([...myData]);
I have started learning React and developing an application using ReactJS. Recently i have moved to React Hooks. I know that in Class Component we can get the latest data from the state with the 2nd argument which is present in setState() like
state = {name: ''};
this.setState({name: name}, () => {console.log(this.state)});
I wanted to know if there is any arguments in React Hooks with which we can get the latest data from it.
I am using React Hooks in the below mentioned way, but upon console logging it always return the previous state
Hooks Eg:
const [socialData, setSocialData] = useState([
{ id: new Date().getTime().toString(), ...newItem }
]);
const onChangeCallback = (index, type, data) => {
let newSocialData = [...socialData];
newSocialData[index] = { ...newSocialData[index], [type]: data };
setSocialData(newSocialData);
onInputChange(newSocialData, formKey);
console.log(newSocialData);
};
The this.setState() second argument is not exactly to get the latest data from the state, but to run some code after the state has been effectively changed.
Remember that setting the state is an asynchronous operation. It is because React needs to wait for other potential state change requests so it can optimize changes and perform them in a single DOM update.
With this.setState() you can pass a function as the first argument, which will receive the latest known state value and should return the new state value.
this.setState((previousState) => {
const newState = previousState + 1;
return newState;
}, () => {
console.log('State has been updated!')
});
With that being said, there are special and rare cases when you need to know exactly when the state change has taken place. In many years of working with React I only faced this scenario once and I consider it a desperate attempt to make things work.
Usually, during a callback execution, like your onChangeCallback you want to change the state as the last thing your function does. If you already know the new state value, why do you want to wait for the real state to change to use it?
The new state value should be a problem to be handled during the next render.
If you want to run some code only when that particular state value changes you can do something like this:
import React, {useState, useEffect, useCallback} from 'react';
function MyComponent() {
const [value, setValue] = useState(false);
const onChangeHandler = useCallback((e) => {
setValue(!!e.target.checked);
}, []);
useEffect(() => {
// THIS WILL RUN ONLY WHEN value CHANGES. VERY SIMILAR TO WHAT YOU ARE TRYING TO DO WITH THE this.setState SECOND ARGUMENT.
}, [value]);
return (
<input type='checkbox' onChange={onChangeHandler} />
);
}
There is also a way to create a custom useState hook, to allow you passing a second argument to setValue and mimic the behavior of this.setState, but internally it would do exactly what I did in the above component. Please let me know if you have any doubt.
I'm currently learning React and trying to understand how to update the state correctly in a function component.
I've learned from the "State and Lifecycle" section in the React docs that you should update the state if it's using the previous state like the following:
// Correct
this.setState((state, props) => ({
counter: state.counter + 1
}));
instead of
// Wrong
this.setState({
counter: this.state.counter + 1,
});
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. (Reference)
Now I want to achieve the same with a function component using the State Hook useState.
As an example how to update the state in a function component the React docs has the following code snippet:
<button onClick={() => setCount(count + 1)}>
Click me
</button>
which is equivalent to the following code in a class component:
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
But isn't this the "wrong" solution because it relies directly on the state?
But isn't this the "wrong" solution because it relies directly on the state?
setState merges the new state into the old one. And does it in a 'piecemeal' fashion e.g. new state can contain a small subset of old state's data. So it's beneficial to have the old state at hand inside the setState body and explicitly merge it using this.setState(oldState => ({ ...oldState, someProp: true }) )
'Explicitly' is better because it shows you don't have illusions about what setState will do anyway: merging even if you didn't.
useState replaces the old state with the new one so the need to have access to oldState is less acute. Even though I wouldn't mind it.
As for updated asynchronously, both setState and useState can batch updates. Although useState won't do batching when dealing with pending Promises, as opposed to ready-to-use values like count + 1 from your sample.
Coming back to setState, there is another reason why the flavor setState(oldState => ({...}) is better. Remember, setState always do merging no matter what. Combined with batching, it can lead to the following piece code executed by React if the flavor setState(newValue) was used:
Object.assign(oldState, newValue1, newValue2, newValue3)
where the values newValueX are the batched updates. Object.assign ensures that 'last update wins'. If the series of newValueX represents successive attempts to increment the same counter then it might not work as expected. Therefore the other flavor of setState is better. With useState and batching it looks like this kind of danger persists.
You can do something like following.
import React, { userState } from 'react';
function TestHook() {
const [count, setCount] = useState(0);
function updateCount() {
setCount(count + 1);
}
return <button onClick={handleCount}>{count}</button>
}
Check it here. https://codesandbox.io/s/one-with-the-hook-0yig1
You can follow it in my Medium post (https://blog.usejournal.com/getting-started-with-react-hooks-f0b5c1e3e0e7)
I'm studying React JS making a simple todo list and part of my code is as follows:
changeStatus(e, index) {
this.state.tasks[index].status = e.target.value;
this.setState((prevState) => ({
tasks: prevState.tasks
}));
}
As React documentation says, we shouldn't change state manually, we should call setState instead, so, I'm receiving a warning on console saying that, even calling setState after the manual changing.
I'm doing so because, for me, is more practical doing that than separating the task, doing a splice on my array, and calling setState with the new values.
Is there a problem with my approach? How would you recommend updating state in a efficient and non-repetitive way?
Try this :
changeStatus(e, index) {
const tmp = { ...this.state };
tmp.tasks[index].status = e.target.value;
this.setState(tmp);
}
You should not mutate the state directly, to do that create a new object from the state and then update that and set it back to state like
changeStatus(e, index) {
var tasks = {...this.state.tasks}
tasks[index].status = e.target.value;
this.setState({tasks});
}
In case you are wondering as to what {...this.state.tasks} do then check this answer:
What is the meaning of this syntax "{...x}" in Reactjs
However you can also make a copy of the state using the ES5 Object.assign() operator like
var tasks = Object.assign({}, this.state.tasks)
We're using Redux.js with React.js (React-Redux), and in the reducer we're using the following code (we are using redux-actions to reduce boilerplate):
const update = require('react/lib/update');
const myReducer = handleActions
({
[myAction]: (state, action) => {
state = update(state, {
something: {$set: !state.someProperty}
});
return someFunction(state);
}
})
We are using update from React Immutability Helpers but assigning the result into the state using state =.
Is this against the basic Redux guidlines - i.e. mutating the state? It seems so, but the code seems to work perfectly and quickly, and the redux devtools shows the state changes correctly...
Your example is okay, because you're not actually mutating the contents of the object that the state variable points to. Instead, you're just updating the local variable named state to point to a different object instead.
It's important to understand how variables and references work in Javascript, which will help clear up what's going on here.
Personally, I'd suggest doing const newState = update(....) for clarity.