why Functional component is not re-rendering even after a prop object change?

11,869

Solution 1

React doesn't know how Core works; it sees a single variable let core = new Core(); that never changes once it's created, so React never knows when to update.

It looks like you are starting to build a central store with Core, so you might want to check out useReducer to handle your growing use case:

const initialState = {
  counter: 0;
};

function reducer(state, action) {
  switch (action.type) {
    case "handleCounter":
      return {
        ...state,
        counter: state.counter + 1,
      };
    default:
      return state;
  }
}

export default function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  function handleCounter() {
    dispatch("handleCounter");
  }

  return (
    <div className="App">
      <Status counter={state.counter} handleCounter={handleCounter} />
    </div>
  );
}

Solution 2

it is working 100% for me

you can change state like this

const [state, setState] = ({})
setState({...state})

or if your state is Array you can change like this

const [state, setState] = ([])
setState([...state])

Solution 3

Components update when state changes, props change, useSelector returns a different value than last render, useContext returns a different value than last render or some other custom reducer.

Value change means a different value, so a different primitive value or a different reference. That is why you see updating state usually copy old state: {...state,value:newValue} instead of mutating: state.value=newValue.

The App component never re renders because none of the previous reasons to re render happened. That means that it never passes changed props to Status.

You can use local state for counter, context, a state manager like redux or useReducer.

Here is a simple example using local state in App

//not using React.memo because counter is the only thing
//  that changes and causes re renders
function Status({ up, counter }) {
  return (
    <div>
      <span> counter is {counter}</span>
      <button onClick={() => up()}>up</button>
    </div>
  );
}
const App = () => {
  const [counter, setCounter] = React.useState(0);
  //not using useCallback because counter is the only thing
  //  that changes and causes re renders
  const up = () => setCounter((c) => c + 1);
  return <Status up={up} counter={counter} />;
};

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>

If you want to keep logic in an object you can do the following:

function createCore() {
  let listeners = [];
  //create a state container to mutate  to to prevent
  //  stale closures for getState
  const stateContainer = { state: { count: 0 } };
  const trigger = (newState) =>
    listeners.forEach((listener) => listener(newState));
  const addListener = (fn) => {
    listeners.push(fn);
    return () =>
      (listeners = listeners.filter(
        (listener) => listener !== fn
      ));
  };
  const up = () => {
    //mutate state container so getState does
    //  not pass a stale closure
    const state = stateContainer.state;
    stateContainer.state = {
      ...state,
      count: state.count + 1,
    };
    trigger(stateContainer.state);
  };
  return {
    addListener,
    getState: () => stateContainer,
    up,
  };
}
const core = createCore();
function Status() {
  //you could put this in a custom hook called
  //  useCore that returns core and state
  const [state, setState] = React.useState(core.getState());
  React.useEffect(
    //addListener returns a function that will remove the
    //  listener when the component unmounts, this is
    //  automatically used as the cleanup function
    () => core.addListener((state) => setState(state)),
    []
  );
  //custom hook would return [core,state]
  return (
    <div>
      <span> counter is {state.count}</span>
      <button onClick={core.up}>up</button>
    </div>
  );
}
const App = () => {
  return <Status />;
};

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>
Share:
11,869
n9n
Author by

n9n

Updated on June 04, 2022

Comments

  • n9n
    n9n about 2 years

    I have an object constructor which have method to modify a value. and I created a new instance of that object in a react component and rendering that counter in a child component. when I call method from child it getting updated in object but the newly update value is not re-rendering. I tried useEffect to listen a change in props but it getting called when value update. but when I change a state in child component the updated values are showing. What is the problem?

    
    function Core() {
      this.counter = 0;
    }
    
    Core.prototype.handleCounter = function(options) {
      this.counter = this.counter + 1;
      console.log(this.counter);
    };
    
    export default function App() {
      let core = new Core();
      return (
        <div className="App">
          <Status counter={core.counter} core={core} />
        </div>
      );
    }
    
    
    

    App.js

    import React, { useEffect } from "react";
    import "./styles.css";
    
    export default function Status({ core, counter }) {
      const [localState, setLocal] = React.useState(false);
      function handleButton() {
        core.handleCounter();
        console.log(core.counter);
      }
      return (
        <div>
          <span> counter is {core.counter}</span>
          <button onClick={() => handleButton()}>update object</button>
          <button
            onClick={() => {
              setLocal(!localState);
            }}
          >
            toggle localState
          </button>
        </div>
      );
    }
    
    

    status.js

    here is a code-sandbox url :https://codesandbox.io/s/quizzical-bash-qh8mn