How React implements its hooks (at a high level) [duplicate] - reactjs

This question already has an answer here:
How do react hooks determine the component that they are for?
(1 answer)
Closed 3 years ago.
Say you have something like this:
const MyComponent = (props) => {
const refA = React.useRef()
const refB = React.useRef()
const refC = React.useRef()
const [x, setX] = React.useState(true)
const [y, setY] = React.useState([])
return <div>...</div>
}
In pseudocode, what is this actually doing when it is called multiple times? Are they doing this basically?
Know that MyComponent is about to be converted from the function into a virtual tree.
Set a global variable that's going to track when every useX hook is called within a single procedural frame.
Store for this component instance the outputs from each of these hook calls.
The next time the hooks are called for this component ID, it gets the map of the last returned results from the last useX calls from within this component.
I ask because (a) it seems like it can only be done with a global variable trick of some sort, and (b), the corresponding code is quite complicated and difficult to parse.
Wondering if one could just give a quick high-level overview or some pseudocode on how React implements these magical hooks.
It seems like it's something like this, though more advanced:
let dispatcher = {}
function MyComponent() {
}
function begin() {
dispatcher.component = {
refs: [],
states: []
}
}
function useRef() {
let ref = {}
dispatcher.component.refs.push(ref)
return ref
}
function useState(val) {
let state = val
dispatcher.component.states.push(val)
return state
}
function end() {
dispatcher.component = null
}
I just don't see how it can memoize and such with this magic.
This is not the same as the question as knowing how hooks know which component they are for. That is just one aspect to my question, I am asking generally how the hooks work.

The question about how hooks implemented is pretty broad to explain each hook, let's take for an example useRef and useState.
While useRef(initialValue) is just an object with a current key, possible implemention:
const myRef = useRef('myReference');
const useRef = (initialValue) => ({ current: initialValue });
Hooks are just arrays, this array referred to state - and it is not shared with other components but it is maintained in a scope that is accessible to the subsequent rendering of the specific component.
On each setter function call, we can use a cursor to define which state needs to be used.
Possible implementation of useState:
const state = [];
const setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
// This is the pseudocode for the useState helper
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
// Our component code that uses hooks
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
// This is sort of simulating Reacts rendering cycle
function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
}
console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']
// click the 'Fred' button
console.log(state); // After-click: ['Fred', 'Yardley']

Related

react function component issue with usEffect and useState

Sometimes I have to use some native js libaray api, So I may have a component like this:
function App() {
const [state, setState] = useState(0)
useEffect(() => {
const container = document.querySelector('#container')
const h1 = document.createElement('h1')
h1.innerHTML = 'h1h1h1h1h1h1'
container.append(h1)
h1.onclick = () => {
console.log(state)
}
}, [])
return (
<div>
<button onClick={() => setState(state => state + 1)}>{state}</button>
<div id="container"></div>
</div>
)
}
Above is a simple example. I should init the lib after react is mounted, and bind some event handlers. And the problem is coming here: As the above shown, if I use useEffect() without state as the item in dependencies array, the value state in handler of onclick may never change. But if I add state to dependencies array, the effect function will execute every time once state changed. Above is a easy example, but the initialization of real library may be very expensive, so that way is out of the question.
Now I find 3 ways to reslove this, but none of them satisfy me.
Create a ref to keep state, and add a effect to change it current every time once state changed. (A extra variable and effect)
Like the first, but define a variable out of the function instead of a ref. (Some as the first)
Use class component. (Too many this)
So is there some resolutions that solve problems and makes code better?
I think you've summarised the options pretty well. There's only one option i'd like to add, which is that you could split your code up into one effect that initializes, and one effect that just changes the onclick. The initialization logic can run just once, and the onclick can run every render:
const [state, setState] = useState(0)
const h1Ref = useRef();
useEffect(() => {
const container = document.querySelector('#container')
const h1 = document.createElement('h1')
h1Ref.current = h1;
// Do expensive initialization logic here
}, [])
useEffect(() => {
// If you don't want to use a ref, you could also have the second effect query the dom to find the h1
h1ref.current.onClick = () => {
console.log(state);
}
}, [state]);
Also, you can simplify your option #1 a bit. You don't need to create a useEffect to change ref.current, you can just do that in the body of the component:
const [state, setState] = useState(0);
const ref = useRef();
ref.current = state;
useEffect(() => {
const container = document.querySelector('#container');
// ...
h1.onClick = () => {
console.log(ref.current);
}
}, []);

