It is common in React to lift-up state and passing that state from parent to child, optionally together with a callback function allowing the child to signal to the parent that the state should change.
I can't seem to find in the official React docs where the child component is allowed to call that callback function.
In the following pseudo-code, I've marked 5 different locations where the child could call the callback. I'm trying to find some (official) docs or references on which of those locations are allowed.
function Parent({}){
const [isActive, setIsActive] = useState(false);
return (
<>
<Foo/>
<Child active={isActive} onChange={setIsActive}/>
</>
);
}
function Child({active,onChange}){
//Location 1: in the render code
onChange(old => !old);
useEffect(() => {
//Location 2: in a useEffect
onChange(old => !old);
//Location 3: on complete of some async operation in the useEffect
asyncOperationLikeFetch().then( () => onChange(old => !old));
}, [])
return (
<p>{active}</p>
<button onClick={() => {
//Location 4: in an event handler
onChange(old => !old)
//Location 5: on complete of some async operation in the event handler
asyncOperationLikeFetch().then( () => onChange(old => !old));
}}>Toggle</>
)
}
I found official documentation for:
Also, you can only update the state of the currently rendering component like this. Calling the set function of another component during rendering is an error
I also found some none official docs for (but I would appreciate a pointer to the official docs):
I found nothing so far for:
Anybody can tell me in which locations I can (not) call that callback received from the parent?
You appear to have a correct understanding.
Case (3) is the same as case (2), enqueueing a state update in useEffect
hook callback, and similarly case (5) is the same as case (4) (and cases (2) and (3) really!) enqueueing a state update in a callback function.
Basically you just want to avoid unintentional side-effects like case (1) where the function is unconditionally called while the function component body is called by React. React components are to be considered Pure Functions.
Cases (2), (3), (4), and (5) are all considered intentional side-effects, either by being called in the useEffect
hook callback, or in some asynchronously called DOM event handler, like a button click handler.
You could do a bit of a deep dive into the legacy React docs to gain insight and an appreciation of the React Component Lifecycle. There you will find documentation on the older Class-based components' lifecycles and some details on when React state updates can be applied.
Take for example:
componentDidMount
(quasi-synonymous to useEffect
being called on the initial render cycle)
componentDidMount()
componentDidMount()
componentDidMount()
is invoked immediately after a component is mounted (inserted into the tree). Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.This method is a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount()
.You may call setState() immediately in
componentDidMount()
. It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though therender()
will be called twice in this case, the user won’t see the intermediate state. Use this pattern with caution because it often causes performance issues. In most cases, you should be able to assign the initial state in theconstructor()
instead. It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position.
componentWillUmount
(quasi-synonymous to useEffect hook callback cleanup function)
componentWillUnmount()
componentWillUnmount()
componentWillUnmount()
is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created incomponentDidMount()
.You should not call setState() in
componentWillUnmount()
because the component will never be re-rendered. Once a component instance is unmounted, it will never be mounted again.
Note also that you could always enqueue state updates from other DOM event callbacks.
A diagram I found helpful when learning React was the lifecycle diagram:
React function component bodies are effectively the render
"method", and is/can be called by React any number of times during the "render phase" where the code/logic is to be "pure and have no side-effects". Here you can't enqueue state updates as these are side-effects. The useEffect
hook is synonymous to "componentDidMount", "componentDidUpdate", and "componentWillUnmount" and all the DOM event handlers are called during or after the view has been rendered and committed to the DOM, e.g. the "commit phase", and here you can issue side-effects and schedule updates to the React state.