I'm calling console.log as a callback from setState and it seems like setState doesn't work. I'm trying to write a function which takes state defined like this:
this.state = {
...this.props.substate
};
this.props.substate is an array of objects, so I'm transforming it to object.
Here is a function that transforms state back to array, removes last element and then transforms array into object:
handleClickRem = event => {
event.preventDefault();
console.log(this.state);
const arrayFromState = Object.values(this.state);
console.log(arrayFromState);
const newArray = arrayFromState.slice(0, -1);
console.log('newArray',newArray);
const newState = {...newArray};
console.log(newState);
//newState is exactly what I want it to be
this.setState(newState, () => console.log(this.state));
};
All console logs works perfectly apart from the last one. I'm also checking state in render(), but it also seems to show no change of state.
I've done similarly a function, which adds elements to state and it works perfectly.
I'll be very thankful for help and patience :)
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 am using React Hooks to manage states within a component.
const addNode = () => {
let pform = pForm
let handles = [vForm, yForm, hForm]
let access_info = [virtualForm, management1Form, management2Form, consoleForm]
let newObj = {
...currentForm,
p: pform,
handles: handles,
access_info: access_info,
}
console.log('newObj', newObj)
setCurrentForm(
newRouterObj
)
console.log(currentForm)
let currArr = [...addedNodes]
currArr.push(currentForm)
setAddedNodes(currArr)
intializeForms()
}
The function above is an onClick that I use when I press an Add button. The forms (pForm, vForm, yForm, etc.) are all separate states. I gather them together and put them into a single object newObj and use setCurrentForm to update the currentForm state to newObj.
When I console.log the newObj, everything goes in fine. However, when I check the currentForm after the setCurrentForm, the fields (p, handles, and access_info) are empty.
I know that states in React can have a delay in updates so I might have to use useEffect. However, in my use case, which is to gather different states and put them in as a new field in the currentForm state seems useEffect is not the best way to solve it. Can anyone help please?
You are misunderstanding exactly how useState works. When you call the useState setter function, the state value isn't actually updated immediately, instead it will trigger the component to re-render with the updated value. Even though you call the setter half way through the function, the state value will remain the original value for the entire lifetime of that function call.
You could slightly tweak what you have to be
const addNode = () => {
...
let currArr = [...addedNodes]
// you know that currentForm is supposed to be newObj, so just push that
// see my explanation above to understand why it currentForm isn't what you expect
currArr.push(newObj)
...
}
It's an async action so values will not be assigned/updated instantly. You need to watch for the changes using useEffect hook to log new values and to do anything in case
useEffect(() => {
// Whenever `currentForm` will be updated, this callback will be invoked
console.log('updated currentForm values', currentForm);
},[currentForm]);
I don't know exactly what it is, but I have run into countless problems in trying to do the simplest state updates on arrays using hooks.
The only thing that I have found to work is using the useReducer to perform a single update on the array with putting dispatch on onClick handlers. In my current project, I am trying to update array state in a for loop nested in a function that runs on a form submit. I have tried many different solutions, and this is just one of my attempts.
function sessionToState(session) {
let formattedArray = []
for (let i = 0; i < session.length; i++) {
formattedArray.push({ url: session[i] })
setLinksArray([...linksArray, formattedArray[i]])
}
}
// --------------------------------------------------------
return (
<div>
<form
method="post"
onSubmit={async e => {
e.preventDefault()
const session = await getURLs({ populate: true })
sessionToState(session)
await createGroup()
I was wondering if there are any big things that I am missing, or maybe some great tips and tricks on how to work with arrays using hooks. If any more information is needed don't hesitate to ask. Thanks.
I was wondering if there are any big things that I am missing
TLDR: setLinksArray does not update linksArray in the current render, but in the next render.
Assuming the variables are initialized as follows:
const [linksArray, setLinksArray] = useState([])
A hint is in the const keyword, linksArray is a constant within 1 render (and this fact wouldn't change with let, because it's just how useState works).
The idea of setLinksArray() is to make a different constant value in the next render.
So the for loop would be similar to:
setLinksArray([...[], session0])
setLinksArray([...[], session1])
setLinksArray([...[], session2])
and you would get linksArray = [session2] in the next render.
Best way to keep sane would be to call any setState function only once per state per render (you can have multiple states though), smallest change to your code:
function sessionToState(session) {
let formattedArray = []
for (let i = 0; i < session.length; i++) {
formattedArray.push({ url: session[i] })
}
setLinksArray(formattedArray)
}
Furthermore, if you need to perform a side effect (like an API call) after all setState functions do their jobs, i.e. after the NEXT render, you would need useEffect:
useEffect(() => {
...do something with updated linksArray...
}, [linksArray])
For a deep dive, see https://overreacted.io/react-as-a-ui-runtime
When invoking state setter from nested function calls you should use functional update form of setState. In your case it would be:
setLinksArray(linksArray => [...linksArray, formattedArray[i]])
It is not exactly clear what kind of problems you encounter, but the fix above will save you from unexpected state of linksArray.
Also this applies to any state, not only arrays.
Performance wise you shouldn't call setState every iteration. You should set state with final array.
const sessionToState = (session) => {
setLinksArray(
session.map(sessionItem => ({url: sessionItem}))
);
}
... or if you want to keep old items too you should do it with function inside setState ...
const sessionToState = (session) => {
setLinksArray(oldState => [
...oldState,
...session.map(sessionItem => ({url: sessionItem}))
]);
}
I am using the code below:
makeRemoteRequest = () => {
let items = [];
models.forEach(element => { //models is an array of the list of models
this.pushReports(element, items);
});
console.log("This is the item array: ",items);
this.setState({
data:items
});
console.log("This is the data in state: ",this.state.data);
}
Somehow, the console log for the items array is showing me the array that I need, but the console log for the this.state.data is empty. How can this be possible? The log for the items array is run right before state is set.
This is preventing me from updating my state.
this.setState is rendering asynchronously. And you're trying to print in next line so it will not give immediate results as you want.
Solution: do this in next line,
setTimeout(() => {console.log("This is the data in state: ",this.state.data) }, 1000)
this.setState() does not run synchronously. Your state is not guaranteed to be updated on the next immediate line, but it will be updated properly on the next render cycle. Try putting console.log within render() and you'll see.
Discussion about this topic here: https://github.com/facebook/react/issues/11527#issuecomment-360199710
Since setState works in an asynchronous way. That means after calling setState the this.state is not immediately changed. So if you want to perform an action immediately after setting state, use 2nd argument as callback on setState. Consider this example:
this.setState({
data: newData
}, () => {
//TODO: Do something with this.state here
});
I have a React Class that's going to an API to get content. I've confirmed the data is coming back, but it's not re-rendering:
var DealsList = React.createClass({
getInitialState: function() {
return { deals: [] };
},
componentDidMount: function() {
this.loadDealsFromServer();
},
loadDealsFromServer: function() {
var newDeals = [];
chrome.runtime.sendMessage({ action: "findDeals", personId: this.props.person.id }, function(deals) {
newDeals = deals;
});
this.setState({ deals: newDeals });
},
render: function() {
var dealNodes = this.state.deals.map(function(deal, index) {
return (
<Deal deal={deal} key={index} />
);
});
return (
<div className="deals">
<table>
<thead>
<tr>
<td>Name</td>
<td>Amount</td>
<td>Stage</td>
<td>Probability</td>
<td>Status</td>
<td>Exp. Close</td>
</tr>
</thead>
<tbody>
{dealNodes}
</tbody>
</table>
</div>
);
}
});
However, if I add a debugger like below, newDeals are populated, and then once I continue, i see the data:
loadDealsFromServer: function() {
var newDeals = [];
chrome.runtime.sendMessage({ action: "findDeals", personId: this.props.person.id }, function(deals) {
newDeals = deals;
});
debugger
this.setState({ deals: newDeals });
},
This is what's calling deals list:
var Gmail = React.createClass({
render: function() {
return (
<div className="main">
<div className="panel">
<DealsList person={this.props.person} />
</div>
</div>
);
}
});
I'd like to add to this the enormously simple, but oh so easily made mistake of writing:
this.state.something = 'changed';
... and then not understanding why it's not rendering and Googling and coming on this page, only to realize that you should have written:
this.setState({something: 'changed'});
React only triggers a re-render if you use setState to update the state.
My scenario was a little different. And I think that many newbies like me would be stumped - so sharing here.
My state variable is an array of JSON objects being managed with useState as below:
const [toCompare, setToCompare] = useState([]);
However when update the toCompare with setToCompare as in the below function - the re-render won't fire. And moving it to a different component didn't work either. Only when some other event would fire re-render - did the updated list show up.
const addUniversityToCompare = async(chiptoadd) =>
{
var currentToCompare = toCompare;
currentToCompare.push(chiptoadd);
setToCompare(currentToCompare);
}
This was the solution for me. Basically - assigning the array was copying the reference - and react wouldn't see that as a change - since the ref to the array isn't being changed - only content within it. So in the below code - just copied the array using slice - without any change - and assigned it back after mods. Works perfectly fine.
const addUniversityToCompare = async (chiptoadd) => {
var currentToCompare = toCompare.slice();
currentToCompare.push(chiptoadd);
setToCompare(currentToCompare);
}
Hope it helps someone like me. Anybody, please let me know if you feel I am wrong - or there is some other approach.
Thanks in advance.
That's because the response from chrome.runtime.sendMessage is asynchronous; here's the order of operations:
var newDeals = [];
// (1) first chrome.runtime.sendMessage is called, and *registers a callback*
// so that when the data comes back *in the future*
// the function will be called
chrome.runtime.sendMessage({...}, function(deals) {
// (3) sometime in the future, this function runs,
// but it's too late
newDeals = deals;
});
// (2) this is called immediately, `newDeals` is an empty array
this.setState({ deals: newDeals });
When you pause the script with the debugger, you're giving the extension time to call the callback; by the time you continue, the data has arrived and it appears to work.
To fix, you want to do the setState call after the data comes back from the Chrome extension:
var newDeals = [];
// (1) first chrome.runtime.sendMessage is called, and *registers a callback*
// so that when the data comes back *in the future*
// the function will be called
chrome.runtime.sendMessage({...}, function(deals) {
// (2) sometime in the future, this function runs
newDeals = deals;
// (3) now you can call `setState` with the data
this.setState({ deals: newDeals });
}.bind(this)); // Don't forget to bind(this) (or use an arrow function)
[Edit]
If this doesn't work for you, check out the other answers on this question, which explain other reasons your component might not be updating.
Another oh-so-easy mistake, which was the source of the problem for me: I’d written my own shouldComponentUpdate method, which didn’t check the new state change I’d added.
To update properly the state, you shouldn't mutate the array. You need to create a copy of the array and then set the state with the copied array.
const [deals, setDeals] = useState([]);
function updateDeals(deal) {
const newDeals = [...deals]; // spreading operator which doesn't mutate the array and returns new array
newDeals.push(deal);
// const newDeals = deals.concat(deal); // concat merges the passed value to the array and return a new array
// const newDeals = [...deals, deal] // directly passing the new value and we don't need to use push
setDeals(newDeals);
}
In my case, I was calling this.setState({}) correctly, but I my function wasn't bound to this, so it wasn't working. Adding .bind(this) to the function call or doing this.foo = this.foo.bind(this) in the constructor fixed it.
My issue was that I was using 'React.PureComponent' when I should have been using 'React.Component'.
I was updating and returning the same object passed to my reducer. I fixed this by making a copy of the element just before returning the state object like this.
Object.assign({}, state)
I was going through same issue in React-Native where API response & reject weren't updating states
apiCall().then(function(resp) {
this.setState({data: resp}) // wasn't updating
}
I solved the problem by changing function with the arrow function
apiCall().then((resp) => {
this.setState({data: resp}) // rendering the view as expected
}
For me, it was a binding issue. Using arrow functions solved it because arrow function doesn't create its's own this, its always bounded to its outer context where it comes from
After looking into many answers (most of them are correct for their scenarios) and none of them fix my problem I realized that my case is a bit different:
In my weird scenario my component was being rendered inside the state and therefore couldn't be updated.
Below is a simple example:
constructor() {
this.myMethod = this.myMethod.bind(this);
this.changeTitle = this.changeTitle.bind(this);
this.myMethod();
}
changeTitle() {
this.setState({title: 'I will never get updated!!'});
}
myMethod() {
this.setState({body: <div>{this.state.title}</div>});
}
render() {
return <>
{this.state.body}
<Button onclick={() => this.changeTitle()}>Change Title!</Button>
</>
}
After refactoring the code to not render the body from state it worked fine :)
If someone is here for similar problem, but using React Functional components rather class components AND also using react reducer, --> Move your api call outside of the reducer. Reducer should never do an api call. Refer to https://stackoverflow.com/a/39516485/12121297 for detailed response
In my case the issue was related to a child component.
In a nutshell:
Parent component updates the state of an array of objects and
forwards the updated array to a child component.
The child component receives the array, creates a copy of if (spread
operator) and uses it to update an internal stateful component which
is supposed to be rendered.
However, react does not re-render the child component when the update
occurs in the parent component.
I solved my problem by adding a useEffect hook in the child components.
I still don’t really understand how the state update is handled in my case.
The code is available in my stackblitz repo here:
https://stackblitz.com/edit/react-hel9yv?file=src%2FApp.js