Ways to deal with React page flickering on re-renders (functional components) - reactjs

A lot of things are causing my React functional components to flicker as new data comes in from various fetches and everything is constantly redrawn top-to-bottom.
For example, if I have a top-level App component which somewhere down the line includes a MyForm component, and the App does an initial fetch of DB Lookups which I depend on in the MyForm (passed as a prop, lookups={lookups}), the MyForm will flicker as it redraws itself on receiving the new prop values.
App
useEffect(() => {
fetchLookups();
}, []);
const fetchLookups = async () => {
const url = '/api/general/lookups';
try {
const responseLookups = await axios.get(url);
setLookups(responseLookups.data);
}
}
MyForm - will flicker - note also I have to check for the Non-NULL Fetch result from App, which is a pain
<Form.Control
value={props.lookups &&
props.lookups.filter(item => item.id === 30)
.map((item, index) => {
return (
item.description
);
})
/>
Also, in the MyForm component, there are lots of use inits/listeners with useEffect(.., []) and they will also cause the form to re-render. I have no way of knowing what the order will be on all those inits/listeners.
So my questions are, what's the best way to deal with this flickering?
Can I detect when a functional component is fully rendered, to show a loading spinner covering the full initialization sequence?
Can I block re-renders in any way?

Related

useCallback is still leading to re-renders of children when parent's states change

Simplifying my app's components, I have a NoteList component with a particular child called FileTree. There are various states on NoteList. What I'm trying to do is to stop FileTree from re-rendering any time a state that only impacts NoteList changes. I'm getting tripped up on some functions that FileTree uses.
NoteList looks roughly like this:
const [folderName, setFolderName] = React.useState("")
...
const handleCreateFolder = React.useCallback(async (e) => {
e.preventDefault();
...
await axios.post(...call to create a folder...)
}, [])
const folderNameChangeHandler = React.useCallback((folN) => {
setFolderName(folN)
}, [])
return (
...
<FileTree prop1={prop1Value} prop2='prop2HardCoded' handleCreateFolder={handleCreateFolder} setFolderName={folderNameChangeHandler} />
)
Using the profiler, I still see that FileTree is re-rendering with every keystroke into a text input that uses setFolderName (or really, it should be using folderNameChangeHandler if I understand correctly) to change folderName.
What am I doing wrong with useCallback?
Using useCallback is not going to prevent re-renders when using setFolderName because setFolderName is going to cause a re-render every time it is called.
Check out React.memo to control when <FileTree /> re-renders.
You can use it to access previous and next props to determine if <FileTree /> should re-render.
function FileTree() {...}
export default React.memo(FileTree, (prevProps, nextProps) => {
return true // this will never re-render
// or do your custom logic to determine when it should re-render
})

How to get browser information for react components in nextjs?

I am writing a react component in nextjs that needs some information about the browser environment before it can be rendered. I know I can get this information from the user agent string after page load with a useEffect like so...
const MyComponent = () => {
const [browser, setBrowser] = useState(null);
useEffect(() => {
const ua = utils.getBrowserFromUA()
setBrowser(ua)
});
if (!browser) {
return <div>Loading...</div>
}
return (
<div>
<SpecialComponent browser={browser} />
</div>
);
};
Is it possible to get these values before the react component renders, before the pageLoad event perhaps? I really only need to calculate these values once and potentially share the values with other components. I can't find anything in the docs apart from running scripts with the beforeInteractive flag but I'm not sure how I would get the data to my component.

Why does React re-render differently whether the state is an object vs a string?

The below React example, userProfile state is an object:
1st render: state is {}
2nd render: state is res.data
-> infinite re-rendering
If userProfile is changed to a string e.g. useState('') with setUserProfile(res.data.bio)
1st render: state is ''
2nd render: state is res.data.bio
3rd render: state is res.data.bio
-> stops re-rendering after the 3rd
Two questions:
Why does React re-render differently depending on whether userProfile state is an object vs a string?
When userProfile is a string, why does React stop re-rendering after the 3rd render instead of after the 2nd one?
App.js
import UserProfile from './UserProfile';
const App = () => {
const login = 'facebook';
return (
<Router>
<Link to={`/user/${login}`}>Fetch Facebook bio from GitHub</Link>
<Route path='/user/:login' component={UserProfile} />
</Router>
);
};
UserProfile.js
const UserProfile = ({ match }) => {
const [userProfile, setUserProfile] = useState({});
const getUser = async username => {
const url = `https://api.github.com/users/${username}`;
const res = await axios.get(url);
setUserProfile(res.data);
};
getUser(match.params.login);
// intentionally not using useEffect(() => { getUser(match.params.login); }, []);
return (<p>{userProfile.bio}</p>);
};
React checks if a state variable has changed, and if so, it rerenders. Since no matter how many times you compare two objects (even if they look identical), they will always be different, React will keep rerendering.
A string, on the other hand, is a primitive type and thus compares differently. Try this:
console.log({d: 3} === {d: 3})
console.log("sss" === "sss")
And it will give you an idea as to why this happens.
So even though you keep setting the state var to the same object, it's not really the same. But a string IS the same so it stops rerendering.
Check out this article on Object equality in JS: http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html
Now let me address your second question:
When userProfile is a string, why does React stop re-rendering after the 3rd render instead of after the 2nd one?
If you look at the React Docs, you will run into these 2 paragraphs, which, I believe answer your question:
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects.
(React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React
won’t unnecessarily go “deeper” into the tree. If you’re doing
expensive calculations while rendering, you can optimize them with
useMemo
Note the second paragraph.

How to reuse a DOM node across React re-renders?

I'm working on integrating a third-party video recording library into my React application. This third-party package integrates directly into a DOM <video> element and has it's own state in such a way that state updates in my component must not cause a re-render of the <video> tag, as that will break the integration. I tried a naive approach like this:
{this.videoNode.current || <video ref={this.videoNode} playsInline className="video-js vjs-default-skin"></video>}
But it only causes React to complain with this error when a re-render was triggered:
Error: Objects are not valid as a React child (found: [object HTMLVideoElement]). If you meant to render a collection of children, use an array instead.
Is there a clean and simple way to do this without having to instantiate that <video> node in the raw HTML outside React?
React reuses the existing dom elements where it can, so as long as you avoid some things that might force a remount you shouldn't need to do any trickery and just render a <video> tag normally.
Things that can cause problems:
1) React assumes that components with different types are a new subtree. So if you did something like this, then the change from div to span would cause the video element to remount:
const Example = () => {
const [withDiv, setWithDiv] = useState(true);
useEffect(() => {
setTimeout(() => setWithDiv(false), 5000);
}, []);
if (withDiv) {
return (
<div><video/></div>
);
} else {
return (
<span><video/></div>
);
}
}
2) key is a special property that can be used to inform react that two components are or are not the same component from one render to another. If the key changes, the component will remount, even if they might otherwise look the same. So the following case will cause the video player to remount:
const Example = () => {
const [key, setKey] = useState(1);
useEffect(() => {
setTimeout(() => setKey(2), 5000);
}, []);
return (
<div key={key}><video/></div>
)
}
But for most other cases, this shouldn't be a problem. The first time you render, a <video> element will be added to the dom, and then on subsequent renders that element will be reused.
For more information, see react's documentation on reconciliation

