Reputation: 25
I have this example code. I am using useState hook to create state urls. When I click the button "push" I am updating urls state with setUrls function. With urls state changed, react should re-render and display "hi". But it is not happening. What could be wrong?
import React, { useState } from "react";
import {
Button,
Text
} from "@chakra-ui/react"
function Hi () {
const [urls, setUrls] = useState([]);
return (
<div>
<Button onClick={(e) => {
let url_list = urls;
url_list.push("hi");
setUrls(url_list);
}}
>
Push
</Button>
{urls.map(item => (
<Text>{item}</Text>
))}
</div>
)
}
Upvotes: 0
Views: 1149
Reputation: 439
The reason the component is not rendering after the change is because of real nature of Objects, Arrays are Objects in javascript and comparisms is done by object reference not the content of the object For Example
const a = ['a','b','c','d','e']
// comparing a to itself is true
console.log(a === a) // This line returns true
const b = a;
console.log(a === b) // this line returns true because they both point to the same reference
// =========================NOW CONSIDER THIS EXAMPLE=========================================
const c = ['a','b','c'];
const d = ['a','b','c'];
console.log(c === d) // This will return false because they point to different object even though their content is the same
// NOW CONSIDER THIS
const e = [...c,'e'];
const h = Object.assign([],c);
h.push('e');
const f = c;
f.push('e');
console.log(e === c ) // false
console.log(f === c ) // true
console.log(f === h ) // false
For react to rerender the previous state must not be equal to the present state and since you are using array on the state the above shows that we have to return a new array and modifying your code i will just change one line and code will work fine
Your code modified(only one line)
import React, { useState } from "react";
import {
Button,
Text
} from "@chakra-ui/react"
function Hi () {
const [urls, setUrls] = useState([]);
return (
<div>
<Button onClick={(e) => {
let url_list = Object.assign([],urls); // copys urls to new array and returns new array to url_list
url_list.push("hi");
setUrls(url_list);
}}
>
Push
</Button>
{urls.map(item => (
<Text>{item}</Text>
))}
</div>
)
}
Upvotes: 1
Reputation: 20441
The reference needs to change when you are setting state, so React can identify that there is an update.
Here url_list
is just another reference to urls
.
let url_list = urls;
url_list.push("hi");
You can create a new Array, using Array.from
or simply spread operator:
let url_list = [...urls];
url_list.push("hi");
One should not mutate state in React, which means the state variable should not be updated directly. In the second method above you are creating a copy using ...
. But note that if urls
is an array of objects, then the inner object reference will still stay the same. Shallow vs Deep copy
Upvotes: 0
Reputation: 1146
You need to spread urls array when assigning it to the url_list.
Note: this would not deep copy the url array. For deep copy you can use lodash or any other library that alows deepCopy/clone.
Have a look at the implementation:
export default function App() {
const [urls, setUrls] = useState([]);
return (
<div>
<button
onClick={(e) => {
setUrls([...urls, 'hi']);
}}
>
Push
</button>
{urls.map((item) => (
<div>{item}</div>
))}
</div>
);
}
Upvotes: 0