Anupam Singh
Anupam Singh

Reputation: 25

React functional component not re-rendering with state change

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

Answers (3)

solomon Yunana
solomon Yunana

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

Tushar Shahi
Tushar Shahi

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

Erick
Erick

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

Related Questions