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>
  )
}
1
  • What if you try let url_list = [...urls, 'hi'], or at least let url_list = [...urls]? Commented Jan 14, 2022 at 3:49

3 Answers 3

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>
  )
}
Sign up to request clarification or add additional context in comments.

4 Comments

I think this is a great answer because it gives examples of JS object equality
yeah once one understands JS objec equality it can help to detect common bugs in codebase
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]));
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
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

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.