I read this article React Hook to Run Code After Render and came across this line:
React can and will sometimes call your components multiple times before actually rendering them to the screen, so you can’t rely on “one call == one render”.
What did he mean?
I wrote this code
function x() {
console.log("x");
}
function y() {
console.log("y");
return "y";
}
function Silicon() {
console.log("silicon");
return <div></div>;
}
function useDarko() {
const [count1, setCount1] = useState(0);
console.log("useDarko");
return [count1, setCount1];
}
export default function Test0022() {
const [darko, setDarko] = useDarko();
const [count1, setCount1] = useState(0);
x();
useEffect(() => {
if (count1 !== 200)
setTimeout(() => {
setCount1((e) => ++e);
}, 100);
}, [count1]);
return (
<>
<Silicon />
{y()}
</>
);
}
I always see "804 console.log". It works as expected, one call == one render.
For about four years, React has been working to implement a feature called "concurrent mode" (yes, it had been announced long before that article was released). It will be in version 18 of React, which is currently in a release-candidate state.
Concurrent mode allows React to abort a long-running render part way through in order to handle a more important update. This new approach has implications for lifecycle hooks and for the behavior of side effects, and as a result the React team has been training the community to start writing their code in a way that works with concurrent mode. For example, they deprecated several class component lifecycle hooks that would not be safe with the new approach.
And as the article mentions, one of the mental models we need to get used to is that a component may be called multiple times before it actually makes it on to the screen. Your component may get called, run all the way through, and then React realizes it needs to throw out that work and start over. These cases are rare, but to help you catch problems you can use strict mode. Among other things, it will deliberately double-render your components in development builds, to make it easier to spot bugs that only occur when these double-rendering cases occur.
Note that in React 17 this strict-mode double-render overwrites the console.log function so that it has no effect, and as a result it's difficult to see that it's happening.
Related
Imagine situation:
const [value, setValue] = useState(false);
const setSomething = (val) => {
setValue((prev) => {
fn(); dispatch(action); // or any other side effect
return prev + val;
});
};
Is it programmatically okey and fine with react principles to call side effects inside useState callback? May it affect the render process somehow?
It is not ok to use side effects inside the updater function. It might affect the render process, depending on the specific side effect.
It is not fine with react principles (separation of concerns, declarative code).
(I remember to have seen some exceptional use cases where putting some code inside the updater function was said to be the only solution, but I can't remember what it was. I'd appreciate an example in the comments.)
1. Consequences of using side effects
It is not ok to use side effects, basically for the same reasons why you shouldn't use side effects outside useEffect anywhere else.
Some side effects might affect the render process, other side effects might work fine (technically), but you are not supposed to rely on what happens inside the setter functions.
React guarantees that e.g. if you call setState( prev => prev + 1 ), then state would now be one more than before.
React does not guarantee what will happen behind the scenes to achieve that goal. React might call these setter functions multiple times, or not at all, and in any order:
StrictMode - Detecting unexpected side effects
... Because the above methods might be called more than once, it’s important that they do not contain side-effects. ...
2. following react principles
You should not put side effects inside the updater function, because it validates some principles, like separation of concerns and writing declarative code.
Separation of concerns:
setCount should do nothing but setting the count.
Writing declarative code:
Generally, you should write your code declarative, not imperative.
I.e. your code should "describe" what the state should be, instead of calling functions one after another.
I.e. you should write "B should be of value X, dependent on A" instead of "Change A, then change B"
In some cases React doesn't "know" anything about your side effects, so you need to take care about a consistent state yourself.
Sometimes you can not avoid writing some imperative code.
useEffect is there to help you with keeping the state consistent, by allowing you to e.g. relate some imperative code to some state, aka. "specifying dependencies".
If you don't use useEffect, you can still write working code, but you are just not using the tools react is providing for this purpose. You are not using React the way it is supposed to be used, and your code becomes less reliable.
Examples for problems with side effects
E.g. in this code you would expect that A and B are always identical, but it might give you unexpected results, like B being increased by 2 instead of 1 (e.g. when in DEV mode and strict mode):
export function DoSideEffect(){
const [ A, setA ] = useState(0);
const [ B, setB ] = useState(0);
return <div>
<button onClick={ () => {
setA( prevA => { // <-- setA might be called multiple times, with the same value for prevA
setB( prevB => prevB + 1 ); // <-- setB might be called multiple times, with a _different_ value for prevB
return prevA + 1;
} );
} }>set count</button>
{ A } / { B }
</div>;
}
E.g. this would not display the current value after the side effect, until the component is re-rendered for some other reason, like increasing the count:
export function DoSideEffect(){
const someValueRef = useRef(0);
const [ count, setCount ] = useState(0);
return <div>
<button onClick={ () => {
setCount( prevCount => {
someValueRef.current = someValueRef.current + 1; // <-- some side effect
return prevCount; // <-- value doesn't change, so react doesn't re-render
} );
} }>do side effect</button>
<button onClick={ () => {
setCount(prevCount => prevCount + 1 );
} }>set count</button>
<span>{ count } / {
someValueRef.current // <-- react doesn't necessarily display the current value
}</span>
</div>;
}
No, it is not ok to issue side-effects from a state updater function, it is to be considered a pure function.
The function return values are identical for identical arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams), and
The function application has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or input/output streams).
You may, or may not, be using the React.StrictMode component, but it's a method to help detect unexpected side effects.
Detecting unexpected side effects
Conceptually, React does work in two phases:
The render phase determines what changes need to be made to
e.g. the DOM. During this phase, React calls render and then
compares the result to the previous render.
The commit phase is
when React applies any changes. (In the case of React DOM, this is
when React inserts, updates, and removes DOM nodes.) React also calls
lifecycles like componentDidMount and componentDidUpdate during
this phase.
The commit phase is usually very fast, but rendering can be slow. For
this reason, the upcoming concurrent mode (which is not enabled by
default yet) breaks the rendering work into pieces, pausing and
resuming the work to avoid blocking the browser. This means that React
may invoke render phase lifecycles more than once before committing,
or it may invoke them without committing at all (because of an error
or a higher priority interruption).
Render phase lifecycles include the following class component methods:
constructor
componentWillMount (or UNSAFE_componentWillMount)
componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
componentWillUpdate (or UNSAFE_componentWillUpdate)
getDerivedStateFromProps
shouldComponentUpdate
render
setState updater functions (the first argument) <--
Because the above methods might be called more than once, it’s
important that they do not contain side-effects. Ignoring this rule
can lead to a variety of problems, including memory leaks and invalid
application state. Unfortunately, it can be difficult to detect these
problems as they can often be non-deterministic.
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState) <--
Functions passed to useState, useMemo, or useReducer
Take a cue from the two highlighted bullet points regarding the intentional double-invoking of state updater functions and treat the state updater functions as pure functions.
For the code snippet you shared, I see no reason at all for the functions to be called from within the updater callback. They could/should be called outside the callback.
Example:
const setSomething = (val) => {
setValue((prev) => {
return prev + val;
});
fn();
dispatch(action);
};
I would not
Just because it works doesn't mean it's a good idea. The code sample you shared will function, but I wouldn't do it.
Putting unrelated logic together will confuse the next person who has to work with this code; very often, that "next person" is you: you, six months from now, after you've forgotten all about this code because you finished this feature and moved on. And now you come back and discover that some of the silverware has been stored in the bathroom medicine cabinet, and some of the linens are in the dishwasher, and all the plates are in a box labeled "DVDs".
I don't know how serious you are about the specific code sample you posted, but in case it's relevant: if you're using dispatch that means you've set up some kind of reducer, either with the useReducer hook, or possibly with Redux. If that's true, you should probably consider whether this boolean belongs in your Redux store, too:
const [ value, setValue ] = useState(false)
function setSomething(val) {
fn()
dispatch({ ...action, val })
}
(But it might not, and that's fine!)
If you're using actual Redux, you'll also have action-creators, and that's generally the correct place to put code that triggers side effects.
Regardless of whatever state tech you're using, I think you should prefer to avoid putting side-effect code into your individual components. The reason is that components are generally supposed to be reusable, but if you put a side-effect into the component that is not essential to display or interaction of the thing being visualized by the component, then you've just made it harder for other callers to use this component.
If the side-effect is essential to how this component works, then a better way to handle this would be to call setValue and the side-effect function directly instead of wrapping them up together. After all, you don't actually depend on the useState callback to accomplish your side-effect.
const [ value, setValue ] = useState(false)
function setSomething(val) {
setValue(value + val)
fn()
dispatch(action)
}
For rendering smaller components/jsx within a bigger component, there are multiple approaches that one can follow. For example, consider this:
Method 1:
function BigComponent(props) {
const renderSmallComponent1 = () => <div>{props.a}</div>;
const renderSmallComponent2 = () => <div>{props.b}</div>;
return (
<div>
{renderSmallComponent1()}
{renderSmallComponent2()}
</div>
)
}
Method 2:
function BigComponent(props) {
const smallComponent1 = <div>{props.a}</div>;
const smallComponent2 = <div>{props.b}</div>;
return (
<div>
{smallComponent1}
{smallComponent2}
</div>
)
}
Method 3:
function SmallComponent1({ a }) {
return <div>{a}</div>;
}
function SmallComponent2({ b }) {
return <div>{b}</div>;
}
function BigComponent(props) {
return (
<div>
<SmallComponent1 a={props.a} />
<SmallComponent2 b={props.b} />
</div>
)
}
I am just trying to understand the difference in these 3 in terms of
dev experience,
how the framework treats them,
are there any performance optimizations,
are there differences in runtime behaviours in all of these?
Is either one better to use in certain scenarios?
These are the things that I understand:
in Method 3, all SmallComponent are React components which are rendered in another component, so they would have a component lifecycle, while in method 1 and 2, they are simple jsx, which does not have lifecycle, so they would not be mounted / unmounted as React components
in Method 2, we would be eagerly evaluating the JSX as it is directly a variable, while in method 1, it would only be evaluated when the function is called in render. So, in case, we have any conditional rendering, the eager evaluation might just be wasteful.
A few other helpful articles:
https://medium.com/missive-app/45-faster-react-functional-components-now-3509a668e69f
https://kentcdodds.com/blog/dont-call-a-react-function-component
UPDATE: it seems observation 1 is incorrect as all 3 of them would still be rendered as react components, and hence would have a component lifecycle. So react would mount/unmount them.
UPDATE 2: No, observation 1 is correct, method 1 and 2 are both treated as regular jsx as part of the BigComponent and they are not treated as react component which have a lifecycle.
UPDATE 3:
There is another method Method 4:
function BigComponent(props) {
const SmallComponent1 = () => {
return <div>{props.a}</div>;
}
const SmallComponent2 = () => {
return <div>{props.b}</div>;
}
return (
<div>
<SmallComponent1 />
<SmallComponent2 />
</div>
)
}
this is similar to Method 3, but Method 3 vs Method 4 is slightly different in execution, when debugging through dev tools.
Method 2:
function BigComponent(props) {
const smallComponent1 = <div>{props.a}</div>;
const smallComponent2 = <div>{props.b}</div>;
return (
<div>
{smallComponent1}
{smallComponent2}
</div>
)
}
If you want to a large UI into seperate smaller UI, this method will give you best performance because
It is still just one big UI component.
react just have to solve variable references.
while re-rendering, BigComponent,smallComponent1 and smallComponent2 are rendered together as single unit.
smallComponent1 and smallComponent2 cannot have their own state, life cycles and hooks.
smallComponent1 and 2 needs to be re-initialized everytime Bigcomponent state is changed. So it is good practise to wrap them with useMemo() if the result of those smallComponents are coming from an expensive computation.
Method 3:
function SmallComponent1({ a }) {
return <div>{a}</div>;
}
function SmallComponent2({ b }) {
return <div>{b}</div>;
}
function BigComponent(props) {
return (
<div>
<SmallComponent1 a={props.a} />
<SmallComponent2 b={props.b} />
</div>
)
}
React needs to resolve reference as well as execute the function after resolving the reference.
It is a composition of react's actual child components into a large Component.
Child components are allowed to have their own hooks.
Child components are not re-initialized but are re-rendered if BigComponent state is changed.
There is chance of SmallComponent1 and SmallComponent2 getting re-rendered multiple times on BigComponents rendering once if small components are updating thier own state based on props change in parents.
if each SmallComponents are supposed to use multiple props which state of BigComponents, Keeping SmallComponents outside BigComponent does offer good developer experience.
I hope Method 1 and Method 4 can also be understood using these above points.
Note: childcomponents stored in variable and childcompoents as function becomes tricker if your application logic is using ref or DOM element for maininting focus or anchor point of rendering.
Have you taken a look at the compiled JS in a React project?
JSX tags are essentially transformed in to React.createElement statements. You can read the docs here. Essentially the syntax is:
React.createElement(FunctionOrClassComponent, { props }, ...children)
In all three of your examples this would take place. In all three examples, the smaller components are functional components rather than class components. That is to say, they don't have the React lifecycle methods of a class component, but they can use equivalent React hooks - should you want to.
Edited: Evaluation (instantiation and rendering) depends on your render logic. If you have conditional rendering statements or your functions return null (or less content) based on certain conditions, then obviously you're doing less work. And as you rightly pointed out in the comments below, when you assign a JSX.Element to a variable, that is evaluated inline rather than as a result of a function - so that happens immediately.
To me, all three are valid approaches. To address your questions:
dev experience,
for small components with minimal state, functional components as variables or lambdas are convenient to write and easily read/parsed when revisiting code at a later date. When a component becomes more complex, you may have to reconsider how it's written and perhaps use Class components.
how the framework treats them,
to my knowledge the framework treats all three of your examples the same in terms of compilation. I'm unsure about rendering optimisation.
are there any performance optimizations,
your examples don't depict anything computationally onerous so performance optimization options are not so obvious
are there differences in runtime behaviours in all of these?
they are all translated to React elements, monitored for props changes, and re-rendered if parents re-render (if things like React.memo are not employed) -- there may be differences vs class-based elements, but I would guess that the runtime differences between your three examples are minimal
Is either one better to use in certain scenarios?
The differences between all three are more a matter of standards or etiquette than functional outcome. As a developer, I would be able to read and understand all three, but working in a team - I would want to see a standard approach.
I'm getting started with a new create-react-app application using TypeScript, hooks, and mobx-react-lite. Despite having used MobX extensively in a React Native app in the past, I've run into an issue that doesn't make any sense to me.
I have a store with two observables: one number and one boolean. There is an initialize() method that runs some library code, and in the success callback, it sets the number and the boolean to different values (see Line A and Line B below).
The issue: my component ONLY re-renders itself when Line A is present. In that case, after the initialization is complete, the 'ready' text appears, and the button appears. If I delete Line B, the 'ready' text still appears. But if I delete Line A (and keep Line B), the button never renders. I've checked things over a hundred times, everything is imported correctly, I have decorator support turned on. I can't imagine why observing a number can trigger a re-render but observing a boolean cannot. I'm afraid I'm missing something horribly obvious here. Any ideas?
The relevant, simplified code is as follows:
// store/app.store.ts
export class AppStore {
#observable ready = false
#observable x = 5
initialize() {
// Takes a callback
ThirdPartyService.init(() => {
this.ready = true
this.x = 10
})
}
}
// context/stores.ts
const appStore = new AppStore()
const storesContext = React.createContext({
appStore
})
export const useStores = () => React.useContext(storesContext)
// App.tsx
const App = observer(() => {
const { appStore } = useStores()
useEffect(() => {
appStore.initialize()
}, [appStore])
return (
<div>
{ appStore.x === 10 && 'ready' } // <-- Line A
{ appStore.ready && <button>Go</button> } // <-- Line B
</div>
)
}
EDIT: A bit more information. I've added some logging statements to just before the return statement for the App component. I also refactored the button conditional to a const. This may provide more insight:
const button = appStore.ready ? <button>Go</button> : null
console.log('render', appStore.ready)
console.log('button', button)
return (
<div className="App">
<header className="App-header">{button}</header>
</div>
)
When appStore.ready is updated, the component does re-render, but the DOM isn't updated. The console shows 'render' true and shows a representation of the button, as it should, but inspecting the document itself shows no button there. Somehow, though, changing the condition from appStore.ready to appStore.x === 10 does update the DOM.
Turns out I didn't quite give complete information in my question. While I was creating a minimal reproduction, I decided to try dropping the top-level <React.StrictMode> component from index.tsx. Suddenly, everything worked. As it happens, mobx-react-lite#1.5.2, the most up-to-date stable release at the time of my project's creation, does not play nice with Strict Mode. Until it's added to a stable release, the two options are:
Remove strict mode from the React component tree
Use mobx-react-lite#next
With react hooks now available should I in case of functional components wrap every function passed with props with useCallback and every other props value with useMemo?
Also having custom function inside my component dependent on any props value should I wrap it with useCallback?
What are good practices to decide which props or const values from component wrap with this hooks ?
If this improves performance why not to do it at all times ?
Lets consider custom button where we wrap click handler and add custom logic
function ExampleCustomButton({ onClick }) {
const handleClick = useCallback(
(event) => {
if (typeof onClick === 'function') {
onClick(event);
}
// do custom stuff
},
[onClick]
);
return <Button onClick={handleClick} />;
}
Lets consider custom button where we wrap click handler and add custom logic on condition
function ExampleCustomButton({ someBool }) {
const handleClick = useCallback(
(event) => {
if (someBool) {
// do custom stuff
}
},
[someBool]
);
return <Button onClick={handleClick} />;
}
Should i in this two cases wrap my handler with useCallback ?
Similar case with use memo.
function ExampleCustomButton({ someBool }) {
const memoizedSomeBool = useMemo(() => someBool, [someBool])
const handleClick = useCallback(
(event) => {
if (memoizedSomeBool) {
// do custom stuff
}
},
[memoizedSomeBool]
);
return <Button onClick={handleClick} />;
}
In this example I even pass memoized value to useCallback.
Another case what if in the component tree many components memoize same value ? How does this impact performance ?
Not worth it, for multiple reasons:
Even official docs say you should do it only when necessary.
Keep in mind that premature optimization is the root of all evil :)
It makes DX (developer experience) far worse: it's harder to read; harder to write; harder to refactor.
When dealing with primitives (like in your example) memoizing costs more CPU power than not doing it. A primitive value doesn't have a concept of references, so there's nothing to memoize in them. On the other hand, memoization itself (as any other hook) does require some tiny processing, nothing is for free. Even though it's tiny, it's still more than nothing (compared to just passing a primitive through), so you'd shoot your own foot with this approach.
To put all together - you'd waste more time typing all the hooks than a user would gain on having them in the application if you want to put them everywhere. The good old rule applies: Measure, then optimize.
I agree with the principles proposed by #jalooc
To give some more insight about the exhibited use cases in the OP, here is my advice:
Expensive children renders
function Component() {
const callback = useCallback(() => { dostuff }, [deps])
return <Child prop={callback} />
}
The above makes sense if Child is a very expensive component to render. As such, it is probably exported like so:
function Child() {
...this takes significant CPU...
}
// Export as a pure component
export default React.memo(Child)
Expensive computations
function Component({ foo }) {
// This very expensive computation will only run when it's input (foo)
// changes, allowing Component to re-render without performance issues
const bar = useMemo(() => {
... something very complicated with `foo` ...
}, [foo])
return <div>{bar}</div>
}
Conclusion
Do what makes sense or that have measured bad performance
A function declaration inside a component changes at each render. If this causes derived expensive computations, memoize it (useCallback) or move it outside the scope.
If a component itself is expensive to render, make it pure with React.memo, with the help of #2 if necessary
If something IS itself expensive to re-compute, memoize it (useMemo)
Do what makes sense or that have measured bad performance
I'd argue that there's nothing wrong with putting useCallback everywhere you create a function. The same points are applicable to useMemo; but I'll be mentioning only useCallback since, for the sake of brevity.
As a concession, I must mention that this isn't a cornerstone in your development process, and you must proceed with a solution that your team is comfortable with, whether it's using useCallback or not.
My main argument for using such memoization hooks "extensively" is that you don't have to think of potential performance issues on the front of "reference update-induced" rerendering if you do it. Fewer things to think about = good. More time and energy to solve the actual problems.
Many say "premature optimization is bad". Well, cargo cult isn't good either, and this citation is a pure example of such, taken too far out of context, and with a premise of having an authority behind it. here used to be a good summary on that, unfortunately, the account is taken down currently, but it's available on archive.org.
The concerns of "premature optimization" in this case are structural changes to the code and impaired readability/write-ability.
Structurally, these changes are for the better, making you separate your components.
Readability-wise, there's an extra wrapper, but that comes together with strict tracking of your function dependencies, which otherwise is done implicitly. Dependencies become explicit as they should be since in this system they play a too important role to swipe it under the rug. Readability, therefore, only wins. Although it's harder to write now, the "ritual" is always the same. Readability is more important than the convenience of writing a function.
(Just make sure you use eslint to track your dependencies; it could be a headache when you forget to add a dependency and it gets cached.)
So, "write now, optimize later" - thanks, I'd pass. To me, it's a negligibly cheap optimization that is justified enough and is reasonable enough, if your team is ready to accept the arguments above. It's also fine if they aren't: the topic itself isn't something to die for. It's just a small quality-of-life tool.
I've recently learned that passing down object literals or functions as props can cause unnecessary re-renders. As I am doing an audit on my app, I am finding some cases where I have common components that have callbacks on events that do different things. It's unclear to me what the most elegant way to handle this would be.
So for example in my app I have a component called SharedBtn that is used all over the app multiple places and in large loops. This button has an onClick listener. But in every instance this onClick is used we are passing down a different function to do a different thing every time.
Example:
https://codesandbox.io/s/k31120vnyo
I read this related article with examples. But their solution is to move the onClick logic to the shared component. This would be ugly for me as it is used in many different spots with many different handlers. How could I have the multiple click handlers without moving the click handling logic to the SharedBtn component itself?
You could create a small wrapper component for each special instance.
class IndexSharedButton extends React.PureComponent {
handleClick = e => {
this.props.onClick(e, this.props.index);
};
render() {
return <SharedBtn copy={this.props.copy} onClick={this.handleClick} />;
}
}
class AnimalSharedButton extends React.PureComponent {
handleClick = e => {
this.props.onClick(this.props.animal, this.props.type);
};
render() {
return (
<SharedBtn copy={this.props.animal} onClick={this.handleClick} />
);
}
}
You could also manually manage caching the bound handlers, but IMO that's pretty ugly and much less idiomatic.
Also, I think it goes without saying that you shouldn't worry about this much at all if you haven't measured it to be an actual issue. Re-renders don't happen that often, and with shouldComponentUpdate should happen even less. Optimizations like this add more code for little benefit.
Most of the time the performance hit is negligible. But the correct way to mitigate this in the age of hooks is via useCallback.
import { useCallback } from "react"
const MyComp = () => {
const func = useCallback(() => doSomething(), []);
return <OtherComp func={func}/>
};