I want to render a loading screen while the render() function of my React app is running. This question solves the problem for the initial loading of the application, however I need to perform updates to the app state and re-render the application afterward based on the new state, which appears to be very costly. However, anything I try causes the app to freeze entirely while the render is running. Is there a way to render a dynamic loading screen while waiting for the DOM to re-render?
Here is a codesandbox showing my intentions: https://codesandbox.io/s/elated-lamarr-fhqwe?file=/src/App.js
In the example, I would like for the "LOADING" div to show (ideally this would be some sort of dynamic "loader" component) while the other divs are being rendered, and then disappear when the render is done.
You have conditional-rendering for this purpose
...
this.state = {
isLoading: true
};
...
componentDidMount() {
expensiveCalculation(...);
this.setState({ isLoading: false }); // Add additional state updates as per above results
}
...
render() {
this.state.isLoading ? <Loader /> : <Component {...props} />;
}
If the computation is expensive, you can use the libraries like memoize-one which helps you to skip the intensive calculations if there is no change in input values.
Related
Say I have my component structure in this manner:
function Parent Component(){
// radio scroll is responsible for detecting the scroll on the page true indicates it is below a certain height false means its on top
const {radioScroll} = useCustomContextHook()
function handleColorChange(){
setColor(blue)
}
return (<div style={{backgroundColor: {radioScroll ? 'blue' : 'green'}}}>
<ChildComponent />
</div>)
}
function ChildComponent(props){
const {data,loading,error} = useQuery(query,variables)
if (error) console.log(error)
if (loading) return 'loading..'
if (data){
return <ChartJSComponent data={data} />
}
}
in the above code block, say radio scroll is coming from a higher order context which tells me when i scroll down if true we are on top of page if false we are below certain number of pixels.
based on which i change the parent component styling, now this radio scroll is not used in my child component however the usequery operation is expensive and time taking since i use a webhook as proxy to authenticate hence the child component always shows a loader when scrolling down meaning the child component re evaluates whenver the radioScroll state changes, i have tried the below but no luck.
export default memo(ChildComponent)
tried to use the memo on the child component to avoid the re evaluation/rendering, but it wasnt working for me.
im using react18, i have tried to wrap the useQuery inside a useMemo, but its throwing errors that i cannot wrap a custom hook inside it.
Thanks!
I am trying to call a function when the page is refreshed. I adding a state if the page is rendered with the data I got from my backend end but I get an warning message "Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state." Even though it works fine (except with the warning message), I dont think this is the best, most efficient way to do it?
If this is the best, most efficient way, how do I fix the waring message?
function Demo() {
constructor(){
this.state = {
username: "unknown",
rendered: false,
}
this.renderUserProfile = this.renderUserProfile.bind(this);
}
update(){
//code to retrieve data from backend node.js *
this.setState({ username: data });
this.setState({ rendered: true });
}
render(){
if (!this.state.rendered) {
this.update();
}
return (<p>demo</p>)
}
}
Thank you for your help!
Do never change state inside render, because every state (or prop) change will call render again. That is what the warning is telling you: you risk having infinite loops.
There is no need of a state param for "rendered", because your component will call render anyway as soon as this.setState({username: data}); executes. If you want something to happen then, add it in update just after the setState line.
Now let's imagine that you still really want it. If you don't want your component to render when the rendered state changes, then just don't use the React Component state, but any standard class attribute:
class MyComponent extends React.Component {
rendered = false
...
render() {
this.rendered = true
....
}
}
Just be aware that this looks super wrong (and useless) since it tries to go around what the React framework is good at.
Finally, from this code there is no way to know how you intend you have new data coming in. If it is an Ajax call, then you will call this.update with that data in the callback of your Ajax call - certainly not in render.
I have a <BlogPost> component which could've been a Stateless Function Component, but turned out as a Class Stateful Component because of the following:
The blogPost items that it renders (receiving as props) have images embedded in their html marked content which I parse using the marked library and render as a blog post with images in between its paragraphs, h1, h2, h3, etc.
The fact is that I need to preload those images before rendering the post content to my client. I think it's a UX disaster if you start reading a paragraph and all of a sudden it moves down 400px because the image that was being loaded has been mounted to the DOM during the time you were reading it.
So I prefer to hold on by rendering a <Spinner/> until my images are ready. That's why the <BlogPost> is a class component with the following code:
class BlogPost extends React.Component {
constructor(props) {
super(props);
this.state={
pending: true,
imagesToLoad: 0,
imagesLoaded: 0
};
}
preloadImages(blogPostMedia) {
this.setState({
pending: true,
imagesToLoad: 0,
imagesLoaded: 0
});
... some more code ...
// Get images urls and create <img> elements to force browser download
// Set pending to false, and imagesToLoad will be = imagedLoaded
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props !== nextProps) {
this.preloadImages(nextProps.singleBlogPost.media);
}
}
componentDidMount() {
this.preloadImages(this.props.singleBlogPost.media);
}
render() {
return(
this.state.pending ?
<Spinner/>
: (this.state.imagesLoaded < this.state.imagesToLoad) ?
<Spinner/>
: <BlogPostStyledDiv dangerouslySetInnerHTML={getParsedMarkdown(this.props.singleBlogPost.content)}/>
);
}
}
export default BlogPost;
At first I was calling the preloadImages() only inside the componentDidMount() method. And that works flawlessly for the first post I render with it.
But as soon as I would click on the next post link; since my <BlogPost>component is already mounted, componentDidMount() doesn't get called again and all the subsequent posts I would render by clicking on links (this is a Single Page App) wouldn't benefit from the preloadImages() feature.
So I needed a way to reset the state and preload the images of the new blogPost received as props inside an update cycle, since the <BlogPost> component it's already mounted.
I decided to call the same preloadImages() function from inside the UNSAFE_componentWillReceiveProps() method. Basically it is reseting my state to initial conditions, so a <Spinner/> shows up right away, and the blog post only renders when all the images have been loaded.
It's working as intended, but since the name of the method contains the word "UNSAFE", I'm curious if there's a better way to do it. Even though I think I'm not doing anything "unsafe" inside of it. My component is still respectful to its props and doesn't change them in anyway. It just been reset to its initial behavior.
RECAP: What I need is a way to reset my already mounted component to its initial state and call the preloadImages() method (inside an update cycle) so it will behave as it was freshly mounted. Is there a better way or what I did is just fine? Thanks.
I would stop using componentWillReceiveProps()(resource). If you don't want the jarring effect, one way you can avoid it is to load the information from <BlogPost/>'s parent, and only once the information is loaded, to pass it into <BlogPost/> as a prop.
But anyway, you can use keys to reset a component back to its original state by recreating it from scratch (resource).
componentWillReceiveProps is deprecated, it's supposed to be replaced with either getDerivedStateFromProps or componentDidUpdate, depending on the case.
Since preloadImages is asynchronous side effect, it should be called in both componentDidMount and componentDidUpdate:
componentDidMount() {
this.preloadImages(this.props.singleBlogPost.media);
}
componentDidUpdate() {
this.preloadImages(this.props.singleBlogPost.media);
}
I am implementing a react-native newsfeed with FlatList and I would like to detect then the feed is loaded, and the first few items rendered. The idea is that the splash page would be shown until the the newsfeed is rendered (https://facebook.github.io/react-native/blog/2018/01/18/implementing-twitters-app-loading-animation-in-react-native.html), at which point the splash feed would animate to the newsfeed. The question is where I do detect the isRendered event. My FlatList has two props that may be of interest:
class NewsFeed extends Component {
state = { data: [] }
loadData = async () => { ... }
renderItem = ({ item }) => { ... }
render () {
return <FlatList renderItem = {this.renderItem} data={this.state.data} />
}
}
I tried flipping the the isRendered boolean flag as a last line in loadData, but after transitioning from the landing screen, it still takes a fraction of a second (sometimes) before the pictures render. So I flipped the isRendered boolean flag in renderItem function, and we never advance past the splash screen at all, meaning the function is never run.
You might want to check the value of isRendered in the render function. If isRendered is still false, you display the splash screen. Then if the boolean is true, you can begin the animation from this component and display the list right away as the data is already loaded. To make this work, call setState to flip the isRendered boolean in loadData as you were doing before, so the render function gets called again once the data is loaded. (And you can call the loadData function in componentDidMount as this will be called right away when the component gets mounted).
If the animation is being called from a different component, you might want to load the data there and do the isRendered check in that component's render function, and then possibly pass the data as a prop to make sure the NewsFeed component is already loaded with the data.
For a component that is hidden at some point in its lifecycle, what is the best way to render it?
1) render the component, but do not show it (display:none).
2) render the component only when needed.
What is better for performance? If the component's props and state update later, would it be better to have the component present, but hidden in the virtual DOM?
render() {
return (
<div style={{display: this.props.visible ? 'block' : 'none'}}>
<RestofComponentHere />
</div>
);
}
or this:
render() {
if (!this.props.visible) {
return null;
}
return (
<div>
<RestofComponentHere />
</div>
);
}
Just go with what works best for that situation. Sometimes it's method 1 and sometimes method 2. If you find out that it's slowing down your app then it's pretty easy to convert to method 1, but this should only if happen if you are conditionally rendering a ton of times. When you have a ref to the component then you want to always render it so that you can access the ref in componentDidMount and so you would have it hidden.
First method is faster as shown in answer here but don't do micro optimizations for the sake of it if conditionally rendering is cleaner code.
I'v used a mixture in my apps.
I would suggest to go with the state values and have a condition based on state values to decide whether the component to be shown or hidden.
constructor(props){
//Set some initial condition for display depending on prop or default value
//Something similar to this:
display: props.toDisplay === undefined ? true : props.toDisplay
}
componentDidMount(){
//update the state depending on the response or change
}
onClick(event){
//It might depend on someOther component's click action.
}
The render method would just have the following:
render(){
const toDisplay = this.state.display
if(toDisplay &&
// Component To render
)
}