Hi,
React compares by reference, in the setState(newState) call, you are just setting state to the same list. Changing the contents of the list doesn't change the reference to the list. When the next state is the same as the old state, React bails out on doing any work.
One quick thing you can do is to slice the array like:
const newData = oldData.slice(0); newData[0] = 'something' setState(newData);
Here's an even more in-depth explanation: https://pavi2410.me/blog/dont-use-usestate-to-handle-arrays-in-react, you don't have to do as they say in the article (mutating and forcing render), but it helps you wrap your head around the issue.
Reactivity by assignment just doesn't work like that in JavaScript, you need a compiler like Svelte does, or you could use a library like https://github.com/immerjs/immer which implements Proxy to achieve reactivity by assignment. Or use getters/setters.
Understanding how JavaScript works is key here, you can just try this out in your browser console:
let arr = [0,1,2,3] let barr = arr barr[2] = 100000 barr === arr // true
So React can't tell that it is different data, and diffing the entire list would be also not guaranteed, because the contents of list, could be references themselves (not primitive data such as numbers, strings, etc.)!
When updating your state using hooks, it's best to use callbacks.
Try updating your code:
setNames(prevNames => [...prevNames, ...newNames])
This works in the same way that prevState worked with setSate().
On to your solution, you can set the initial state within useState():
const array = [
{
age: 13,
name: "Housing"
},
{
age: 23,
name: "Housing"
}
];
const [names, setNames] = useState(() => array.map(item => item.name));
useEffect(() => {
console.log(names);
}, []);
You should have the useEffect() subscribe to both the contentLoading and array props, so it runs whenever there is a change to either, instead of running a single-time after the first mount.
Working code and sandbox: https://codesandbox.io/s/mutable-mountain-wgjyv
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const array = [
{
age: 13,
name: "Housing"
},
{
age: 23,
name: "Housing"
}
];
const App = () => {
const [loading, setLoading] = useState(false);
useEffect(() => {
setTimeout(() => {
setLoading(true);
}, 2000);
}, []);
return <Child array={array} contentLoading={loading} />;
};
const Child = ({ array, contentLoading }) => {
const [names, setNames] = useState([]);
useEffect(() => {
createArray();
}, [contentLoading, array]);
const createArray = () => {
if (contentLoading) {
const newNames = array.map(item => item.name);
setNames([...names, ...newNames]);
}
};
return (
<div>
{names.map(name => (
<div>{name}</div>
))}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
React useState() array not updating
useState not updating an array at all
React useState Help - Update item in Array of Objects
Need help with useState hook - can't update an array using the spread operator
The problem appears to be that you're passing to useState() the array (updatedVal) that has its reference unchanged, thus it appears to React that your data hasn't been modified and it bails out without updating your state.
Try drop that unnecessary variable and do directly setCountries([...countries, obj])
Another minor fix about your code I may suggest: you may use Array.prototype.every() to make sure that every existing item has different label. It has two advantages over .filter() - it will stop looping right upon hitting duplicate (if one exists) and won't proceed till the end of array (as .filter() does), thus won't slow down unnecessarily re-render and it returns boolean, so you won't actually need extra variable for that.
Following is a quick demo of that approach:
const { useState, useEffect } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const CountryList = () => {
const [countries, setCountries] = useState([])
useEffect(() => {
fetch('https://run.mocky.io/v3/40a13c3b-436e-418c-85e3-d3884666ca05')
.then(res => res.json())
.then(data => setCountries(data))
}, [])
const addCountry = e => {
e.preventDefault()
const countryName = new FormData(e.target).get('label')
if(countries.every(({label}) => label != countryName))
setCountries([
...countries,
{
label: countryName,
value: countries.length+1
}
])
e.target.reset()
}
return !!countries.length && (
<div>
<ul>
{
countries.map(({value, label}) => (
<li key={value}>{label}</li>
))
}
</ul>
<form onSubmit={addCountry}>
<input name="label" />
<input type="submit" value="Add country" />
</form>
</div>
)
}
render (
<CountryList />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
I have found the root problem. In your Select Box component you have:
const [defaultValueoptions, setdefaultValueoptions] = useState(options);
It should be:
const [defaultValueoptions, setdefaultValueoptions] = useState([]);
useEffect(() => {
setdefaultValueoptions(options);
}, [options]);
I am fairly new to React and I think I am missing an important concept about state.
I am trying to update a state variable, specifically an array. The following function successfully updates the array if I use Array.push(), but not if I use destructuring/the spread operator. What am I missing? Also, the template is completely non-reactive - even when the console.log statement displays the correct value, the HTML does not.
function MyComponent() {
const [selection, setSelection] = useState([]);
const handleSelection = (id) => {
// doesn't work
// let newSelection = [...selection, id];
let newSelection = selection;
newSelection.push(id);
// Prints the correct value when using Array.push() but not destructuring
console.log(selection);
setSelection(newSelection);
};
return (
{/* never changes */}
<pre>{JSON.stringify(selection, null, 2)}</pre>
)
}
EDIT: I thought this was a problem with how I was setting state on the selection variable, but it turns out the issue was with other parts of my code that are tangentially related to this. The problem was that I was trying to to loop over some data and generate a component for each datum, but I wasn't doing that correctly. I was setting a different state variable to hold these components, and the various pieces of state were out of sync. Here is a repro of the working code, where I am mapping the components correctly (in the template directly rather than setting state, which seems to be better practice). As you can see, spread/destructuring works perfectly as expected now.
{selection}
handleSelection("testId")}>CLICK ME ); };basically I have a state
const [rooms,setRooms]=useState([])
const [track,setTrack]=useState(0)
useEffect(()=>{
const ws = new WebSocket(urls);
//problem occurs here
ws.onmessage=function(e){
if(e.command==="joined"){
//e.rooms gives array
setRooms(e.rooms) //state not updating here
//console.log(rooms) gives empty array
setTrack(rooms[track]) //track doesnot have value as rooms is empty
}
}
},[])Please help , I have wasted whole day because of this .
It's my first personal project and I'm stuck with two problems. I already tried a lot of different things, watched videos, and checked some codes, but still, nothing worked. I feel like it's something very basic that I'm forgetting.
The project is a Notepad app, a very simple one that I intend to add more features to later on.
However, every time my "send" button is clicked, the value doesn't get saved, it only shows up in the array on the second click.
Then, my list of 'Saved Notes' won't render the new note when "send" is clicked, only when I reload the page. I haven't been successful in using useEffect for this.
pages/home.jsx
import NotePad from "../Components/NotePad";
import StickNotes from "../Components/StickNotes";
export default function Home() {
return (
<main>
Note Padding
<NotePad />
<StickNotes />
</main>
)
}------
components/NotePad.jsx
import { useState } from 'react';
export default function NotePad() {
const [title, setTitle] = useState('')
const [note, setNote] = useState('')
const [noteList, setNoteList] = useState([])
function submitNote() {
const noteObj = {
id: Math.random(),
title,
note
}
setNoteList([...noteList, noteObj])
localStorage.setItem('noteList', JSON.stringify(noteList))
setNote('')
setTitle('')
}
return (
<form>
<input
label="note-title"
placeholder="Note Title"
type="text"
value={title}
onChange={ (e) => setTitle(e.target.value) }
/>
<input
label="note-text"
placeholder="Write your note here"
type="text"
value={note}
onChange={ (e) => setNote(e.target.value) }
/>
<button
type="button"
onClick={ () => submitNote() }
>
Send
</button>
</form>
)
}-------
components/StickNotes.jsx
export default function StickNotes() {
const list = JSON.parse(localStorage.getItem('noteList'))
return (
<div>
{ list && (list
.map(note => (
<div key={ note.id }>
<h1>{ note.title }</h1>
<div>{ note.note }</div>
</div>
)))
}
</div>
)
}
demonstration
The initial data is an array, see in countriesData variable, also in setName you try to use {}.
You should call with [] instead of {} as:
setName([ ...name, ...arr ]);
You need to do:
setName([ ...name, ...arr ]);
or
setName((prev) => [...prev, ...arr]);
and also update data:
<CountryName data={name} />
Here is full code:
import React, { useState } from "react";
import "./styles.css";
const countriesData = [
{ id: 1, name: "India", person: "Harpreet" },
{ id: 2, name: "Aus", person: "Monty" },
{ id: 3, name: "UK", person: "Preet" }
];
function CountryName({ data }) {
return (
<React.Fragment>
{data.map((element, index) => (
<div key={index}>
<h1>{element.person}</h1>
<span>from : {element.name}</span>
</div>
))}
</React.Fragment>
);
}
export default function App() {
const [name, setName] = useState(countriesData);
const arr = [{ id: name.length + 1, name: "Pak", person: "Mnm" }];
const AddMe = () => {
setName((prev) => [...prev, ...arr]);
};
return (
<>
<CountryName data={name} />
<button onClick={AddMe}> Add me</button>
</>
);
}
Here is the demo: https://codesandbox.io/s/keen-buck-ue9sn?file=/src/App.js:0-826