I have a .tsx file that renders two component:
export default observer(function MyModule(props: MyModuleProps) {
....
return (
<div>
<TopPart></TopPart>
<LowerPart></LowerPart>
</div>
);
});
The problem I have is that the TopPart contains lots of sub components, so it takes much longer time to render, and the LowerPart is more important and I want to render it first, but in this code, the LowerPart won't be available until the TopPart has been rendered.
What I want to do is to first render the LowerPart, then the TopPart, without changing the layout. I am wondering how I can achieve this goal properly.
Disclaimer: this is a hack.
If the problem is server side, this is easy for react. Just throw up a placeholder while data is loading, then save it in state when loading finishes and render.
The following answer assumes this is a rendering performance problem. If it is, then you look at that rendering performance. Paginate your lists, simplify your CSS rules, profile react and see what is taking the time.
What follows may be interesting, but is probably a bad idea. React is declarative, meaning you tell the result you want and then let it crunch things to deliver that. As soon as you start telling it what order to do things in, you break that paradigm and things may get painful for you.
If you want to break up rendering you could use state to prevent the expensive component from rendering, and then use an effect to update that state after the first render, which then renders both components.
You could make a custom hook like this:
function useDeferredRender(): boolean {
const [doRender, setDoRender] = useState(false);
useEffect(() => {
if (!doRender) {
setTimeout(() => setDoRender(true), 100);
}
}, [doRender]);
return doRender;
}
This hook create the doRender state, initialized to false. Then it has an effect which sets the state to true after a brief timeout. This means that doRender will be false on the first render, and then the hook will immediately set doRender to true, which triggers a new render.
The timeout period is tricky. Too small and React may decide to batch the render, too much and you waste time. (Did I mention this was a hack?)
You would this like so:
function App() {
const renderTop = useDeferredRender();
return (
<div className="App">
{renderTop ? <TopPart /> : "..."}
<LowerPart />
</div>
);
}
Working example
One last time: this is probably a bad idea.
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)
}
I'm setting the data that my flatlist component displays using a state called selectedStream. selectedStream changes every time the user presses a different group option. I've noticed that the flatlist takes 1-3 seconds to refresh all the posts that it's currently displaying already. I want there to be a loading indicator so that by the time the indicator goes away, the list is already properly displayed with the newly updated data.
<FlatList
maxToRenderPerBatch={5}
bounces={false}
windowSize={5}
ref={feedRef}
data={selectedStream}/>
Whenever we are working with anything related to the UI, sometimes we may face delays in UI re-rendering. However, we need to first figure out what is actually causing the delay.
The right question to ask about your code would be:
Is the rendering of items taking longer than expected? Or, is the data being passed with a delay because it is dependant on an API call or any other async task?
Once you answer that question, you may end up with two scenarios:
1. FlatList taking longer to render views
This doesn't usually happen as the RN FlatList will only render views that are visible to the user at any given time and will keep rendering new views as the user scrolls through the list. However, there may be some flickering issues for which you can refer to the below article:
8 Ways to optimise your RN FlatList
2. Passing the data causes the delay
This is the most common scenario, where we may call an API endpoint and get some data and then do setState to update any view/list accordingly. A general approach is to show some sort of a progress-bar that would indicate that the application is busy and thus maintaining a proper user-experience. The easiest way to do that is by conditional rendering.
A general example would be:
const [myList, setMyList] = useState();
function callAPIforMyList(){
// logic goes here
}
return {
{myList ? <ActivityIndicator .../> : <Flatlist .... />
}
The above code will check if myList is undefined or has a value. If undefined, it will render the ActivityIndicator or else the FlatList.
Another scenario could be when myList may have existing data but you need to update/replace it with new data. This way the above check may fail, so we can put another check:
const [myList, setMyList] = useState();
const [isAPIbusy, setAPIBusy] = useState(false)
function callAPIformyList() {
setAPIBusy(true)
/// other logics or async calls or redux-dispatch
setAPIBusy(false)
}
return {
{!isAPIBusy && myList ? (<Flatlist .... />) : (<ActivityIndicator .../>)
}
You can add multiple conditions using more turneries such as isAPIBusy ? <View1> : otherBoolean ? <View2> : <Default_View_When_No_Conditions_Match)/>
Hope this helps clarify your needs.
TL;DR React sometimes renders a loading state and sometimes not, without changes in the UI. This is probably due to batched updates.
I would like to know if the problem below is due to batched updates. If the answer is "yes", I would like to know if there's preferred way to opt-out of batched updates in React to get deterministic render behavior. Go down to "Experiment" if you want to skip the setup.
Setup
Here's the setup, a chart that takes a long time to render. So long that the render is blocking. There are three different ways to render the chart here:
one is the normal way
one with a "mounted" render hack
one with the same "mounted" render hack, but with an additional setTimeout
Option 2 & 3 both have a small useState to check whether they've been mounted. I do this to show a "Loading" state conditionally:
function ChartWithMountHack({ data }: { data: Data }) {
// initially not mounted
const [isMounted, setIsMounted] = useState<boolean>(false);
useEffect(() => {
// "Now I've been mounted!"
setIsMounted(true);
}, []);
return !isMounted ? <p>Loading</p> : <Chart data={data} />;
}
I did this, because I want to show a "Loading" state instead of a blocking render, so e.g. page switches or ternary rendering (e.g. hasData ? <p>No data</p> : <Chart />) are shown immediately, instead of blocking. (If there are better ways, please let me know!)
Experiment
Now, each button will render one of the three options/charts. Again, the second and third chart have a small hack to check whether they're mounted or not.
Try clicking on the first button and the second button back & forth quickly.
You will see that sometimes the "Chart with mount hack" will ("correctly") render the "Loading" state, but sometimes it just doesn't render the "Loading" - instead it blocks the render up until the chart is finished rendering (skips the "Loading" state).
I think this is due to the render cycles and whether you get the two updates in one cycle of the batching. (first: isMounted === false -> second: isMounted === true)
I can't really tell how to reproduce this, hence the "nondeterministic" in the title. Sometimes you also have to click on "Regenerate data" and click back & forth after that.
Cross-check
Option 3 ("Chart with mount hack with timeout") ALWAYS gives me the "Loading" state, which is exactly what I want. The only difference to option 2 is using a setTimeout in the useEffect where isMounted is set to true. setTimeout is used here to break out of the update batching.
Is there a better way to opt-out of the batching, so isMounted will always render with its initial value (false)? Using setTimeout here feels like a hack.
React has concurrent features to handle these sort of things, for example React Suspense tags or you make use of Subscription libraries like Rxjs, which its subscription should be done in the componentDidMount and componentWillUnmount to unsubscribe the data.
Then the isMounted is just a work around for a pending issue, probably from the library you're using or sometimes just your bundler/build tool acting out a bit.
lastly to avoid unnecessary re-render, you can use React memoization of component using React.Memo.
Kindly read more on these.
I am trying to learn React by building a web application. Since I want to learn it step by step, for now I don't use Redux, I use only the React state and I have an issue.
This is my components architecture:
App.js
|
_________|_________
| |
Main.js Side.js
| |
Game.js Moves.js
As you can see, I have the main file called App.js, in the left side we have the Main.js which is the central part of the application which contains Game.js where actually my game is happening. On the right side we have Side.js which is the sidebar where I want to display the moves each player does in the game. They will be displayed in Moves.js.
To be more clear think at the chess game. In the left part you actually play the game and in the right part your moves will be listed.
Now I will show you my code and explain what the problem is.
// App.js
const App = React.memo(props => {
let [moveList, setMovesList] = useState([]);
return (
<React.Fragment>
<div className="col-8">
<Main setMovesList={setMovesList} />
</div>
<div className="col-4">
<Side moveList={moveList} />
</div>
</React.Fragment>
);
});
// Main.js
const Main = React.memo(props => {
return (
<React.Fragment>
<Game setMovesList={props.setMovesList} />
</React.Fragment>
);
});
// Game.js
const Game= React.memo(props => {
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
return () => {
document.getElementById('board').removeEventListener('click', executeMove, false);
};
})
return (
// render the game board
);
});
// Side.js
const Side= React.memo(props => {
return (
<React.Fragment>
<Moves moveList={props.moveList} />
</React.Fragment>
);
});
// Moves.js
const Moves= React.memo(props => {
let [listItems, setListItems] = useState([]);
useEffect(() => {
let items = [];
for (let i = 0; i < props.moveList.length; i++) {
items.push(<div key={i+1}><div>{i+1}</div><div>{props.moveList[i]}</div></div>)
}
setListItems(items);
return () => {
console.log('why this is being triggered on each move?')
};
}, [props.moveList]);
return (
<React.Fragment>
{listItems}
</React.Fragment>
);
});
As you can see on my code, I have defined the state in App.js. On the left side I pass the function which updates the state based on the moves the player does. On the right side I pass the state in order to update the view.
My problem is that on each click event inside Game.js the component Moves.js unmounts and that console.log is being triggered and I wasn't expected it to behave like that. I was expecting that it will unmount only when I change a view to another.
Any idea why this is happening ? Feel free to ask me anything if what I wrote does not make sense.
Thanks for explaining your question so well - it was really easy to understand.
Now, the thing is, your component isn't actually unmounting. You've passed props.movesList as a dependency for the usEffect. Now the first time your useEffect is triggered, it will set up the return statement. The next time the useEffect gets triggered due to a change in props.movesList, the return statement will get executed.
If you intend to execute something on unmount of a component - shift it to another useEffect with an empty dependency array.
answering your question
The answer to your question
"why this is being triggered on each move"
would be:
"because useEffect wants to update the component with the changed state"
But I would be inclined to say:
"you should not ask this question, you should not care"
understanding useEffect
You should understand useEffect as something that makes sure the state is up to date, not as a kind of lifecycle hook.
Imagine for a moment that useEffect gets called all the time, over and over again, just to make sure everything is up to date. This is not true, but this mental model might help to understand.
You don't care if and when useEffect gets called, you only care about if the state is correct.
The function returned from useEffect should clean up its own stuff (e.g. the eventlisteners), again, making sure everything is clean and up to date, but it is not a onUnmount handler.
understanding React hooks
You should get used to the idea that every functional component and every hook is called over and over again. React decides if it might not be necessary.
If you really have performance problems, you might use e.g. React.memo and useCallback, but even then, do not rely on that anything is not called anymore.
React might call your function anyway, if it thinks it is necessary. Use React.memo only as kind of a hint to react to do some optimization here.
more React tips
work on state
display the state
E.g. do not create a list of <div>, as you did, instead, create a list of e.g. objects, and render that list inside the view. You might even create an own MovesView component, only displaying the list. That might be a bit too much separation in your example, but you should get used to the idea, also I assume your real component will be much bigger at the end.
Don’t be afraid to split components into smaller components.
It seems the problem is occurred by Game element.
It triggers addEventListener on every render.
Why not use onClick event handler
/* remove this part
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
})
*/
const executeMove = (e) => {
props.setMovesList(e.target);
}
return (
<div id="board" onClick={executeMove}>
...
</div>
)
If you want to use addEventListener, it should be added when the component mounted. Pass empty array([]) to useEffect as second parameter.
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
}, [])
I have component that uses both useState() and other custom hooks multiple times.
I want to act based on these state values. I could do that directly in the function component's body:
const MyComponent = () => {
const [someState, setSomeState] = useState(false);
const [otherState, setOtherState] = useState(false);
const customHookValue = useCustomHook();
if (someState) foo();
const foo = () => setOtherState(!otherState);
if (customHookValue > 10) bar();
const bar = () => setSomeState(somestate > customHookValue);
}
However, every time someState changes (and a re-render happens) the second conditional also runs, causing bar() to run if the conditional passes. This feels unnatural. Logically, bar() should only run when customHookValue changes, as someState only changes if customHookValue has changed since last render.
In summary, a re-render caused by a change to someState should not cause a bunch of unrelated state setting functions to run again. Even though re-running them causes no change in the outcome of the program, it is not logically right. They only need to re-run when their corresponding conditional changes. It could effect performance.
This must be a common challenge in React. I am quite new to it, so I do not know what the approach to solve this would be.
Questions
How would I solve the above in the recommended manner?
Would I have to wrap every conditional in a useEffect or a useMemo?
EDIT:
Updated the second conditional, to make my question clearer (it should depend on customHook).
CLARIFICATION:
As it might not have been clear, my issue is as follows. When state changes, a re-render occurs. This causes all functions in the component's body to re-run. Now, if we have many useState in a component, and only one changes, a bunch of potentially unrelated state-changing and potentially expensive functions I have defined in the components body will run. These state-changing functions would only have to run if the state values they are trying to set has changed. If the values they are setting has not changed, it is unnecessary for them to run again. But, the re-render reruns all functions in the component's body regardless.
It looks like (as others have suggested) you want useEffect:
useEffect(() => {
if (someState) {
setOtherState(!otherState)
}
}, [someState, otherState])
useEffect(() => {
if (customHookValue > 10) {
setSomeState(someState > customHookValue)
}
}, [customHookValue])
Since you only want the setSomeState to run if customHookValue changes, make it the only item in the dependencies array passed to useEffect.
The exhaustive-deps eslint-plugin-react-hooks will complain about the second useEffect, since the function depends on the value of someState, even though someState will only potentially change if customHookValue changes. I also wouldn't worry about things potentially affecting performance until they do. I don't know a ton about the internals of React, but it does some things under the hood to avoid re-renders it doesn't need to do, and can do multiple renders before an actual update is painted.