score:61
first of all, if you are looking for a conceptual explanation of how hooks work and how they know what component instance they are tied to then see the following:
- in depth article i found after writing this answer
- hooks faq
- related stackoverflow question
- related blog post by dan abramov
the purpose of this question (if i understand the intent of the question correctly) is to get deeper into the actual implementation details of how react knows which component instance to re-render when state changes via a setter returned by the usestate
hook. because this is going to delve into react implementation details, it is certain to gradually become less accurate as the react implementation evolves over time. when quoting portions of the react code, i will remove lines that i feel obfuscate the most relevant aspects for answering this question.
the first step in understanding how this works is to find the relevant code within react. i will focus on three main points:
- the code that executes the rendering logic for a component instance (i.e. for a function component, the code that executes the component's function)
- the
usestate
code - the code triggered by calling the setter returned by
usestate
part 1 how does react know the component instance that called usestate
?
one way to find the react code that executes the rendering logic is to throw an error from the render function. the following modification of the question's codesandbox provides an easy way to trigger that error:
this provides us with the following stack trace:
uncaught error: error in child render
at child (index.js? [sm]:24)
at renderwithhooks (react-dom.development.js:15108)
at updatefunctioncomponent (react-dom.development.js:16925)
at beginwork$1 (react-dom.development.js:18498)
at htmlunknownelement.callcallback (react-dom.development.js:347)
at object.invokeguardedcallbackdev (react-dom.development.js:397)
at invokeguardedcallback (react-dom.development.js:454)
at beginwork$$1 (react-dom.development.js:23217)
at performunitofwork (react-dom.development.js:22208)
at workloopsync (react-dom.development.js:22185)
at renderroot (react-dom.development.js:21878)
at runrootcallback (react-dom.development.js:21554)
at eval (react-dom.development.js:11353)
at unstable_runwithpriority (scheduler.development.js:643)
at runwithpriority$2 (react-dom.development.js:11305)
at flushsynccallbackqueueimpl (react-dom.development.js:11349)
at flushsynccallbackqueue (react-dom.development.js:11338)
at discreteupdates$1 (react-dom.development.js:21677)
at discreteupdates (react-dom.development.js:2359)
at dispatchdiscreteevent (react-dom.development.js:5979)
so first i will focus on renderwithhooks
. this resides within reactfiberhooks. if you want to explore more of the path to this point, the key points higher in the stack trace are the beginwork and updatefunctioncomponent functions which are both in reactfiberbeginwork.js.
here is the most relevant code:
currentlyrenderingfiber = workinprogress;
nextcurrenthook = current !== null ? current.memoizedstate : null;
reactcurrentdispatcher.current =
nextcurrenthook === null
? hooksdispatcheronmount
: hooksdispatcheronupdate;
let children = component(props, reforcontext);
currentlyrenderingfiber = null;
currentlyrenderingfiber
represents the component instance being rendered. this is how react knows which component instance a usestate
call is related to. no matter how deeply into custom hooks you call usestate
, it will still occur within your component's rendering (happening in this line: let children = component(props, reforcontext);
), so react will still know that it is tied to the currentlyrenderingfiber
set prior to the rendering.
after setting currentlyrenderingfiber
, it also sets the current dispatcher. notice that the dispatcher is different for the initial mount of a component (hooksdispatcheronmount
) vs. a re-render of the component (hooksdispatcheronupdate
). we'll come back to this aspect in part 2.
part 2 what happens in usestate
?
in reacthooks we can find the following:
export function usestate<s>(initialstate: (() => s) | s) {
const dispatcher = resolvedispatcher();
return dispatcher.usestate(initialstate);
}
this will get us to the usestate
function in reactfiberhooks. this is mapped differently for initial mount of a component vs. an update (i.e. re-render).
const hooksdispatcheronmount: dispatcher = {
usereducer: mountreducer,
usestate: mountstate,
};
const hooksdispatcheronupdate: dispatcher = {
usereducer: updatereducer,
usestate: updatestate,
};
function mountstate<s>(
initialstate: (() => s) | s,
): [s, dispatch<basicstateaction<s>>] {
const hook = mountworkinprogresshook();
if (typeof initialstate === 'function') {
initialstate = initialstate();
}
hook.memoizedstate = hook.basestate = initialstate;
const queue = (hook.queue = {
last: null,
dispatch: null,
lastrenderedreducer: basicstatereducer,
lastrenderedstate: (initialstate: any),
});
const dispatch: dispatch<
basicstateaction<s>,
> = (queue.dispatch = (dispatchaction.bind(
null,
// flow doesn't know this is non-null, but we do.
((currentlyrenderingfiber: any): fiber),
queue,
): any));
return [hook.memoizedstate, dispatch];
}
function updatestate<s>(
initialstate: (() => s) | s,
): [s, dispatch<basicstateaction<s>>] {
return updatereducer(basicstatereducer, (initialstate: any));
}
the important part to notice in the mountstate
code above is the dispatch
variable. that variable is the setter for your state and gets returned from mountstate
at the end: return [hook.memoizedstate, dispatch];
. dispatch
is just the dispatchaction
function (also in reactfiberhooks.js) with some arguments bound to it including currentlyrenderingfiber
and queue
. we'll look at how these come into play in part 3, but notice that queue.dispatch
points at this same dispatch
function.
usestate
delegates to updatereducer
(also in reactfiberhooks) for the update (re-render) case. i'm intentionally leaving out many of the details of updatereducer
below except to see how it handles returning the same setter as the initial call.
function updatereducer<s, i, a>(
reducer: (s, a) => s,
initialarg: i,
init?: i => s,
): [s, dispatch<a>] {
const hook = updateworkinprogresshook();
const queue = hook.queue;
const dispatch: dispatch<a> = (queue.dispatch: any);
return [hook.memoizedstate, dispatch];
}
you can see above that queue.dispatch
is used to return the same setter on re-render.
part 3 what happens when you call the setter returned by usestate
?
here is the signature for dispatchaction:
function dispatchaction<a>(fiber: fiber, queue: updatequeue<a>, action: a)
your new state value will be the action
. the fiber
and work queue
will be passed automatically due to the bind
call in mountstate
. the fiber
(the same object saved earlier as currentlyrenderingfiber
which represents the component instance) will point at the same component instance that called usestate
allowing react to queue up the re-rendering of that specific component when you give it a new state value.
some additional resources for understanding the react fiber reconciler and what fibers are:
Source: stackoverflow.com
Related Query
- How do react hooks determine the component that they are for?
- React query error boundaries: how to give users different options depending on whether they are seeing the component for the second time
- React Hooks (Rendering Arrays) - Parent component holding a reference of children that are mapped vs Parent component holding the state of children
- How to structure a component using React Hooks with an object that uses the DOM directly? (such as OpenLayers)?)
- How to refresh a nested Component view that shows the result for a search being done in React
- How to redirect to a url along with a component in react such that props passed to the component are not lost
- How to create a div with arrow and divider for content such that they are aligned using react and css?
- How to test for the string '...loading' in React component that uses styled-components?
- How to listen for click events that are outside of a component
- How do I wrap a React component that returns multiple table rows and avoid the "<tr> cannot appear as a child of <div>" error?
- Figuring out how to mock the window size changing for a react component test
- React - How to detect when all sub-components of a parent component are visible to the user?
- How to TEST async calls made in componentDidMount that set the state of React Component
- If the props for a child component are unchanged, does React still re-render it?
- How can I write a unit test for a react component that calls reduxjs's mapStateToProps?
- bind(): You are binding a component method to the component. React does this for you automatically?
- how do I interpolate a Link component in Next-i18next / React i18next that changes position in the text
- How do I test component methods on a React component that are defined as arrow functions (class properties)?
- How do I manage state on a React component that can have state changed from the parent or from events upon it?
- How does React Developer Tools determine that the webpage is using React?
- React : How to stop re-rendering component or making an API calls on router change for the same route
- How does React implement hooks so that they rely on call order
- How to conditionally render a React component that uses hooks
- How is an argument for a callback in React being passed from the child component to the parent component?
- How is it that my React component understands the 'children' property?
- How to determine which component is for a React child?
- How to display and handle dynamic checkoxes that are dependent on Task array value in the backend (Mongodb) in react js?
- How to update the correct state value in a parent component that gets its value from a child component using hooks in react?
- How do I test a React component that calls a function on its parent, which changes props on the component?
- How to delay the redirect of a React Router Link component for 1 second?
More Query from same tag
- How Can I convert React.createclass to Class Component?
- Where to store API key in ReactJS?
- How to test a function in var in javascript
- How to find list of words in array of string using regex
- Looping through enum in react typescript render gives error
- React list rendering doesn't work - expected an assignment or function call and instead saw an expression
- react router v6 won't switch to Home Page after Login Page: Warning: Can't perform a React state update on an unmounted component
- Avoiding React Race condition with AbortController not working
- React JSX: How to set props to placeholder attribute
- How can I use forwardRef in React Component?
- How can I use history.push( ) and call a function at the same time?
- Problem while deploying `create-react-app` on github using `gh-pages`
- Is it safe to check object reference in componentWillReceiveProps?
- Is there a way to have a React child component displayed as a string, with indentations, returns, and with jsx syntactic sugar?
- Issue iterating through array and making an api get request with each iteration: getting status 429 - to many request
- How to delete/set "null" nested property of state array in ReactJs Redux?
- React: Nothing was returned from render even though I am returning
- How do I pass Page title to the menu (like breadcrumbs)
- onclick prepare html when ajax response arrivers and render in react?
- React.js: How to delete row from an ag grid?
- I am trying to push data into array defined in useState in react, but the data is not being pushed in to array
- How to create refs in react constructor function in es7?
- What is the proper way to avoid rerenders when selecting an array in React Redux?
- Catch Data from URL params in react class Compoent
- Remove ObjectId from mongodb document serialized to JSON in C#
- React states and mobx - antipattern?
- Material UI "with styles" with babel, styles broken
- Deploying React webpage to Github
- How can I correctly render elements from an array in Reactjs?
- Concatenate a String and <a href=""></a>