What are production use cases for the useRef, useMemo, useCallback hooks?

Outside of the counter example seen in many YouTube tutorial videos, what are practical/real-world use cases for useMemo and useCallback?
Also, I've only seen an input focus example for the useRef hook.
Please share other use cases you've found for these hooks.
useRef:
Syntax: const refObject = useRef(initialValue);
It simply returns a plain JavaScript object. Its value can be accessed and modified (mutability) as many times as you need without worrying about "rerender".
Its value will persist (won't be reset to the initialValue unlike an ordinary* object defined in your function component; it persists because useRef gives you the same object instead of creating a new one on subsequent renders) for the component lifetime.
If you write const refObject = useRef(0) and print refObject on console, you would see the log an object - { current: 0 }.
*ordinary object vs refObject, example:
function App() {
const ordinaryObject = { current: 0 } // It will reset to {current:0} at each render
const refObject = useRef(0) // It will persist (won't reset to the initial value) for the component lifetime
return <>...</>
}
Few common uses, examples:
To access the DOM: <div ref={myRef} />
Store mutable value like instance variable (in class)
A render counter
A value to be used in setTimeout / setInterval without a stale closure issue.
useMemo:
Syntax: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
It returns a memoized value. The primary purpose of this hook is "performance optimization". Use it sparingly to optimize the performance when needed.
It accepts two arguments - "create" function (which should return a value to be memoized) and "dependency" array. It will recompute the memoized value only when one of the dependencies has changed.
Few common uses, examples:
Optimize expensive calculations (e.g. operations on data like sort, filter, changing format etc.) while rendering
Unmemoized example:
function App() {
const [data, setData] = useState([.....])
function format() {
console.log('formatting ...') // this will print at every render
const formattedData = []
data.forEach(item => {
const newItem = // ... do somthing here, formatting, sorting, filtering (by date, by text,..) etc
if (newItem) {
formattedData.push(newItem)
}
})
return formattedData
}
const formattedData = format()
return <>
{formattedData.map(item => <div key={item.id}>
{item.title}
</div>)}
</>
}
Memoized example:
function App() {
const [data, setData] = useState([.....])
function format() {
console.log('formatting ...') // this will print only when data has changed
const formattedData = []
data.forEach(item => {
const newItem = // ... do somthing here, formatting, sorting, filtering (by date, by text,..) etc
if (newItem) {
formattedData.push(newItem)
}
})
return formattedData
}
const formattedData = useMemo(format, [data])
return <>
{formattedData.map(item => <div key={item.id}>
{item.title}
</div>)}
<>
}
useCallback:
Syntax: const memoizedCallback = useCallback(() => { //.. do something with a & b }, [a, b])
It returns a memoized function (or callback).
It accepts two arguments - "function" and "dependency" array. It will return new i.e. re-created function only when one of the dependencies has changed, or else it will return the old i.e. memoized one.
Few common uses, examples:
Passing memoized functions to child components (that are optimized with React.memo or shouldComponentUpdate using shallow equal - Object.is) to avoid unnecessary rerender of child component due to functions passed as props.
Example 1, without useCallback:
const Child = React.memo(function Child({foo}) {
console.log('child rendering ...') // Child will rerender (because foo will be new) whenever MyApp rerenders
return <>Child<>
})
function MyApp() {
function foo() {
// do something
}
return <Child foo={foo}/>
}
Example 1, with useCallback:
const Child = React.memo(function Child({foo}) {
console.log('child rendering ...') // Child will NOT rerender whenever MyApp rerenders
// But will rerender only when memoizedFoo is new (and that will happen only when useCallback's dependency would change)
return <>Child<>
})
function MyApp() {
function foo() {
// do something
}
const memoizedFoo = useCallback(foo, [])
return <Child foo={memoizedFoo}/>
}
Passing memoized functions to as dependencies in other hooks.
Example 2, without useCallback, Bad (But eslint-plugin-react-hook would give you warning to correct it):
function MyApp() {
function foo() {
// do something with state or props data
}
useEffect(() => {
// do something with foo
// maybe fetch from API and then pass data to foo
foo()
}, [foo])
return <>...<>
}
Example 2, with useCallback, Good:
function MyApp() {
const memoizedFoo = useCallback(function foo() {
// do something with state or props data
}, [ /* related state / props */])
useEffect(() => {
// do something with memoizedFoo
// maybe fetch from API and then pass data to memoizedFoo
memoizedFoo()
}, [memoizedFoo])
return <>...<>
}
These hooks rules or implementations may change in the future. So, please make sure to check hooks reference in docs. Also, it is important to pay attention to eslint-plugin-react-hook warnings about dependencies. It will guide you if omit any dependency of these hooks.
I want to add, for useMemo i usually use it when i want to combine useState and useEffect at the same time. For example:
...
const [data, setData] = useState(...);
const [name, setName] = useState("Mario");
// like the example by ajeet, for complex calculations
const formattedData = useMemo(() => data.map(...), [data])
// or for simple state that you're sure you would never modify it directly
const prefixedName = useMemo(() => NAME_PREFIX + name, [name]);
I do not know if there will be performance issues because the docs stated that useMemo should be used for expensive calculation. But i believe this is way cleaner than using useState
useMemo always use for performance optimization. Be careful to add all the deps need.

Best practice for marking hooks as not to be reused in multiple places

It seems a lot of my custom React Hooks don't work well, or seem to cause a big performance overhead if they are reused in multiple places. For example:
A hook that is only called in the context provider and sets up some context state/setters for the rest of the app to use
A hook that should only be called in a root component of a Route to setup some default state for the page
A hook that checks if a resource is cached and if not, retrieves it from the backend
Is there any way to ensure that a hook is only referenced once in a stack? Eg. I would like to trigger a warning or error when I call this hook in multiple components in the same cycle.
Alternatively, is there a pattern that I should use that simply prevents it being a problem to reuse such hooks?
Example of hook that should not be reused (third example). If I would use this hook in multiple places, I would most likely end up making unnecessary API calls.
export function useFetchIfNotCached({id}) {
const {apiResources} = useContext(AppContext);
useEffect(() => {
if (!apiResources[id]) {
fetchApiResource(id); // sets result into apiResources
}
}, [apiResources]);
return apiResources[id];
}
Example of what I want to prevent (please don't point out that this is a contrived example, I know, it's just to illustrate the problem):
export function Parent({id}) {
const resource = useFetchIfNotCached({id});
return <Child id={id}>{resource.Name}</Child>
}
export function Child({id}) {
const resource = useFetchIfNotCached({id}); // <--- should not be allowed
return <div>Child: {resource.Name}</div>
}
You need to transform your custom hooks into singleton stores, and subscribe to them directly from any component.
See reusable library implementation.
const Comp1 = () => {
const something = useCounter(); // is a singleton
}
const Comp2 = () => {
const something = useCounter(); // same something, no reset
}
To ensure that a hook called only once, you only need to add a state for it.
const useCustomHook = () => {
const [isCalled, setIsCalled] = useState(false);
// Your hook logic
const [state, setState] = useState(null);
const onSetState = (value) => {
setIsCalled(true);
setState(value);
};
return { state, setState: onSetState, isCalled };
};
Edit:
If you introduce a global variable in your custom hook you will get the expected result. Thats because global variables are not tied to component's lifecycle
let isCalledOnce = false;
const useCustomHook = () => {
// Your hook logic
const [state, setState] = useState(null);
const onSetState = (value) => {
if (!isCalledOnce) {
isCalledOnce = true;
setState(false);
}
};
return { state, setState: onSetState, isCalled };
};

React set variable value from a call back from child component

I am learning React and need some help in finding out a problem with this code and understanding what am I not doing incorrectly. Parent component is MyMap and it needs values for Lat from its child component which is Search bar.
Call back function name is setLatInfo and it will set the value for the lat variable defined in useState.
The error that I am seeing is Assignments to the 'setLatInfo' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.
on the function setLatInfo = (latInfo: number)
How can I assign a call back function within an useEffect block?
import SearchBar from "../components/SearchBar";
const MyMap: FunctionComponent = () => {
const [lat, setLat] = useState();
let setLatInfo: (latInfo: number) => void;
useEffect(() => {
const loadData = () => {
// map?.map
// (map as any)?.map.addLayer(h3Layer);
};
setLatInfo = (latInfo: number) => { // this entire function is showing error
setLat(latInfo);
console.log(lat);
console.log(latInfo);
};
}, []);
return (
<div>
<SearchBar
parentCallbackLat={setLatInfo}
/>
</div>
);
};
Best to check out the usCallback syntax and how it works, as well as some examples so you can understand exactly what's happening with it. A basic example for your use case would be:
const [lat, setLat] = useState();
const setLatInfo = useCallback(latInfo => {
console.log(latInfo);
setLat(latInfo);
},[setLat]);
useEffect(() => {
setLatInfo("initial value");
}, [setLatInfo]);
return <SearchBar parentCallbackLat={setLatInfo} />;
Also, just so you know React state changes are async and you can't print their new values on the next line

How to work around expensive custom hooks?

As we know, the rule is:
Only Call Hooks at the Top Level. Don’t call Hooks inside loops, conditions, or nested functions.
So my questions is how to use and design a custom hook that is expensive?
Given this hook:
const useExpensiveHook = () => {
// some code that uses other built-in hooks...
const value = computeExpensiveValue();
// some more code....
return value;
}
If that rule did not exist my client code would be:
const myComponent = ({isSuperFeatureEnabled}) => {
let value;
if (isSuperFeatureEnabled){
value = useExpensiveHook();
}
return <div>{value}</div>;
}
The solution I came up with is to let the hook know that it should bail out, like this, using a flag:
const useExpensiveHook = ({enabled}) => {
// some code that uses other built-in hooks...
let value;
if(enabled) {
value = computeExpensiveValue();
}
else {
value = undefined;
}
// some more code....
return value;
};
and the client code:
const myComponent = ({isSuperFeatureEnabled}) => {
const value = useExpensiveHook({enabled : isSuperFeatureEnabled});
return <div>{value}</div>;
}
It is passing a flag to expensive hooks the right way to handle conditional hooks? What are the other options?
In original example it is hook initial value that is expensive, not a hook itself, computeExpensiveValue can be conditionally called:
const [value, setValue] = useState(enabled ? computeExpensiveValue() : undefined);
In currently listed example useExpensiveHook is not a hook but some function; it doesn't use React hooks.
The purpose of quoted rule is to make built-in hooks called unconditionally because the state of hooks is determined by the order in which they are called:
if (flipCoin())
var [foo] = useState('foo');
var [bar] = useState('bar');
In case useState('foo') isn't called on next component render, useState('bar') becomes the first useState hook to be called and considered foo state, while second useState is missing, this inconsistency triggers an error in a renderer.
If it were guaranteed that the order of hook calls is preserved, it would be acceptable to use conditions but this is rarely feasible in practice. Even if there's seemingly constant condition like if (process.env.NODE_ENV === 'development'), it could change under some circumstances at runtime and result in said problems that are hard to debug.
Correct:
useEffect(() => {
if (varyingCondition)
computeExpensiveValue();
});
Incorrect:
if (varyingCondition)
useEffect(() => {
computeExpensiveValue();
});
This rule applies only to built-in hooks and functions that call them directly or indirectly (so-called custom hooks). As long as computeExpensiveValue doesn't use built-in hooks internally, it can be conditionally called, as 'correct' example shows.
In case a component needs to conditionally apply third-party hook depending on prop flag, it should be guaranteed that the condition won't change with time by restricting it to be initial prop value:
const Component = ({ expensive, optionalValue }) => {
const isExpensive = useMemo(() => expensive, []);
if (isExpensive)
optionalValue = useExpensiveHook();
return ...
}
This way <Component expensive={flipCoin()} /> won't break the rule but just misuse the component.
Since it should be known if expensive hook is needed at the time when <Component expensive/> is used, a cleaner way is to compose this functionality in higher-order component and use different components depending on which one is needed:
const withExpensive = Comp => props => {
const optionalValue = useExpensiveHook();
return <Comp optionalValue={optionalValue} ...props />;
}
const Component = ({ optionalValue }) => {
return ...
}
const ExpensiveComponent = withExpensive(Component);
The argument to useState is being used only once and hence if you initially pass enabled as false to it, it will not execute the computeExpensiveValue ever. Hence you would need to add a useEffect call too. You could instead design your hook like
const useExpensiveHook = ({enabled}) => {
const [value, setValue] = useState(enabled ? computeExpensiveValue : undefined);
useEffect(()=> {
if(enabled) {
const value = computeExpensiveValue();
setValue(value);
}
}, [enabled]);
// some more code....
return value;
};

Resources