In the following, multiple calls of useState() simulated by an array for each state variable.
In each state updater method call, render will be called by React.
Thus, we force render by calling an original state updater method(i.e. setValue) after calling our simulated state updater.
Function Component (SimpleForm) will be called for rendering so React will reset the context(not state) for component internally before invoking this method.
Thus, we simulate this with a resetContext method.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
let myState = {};
let counter = 0;
function resetContext() {
counter = 0;
}
function myUseState(initialValue) {
console.log("counter: ", counter, " - myState:", myState);
const notAlreadyDefined = myState[counter] === undefined;
if (notAlreadyDefined) {
myState[counter] = initialValue;
}
let cnt = counter;
const pair = [
myState[cnt],
(val) => {
console.log("setter", val, cnt);
myState[cnt] = val;
// In each updater method, render() will be called by React.
// So, we force render by calling an original state updater method(i.e. setValue) after calling our simulated state updater.
}
];
counter++;
return pair;
}
function SimpleForm(props) {
const [value, setValue] = useState("John");
const [value2, setValue2] = useState("Edward");
// Function Component (SimpleForm) will be called to render so React will reset the context(not state) for component internally before invoking this method.
// So, we simulate this with a resetContext method.
resetContext();
const [firstName, setFirstName] = myUseState("John");
const [lastName, setLastName] = myUseState("Edward");
const [age, setAge] = useState(30);
console.log("called", new Date(), firstName, lastName);
return (
<form>
<label>
First Name:
<input
type="text"
value={firstName}
onChange={(event) => {
setValue(event.target.value);
setFirstName(event.target.value);
}}
/>
</label>
<br />
<label>
Last Name:
<input
type="text"
value={lastName}
onChange={(event) => {
setValue2(event.target.value);
setLastName(event.target.value);
}}
/>
</label>
<br />
<label>
Age:
<input
type="number"
value={age}
onChange={(event) => setAge(event.target.value)}
/>
</label>
<br />
<input type="submit" value="Submit" />
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<>
<SimpleForm firstName="JOHN" lastName="Edward" age={30} />
<br />
</>,
rootElement
);
https://codesandbox.io/s/react-usestate-hook-example-forked-unjk1m?file=/src/index.js
Also, check the explanation from React Docs
Under the Hood How does React associate Hook calls with components?
React keeps track of the currently rendering component. Thanks to the
Rules of Hooks, we know that Hooks are only called from React
components (or custom Hooks — which are also only called from React
components).
There is an internal list of “memory cells” associated with each
component. They’re just JavaScript objects where we can put some data.
When you call a Hook like useState(), it reads the current cell (or
initializes it during the first render), and then moves the pointer to
the next one. This is how multiple useState() calls each get
independent local state.
https://reactjs.org/docs/hooks-faq.html#under-the-hood