How to use React Hooks Context with multiple values for Providers

What is the best way to share some global values and functions in react?
Now i have one ContextProvider with all of them inside:
<AllContext.Provider
value={{
setProfile, // second function that changes profile object using useState to false or updated value
profileReload, // function that triggers fetch profile object from server
deviceTheme, // object
setDeviceTheme, // second function that changes theme object using useState to false or updated value
clickEvent, // click event
usePopup, // second function of useState that trigers some popup
popup, // Just pass this to usePopup component
windowSize, // manyUpdates on resize (like 30 a sec, but maybe can debounce)
windowScroll // manyUpdates on resize (like 30 a sec, but maybe can debounce)
}}
>
But like sad in docs:
Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider’s parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for value:
This is bad:
<Provider value={{something: 'something'}}>
This is ok:
this.state = {
value: {something: 'something'},
};
<Provider value={this.state.value}>
I imagine that in future i will have maybe up to 30 context providers and it's not very friendly :/
So how can i pass this global values and functions to components? I can just
Create separate contextProvider for everything.
Group something that used together like profile and it's functions,
theme and it's functions (what about reference identity than?)
Maybe group only functions because thay dont change itself? what
about reference identity than?)
Other simpliest way?
Examples of what i use in Provider:
// Resize
const [windowSize, windowSizeSet] = useState({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight
})
// profileReload
const profileReload = async () => {
let profileData = await fetch('/profile')
profileData = await profileData.json()
if (profileData.error)
return usePopup({ type: 'error', message: profileData.error })
if (localStorage.getItem('deviceTheme')) {
setDeviceTheme(JSON.parse(localStorage.getItem('deviceTheme')))
} else if (profileData.theme) {
setDeviceTheme(JSON.parse(JSON.stringify(profileData.theme)))
} else {
setDeviceTheme(settings.defaultTheme)
}
setProfile(profileData)
}
// Click event for menu close if clicked outside somewhere and other
const [clickEvent, setClickEvent] = useState(false)
const handleClick = event => {
setClickEvent(event)
}
// Or in some component user can change theme just like that
setDeviceTheme({color: red})
The main consideration (from a performance standpoint) for what to group together is less about which ones are used together and more about which ones change together. For things that are mostly set into context once (or at least very infrequently), you can probably keep them all together without any issue. But if there are some things mixed in that change much more frequently, it may be worth separating them out.
For instance, I would expect deviceTheme to be fairly static for a given user and probably used by a large number of components. I would guess that popup might be managing something about whether you currently have a popup window open, so it probably changes with every action related to opening/closing popups. If popup and deviceTheme are bundled in the same context, then every time popup changes it will cause all the components dependent on deviceTheme to also re-render. So I would probably have a separate PopupContext. windowSize and windowScroll would likely have similar issues. What exact approach to use gets deeper into opinion-land, but you could have an AppContext for the infrequently changing pieces and then more specific contexts for things that change more often.
The following CodeSandbox provides a demonstration of the interaction between useState and useContext with context divided a few different ways and some buttons to update the state that is held in context.
You can go to this URL to view the result in a full browser window. I encourage you to first get a handle for how the result works and then look at the code and experiment with it if there are other scenarios you want to understand.
This answer already does a good job at explaining how the context can be structured to be more efficient. But the final goal is to make context consumers be updated only when needed. It depends on specific case whether it's preferable to have single or multiple contexts.
At this point the problem is common for most global state React implementations, e.g. Redux. And a common solution is to make consumer components update only when needed with React.PureComponent, React.memo or shouldComponentUpdate hook:
const SomeComponent = memo(({ theme }) => <div>{theme}</div>);
...
<AllContext>
{({ deviceTheme }) => <SomeComponent theme={deviceTheme}/>
</AllContext>
SomeComponent will be re-rendered only on deviceTheme updates, even if the context or parent component is updated. This may or may not be desirable.
The answer by Ryan is fantastic and you should consider that while designing how to structure the context provider hierarchy.
I've come up with a solution which you can use to update multiple values in provider with having many useStates
Example :
const TestingContext = createContext()
const TestingComponent = () => {
const {data, setData} = useContext(TestingContext)
const {value1} = data
return (
<div>
{value1} is here
<button onClick={() => setData('value1', 'newline value')}>
Change value 1
</button>
</div>
)
}
const App = () => {
const values = {
value1: 'testing1',
value2: 'testing1',
value3: 'testing1',
value4: 'testing1',
value5: 'testing1',
}
const [data, setData] = useState(values)
const changeValues = (property, value) => {
setData({
...data,
[property]: value
})
}
return (
<TestingContext.Provider value={{data, setData: changeValues}}>
<TestingComponent/>
{/* more components here which want to have access to these values and want to change them*/}
</TestingContext.Provider>
)
}
You can still combine them! If you are concerned about performance, you can create the object earlier. I don't know if the values you use change, if they do not it is quite easy:
state = {
allContextValue: {
setProfile,
profileReload,
deviceTheme,
setDeviceTheme,
clickEvent,
usePopup,
popup,
windowSize
}
}
render() {
return <AllContext.Provider value={this.state.allContextValue}>...</AllContext>;
}
Whenever you then want to update any of the values you need to do I like this, though:
this.setState({
allContextValue: {
...this.state.allContextValue,
usePopup: true,
},
});
This will be both performant, and relatively easy as well :)
Splitting those up might speed up a little bit, but I would only do that as soon as you find it is actually slow, and only for parts of your context that would have a lot of consumers.
Still, if your value does not change a lot, there is really nothing to worry about.
Based on Koushik's answer I made my own typescipt version.
import React from "react"
type TestingContextType = {
value1?: string,
value2?: string,
value3?: string,
value4?: string,
value5?: string,
}
const contextDefaultValues = {
data: {
value1: 'testing1',
value2: 'testing1',
value3: 'testing1',
value4: 'testing1',
value5: 'testing1'
} as TestingContextType,
setData: (state: TestingContextType) => {}
};
const TestingContext = React.createContext(contextDefaultValues);
const TestingComponent = () => {
const {data, setData} = React.useContext(TestingContext);
const {value1} = data
return (
<div>
{value1} is here
<button onClick={() => setData({ value1 : 'newline value' })}>
Change value 1
</button>
</div>
)
}
const App = () => {
const [data, setData] = React.useState(contextDefaultValues.data)
const changeValues = (value : TestingContextType) => setData(data && value);
return (
<TestingContext.Provider value={{data, setData: changeValues}}>
<TestingComponent/>
{/* more components here which want to have access to these values and want to change them*/}
</TestingContext.Provider>
)
}

Resources