Yousha Mahmood
Yousha Mahmood

Reputation: 23

React Strictmode preventing a custom made useState hook to change the state at the first call

I was following along a presentation of Ryan florence (creator of remix and ReactTraining.com). There he was demistifying useState hook, by making one of his own.
I followed along the procedure, and implemented this in a component.

import React from 'react'
import ReactDOM from 'react-dom';



function MadeUseStateMyself() {
  const [minutes,setMinutes] = useState(5)
  const [error,setError] = useState(null)
  
  const handleAdd = () => {
      if(minutes<9){
          setMinutes(minutes+1)
          setError(null)
      }
      else{
          setError('Less than 10 please')
      }
  }
  const handleSubStract = () => {
    if(minutes>0){
        setMinutes(minutes-1)
        setError(null)
    }
    else{
        setError('More than 0 please')
    }
}

  return (
    <React.Fragment>
        <button onClick={handleSubStract} > - </button>
        <p>{minutes}</p>
        <button onClick={handleAdd} > + </button>
        {error && <div>{error}</div>}
    </React.Fragment>
  )
}
const states = []
let calls = -1
const useState = def => {
    const callId = ++calls
    if(states[callId]) {
        return states[callId]
    }
    const setDef = newV => {
       states[callId][0] = newV
       reRender()
    }
    const state = [def,setDef]
    states.push(state)
    return state
}
function reRender(){
    calls = -1
    ReactDOM.render(<MadeUseStateMyself/>,document.getElementById('root'))
}
export default MadeUseStateMyself

Here I noticed a really peculiar behavior, that is, whenever I use React.StrictMode wrapping my entire component, the state doesnt change after the first function call(In this case, I implemented an incremental and a decremental button which changes the state of one singular integer state.)
This problem doesnt rise if I get rid of the React.StrictMode wrapper. Here is a CodeSandbox

Reading the StrictMode docs , one of my assumptions is that as in strictmode, function component bodies are invoked twice, it loses its first state during the second function call, thus losing the first change of state.
Am I even close?

Upvotes: 2

Views: 1021

Answers (1)

Drew Reese
Drew Reese

Reputation: 202618

I had to watch about 13 minutes into the video to spot the first difference between your code and that in the demo. The presenter said to immediately manually invoke the custom reRender function to do the initial render. It's from here I noticed that you were rendering your app twice. MadeUseStateMyself is rendered once in the custom reRender function and is also exported and rendered again into the DOM in the index.js file.

You've effectively two instances of your MadeUseStateMyself component rendered as a React stomping on each other.

If you take your code and immediately invoke reRender, and also completely remove the index.js rendering code, while also wrapping MadeUseStateMyself in a React.StrictMode component, you'll see it has no issues rendering and updating.

MadeUseStateMyself

import React from "react";
import ReactDOM from "react-dom";

const states = [];
let calls = -1;

const useState = (def) => {
  const callId = ++calls;
  if (states[callId]) {
    return states[callId];
  }
  const setDef = (newV) => {
    states[callId][0] = newV;
    reRender();
  };
  const state = [def, setDef];
  states.push(state);
  return state;
};

function MadeUseStateMyself() {
  const [minutes, setMinutes] = useState(5);
  const [error, setError] = useState(null);

  const handleAdd = () => {
    if (minutes < 9) {
      setMinutes(minutes + 1);
      setError(null);
    } else {
      setError("Less than 10 please");
    }
  };

  const handleSubStract = () => {
    if (minutes > 0) {
      setMinutes(minutes - 1);
      setError(null);
    } else {
      setError("More than 0 please");
    }
  };

  return (
    <React.Fragment>
      <button onClick={handleSubStract}> - </button>
      <p>{minutes}</p>
      <button onClick={handleAdd}> + </button>
      {error && <div>{error}</div>}
    </React.Fragment>
  );
}

function reRender() {
  calls = -1;
  ReactDOM.render(
    <React.StrictMode> // <-- add the React.StrictMode
      <MadeUseStateMyself />
    </React.StrictMode>,
    document.getElementById("root")
  );
}

reRender(); // <-- Invoke immediately

Edit react-strictmode-preventing-a-custom-made-usestate-hook-to-change-the-state-at-t

Upvotes: 1

Related Questions