0

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>
  )
}

3 Answers3

1

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>
  )
}
solomon Yunana
  • 409
  • 3
  • 6
  • I think this is a great answer because it gives examples of JS object equality – BEVR1337 Jan 14 '22 at 04:18
  • 1
    yeah once one understands JS objec equality it can help to detect common bugs in codebase – solomon Yunana Jan 14 '22 at 04:22
  • 1
    Since concat returns a new value, this could be simplified to `setUrls(urls.concat(["hi"])`, or spread `setUrls([...urls, "hi"]);`. You can also pass callbacks to setters: `setUrls((previousUrls) => previousUrls.concat([newUrl]));` – BEVR1337 Jan 14 '22 at 08:27
  • Absolutely correct, choosing any of the pattern is a matter of preference or convenience most times it could be base on code style which i will prefer the spread – solomon Yunana Jan 14 '22 at 08:45
0

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>
  );
}
Erick
  • 1,098
  • 10
  • 21
0

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

Tushar Shahi
  • 16,452
  • 1
  • 18
  • 39