ReactJS - render called, but DOM not updated - javascript

This has happened to me a few times. I have always managed to work around the issue, yet I am still intrigued to understand why this happens, and what I have missed.
Essentially if I have a condition within my render method which specifies the class for my div:
let divClass = this.state.renderCondition ? 'red' : 'blue';
By default I set renderCondition within my state to false.
If I then define an onClick handler on a button (as follows), and click the button, whilst render IS called, the DOM is NOT updated. That is to say the class does not change.
onClickCompile: function() {
this.setState({renderCondition: true}, function() {
synchronousSlowFunction();
});
}
This seems to have something to do with running slow synchronous code in that if the code is quick and simple the DOM IS updated appropriately.
If I wrap the call to synchronousSlowFunction in a 500 millisecond timeout, everything works as expected. I would however like to understand what I have misunderstood such that I do not need this hack.

Can you share the button onClick code? I might be wrong but this looks like an incorrectly set button onClick listener.
Make sure that onClick callback is defined without (), i.e.
<button onClick={this.something} />
instead of:
<button onClick={this.something()} />
Post more code so we can get a better (bigger) picture

synchronousSlowFunction is as you mentioned synchronous. This means, that it blocks your component while it is running. That means, that react cannot update your component, because it has to wait for the callback function to complete until it can call render with the updated values. The setTimeout wrap makes the call asynchronous, so that react can render/update your component, while the function is doing its work. It is not the time delay, that makes it work but simply the callback, which is not render blocking. You could also wrap in in a Promise or make the slow function async to prevent render blocking.

Try something Like this:
this.setState((state) => ({
...state, renderCondition: true
}));
Maybe you're doing another setState for renderCondition some where the code above should fix such things.
or maybe try using PureComponent
import React, { PureComponent } from 'react'
export default class TablePreferencesModal extends PureComponent {
render() {
return (
<div>
</div>
)
}
}

After set state use this line
this.setState({})

Related

Debounce on a onclick function using typescript react

I am trying to implement lodash debounce function on an onclick function to avoid multiple clicks on a button. The solution I came up with is as follows:
function saveForm() {
//do stuff here
}
<Button onClick={debounce(() => saveForm, 1500, {
maxWait: 2000 })}>
SAVE
</Button>
I have seen many examples where debounce is performed on a function outside the return and then just use that debounced function under onclick. Is performing debounce directly (inline) on the button element wrong practice ?
As far as good coding practice is concerned, you should avoid putting too much of business logic inside JSX. Just extract your onClick handler outside JSX.
Secondly, You do not want to return saveForm after debounce. Rather call it. So replace () => saveForm with saveForm.
function saveForm() {
//do stuff here
}
const debouncedClickHandler = debounce(saveForm, 1500, {maxWait: 2000})
<Button onClick={debouncedClickHandler}>SAVE</Button>
You can also use useCallback hook. I leave it up to you to explore the best practices for using useCallback hook if you need to.
If your component is re-rendered frequently, I think it could be replaced to useCallback, but in normal case, I think your usecase looks okay.
Instead of this you can you can add a loading state and based on that loading state disable the button.
Actually we will validate within the form and save form outside the component. That gives clear valid input data and blindly save.
Yea, #abdemirza right - better solution from UX perspective of view - it's to add loading state and disable button, when you async function do something.
It's not a mistake to use a debounce, but better use loading state

Change document.body, modify React state and change body again

I want to change CSS class of body when user clicks a button, and reverse that when operations defined in handleClick are finished.
I have following handleClick function:
handleClick = data => {
document.body.classList.add('waiting');
this.setState({data}, () => document.body.classList.remove('waiting'));
}
I would expect that to work in following way:
Add waiting class to body
Change state of component
Remove waiting class from body
But it doesn't work that way. In order for that to work I have to change handleClick function to something like this:
handleClick = data => {
document.body.classList.add('waiting');
setTimeout(() => this.setState({data}, () => document.body.classList.remove('waiting')))
}
I would like to know why this works.
I know that setTimeout is 'delaying' execution of this.setState but as far as I know this.setState itself is asynchronous so shouldn't it be delayed on its own? Does that mean that changing document.body.classList is also asynchronous and its execution is delayed more than execution of this.setState?
But it doesn't work that way.
It does, but that doesn't mean you see anything change in the browser window. :-) What you're doing is exactly what you described: Setting a class before the state change, doing the state change, and removing it after the state change.
const {useState} = React;
class Example extends React.Component {
state = {
counter: 0,
};
handleClick = () => {
console.log("Adding class");
document.body.classList.add('waiting');
// NOTE: Normally this should use the callback form of a state setter,
// rather than assuming that `this.state.counter` is up-to-date. But
// I wanted to keep using the same type of call as you were
console.log("Setting state");
this.setState({counter: this.state.counter + 1}, () => {
console.log("Removing class");
document.body.classList.remove('waiting');
});
};
componentDidUpdate() {
console.log("componentDidUpdate");
}
render() {
console.log(`render, counter = ${this.state.counter}`);
return <div>
<div>{this.state.counter}</div>
<input type="button" value="Click Me" onClick={this.handleClick} />
</div>;
}
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
But the after-state-change callback may occur before the DOM is updated, and separately may occur before the page display is updated to account for the change in the body classes.
...but as far as I know this.setState itself is asynchronous so shouldn't it be delayed on its own...
The state update is asynchronous, yes, but may well be done very proactively (it could even be a microtask that runs immediately after the current task completes, which is before the browser updates the page display for the class change — or not, it's an implementation detail of React).
Does that mean that changing document.body.classList is also asynchronous and its execution is delayed more than execution of this.setState?
No, the change is synchronous, but you don't see the change until the next time the browser paints the display, which won't be until the current task and all of its queued microtasks is complete (or even after that; browsers sync to the refresh rate of your display device, so typically 60 times/second [once every roughly 16.67ms] though it can be more with higher-end monitors and display adapters).
Your setTimeout(/*...*/, 0) trick is one common way to handle this, although the browser may not have updated the display yet when that timer fires (because again, typically it's about 16.67ms between updates). You can hook into the browser's page display update cycle by using requestAnimationFrame, which gives you a callback just before the browser is going to update the display. But it's probably overkill vs. (say) a delay of 20ms.
An alternative way to handle this would be to add the class immediately and use componentDidUpdate to schedule removing it on the next animation frame. But again, that may be overcomplicated.

setTimeout function does not display HTML after function ends

I have created a timeout function for 3 seconds and after that time it is supposed to render the HTML. I'm following the official documentation to do this and the code is like on the documentation. Anyone has had a similar issue?
render() {
return (
<div>
{this.state.activePage === 'Home' ? setTimeout(function () {
<p>Hello World</p>
}, 3000) : null}
</div>
)
}
You should not write code like that in react. The setTimeout returns:
The returned timeoutID is a positive integer value which identifies
the timer created by the call to setTimeout(). T
and not the JSX which you returned from callback. But even if it did asynchronously return the JSX which you return from callback, you won't see the new content on the screen. To change UI in react you must change a state variable using setState which will cause a re render. Also render is not the place to call setState. You can call setState in useEffect for example.
It is not possible to return a value from the function passed to setTimeout.
Here are two options you could try
Create a state field to store the message you want to display and update the state from inside of the setTimeout. Also such an update cannot be done inside of the return. You would want to move this logic to a component mount function.
Here is a more vanilla JS approach to this issue using promise objects
Get return value from setTimeout
Also could you provide the document you are referring to for this code, so we can have a deeper dive onto the issue?

ReactJS - inline arrow functions performance

I have read that using an arrow function inline re-creates the function on every component re-render causing performance issues.
In my code I have this situation
render() {
return (
<View
onLayout={(event) => {
this.itemHeights[index] = event.nativeEvent.layout.height;
}}
>
...
)
}
I have thought to do something like this to get a better performance:
getItemHeight = (index, {nativeEvent: {layout: {height}}}) => this.itemHeights[index] = event.nativeEvent.layout.height;
render() {
return (
<View
onLayout={(event) => getItemHeight(index, event)}
>
...
)
}
But I am not really sure if this is better... at I have to invoke a function in a inline function. I did it because I need the parameter index, so I can't do:
<View
onLayout={getItemHeight}
>
I know that this may not be too expensive, but it is very expensive for my use case, as I have to re-render items on the fly.
Any ideas?
Declaration vs Execution
First of all I think you are confusing two things here.
The declaration of a function is something different than the execution of a function.
Also the component does not re render all the time, just because a function is redeclared inside the render functions return.
There is also a vast difference between the performance impact between the declaration of a function and its execution.
Referentially stable function vs inline functions
So what you are doing in your first example is redeclaring a function for the View component. This basically resets the value for the onLayout prop, because inline functions are not referentially stable.
~~In the second example (I figure this is a class component), you are using a referentially stable function, so the prop value of onLayout stays the same.~~
UPDATE: Actually this is not true, my bad. You are still using a instable function. But you could just bring that up as a class member like so:
public getItemHeight(index, {nativeEvent: {layout: {height}}}) {
this.itemHeights[index] = event.nativeEvent.layout.height;
}
// ...
// ... inside the render function:
return {
<View onLayout={this.getItemHeight}></View>
}
But I am not really sure if this is better... at I have to invoke a function in a inline function. I did it because I need the parameter index, so I can't do:
Actually this statement isn't true. You can also retrieve the index with a defined function, as I showed above.
Using this code you are just giving the prop a reference to the function that you want to be invoked by the callback. This will populate the arguments just fine.
When do components re render?
Components re render when there is a prop or state change.
So in your first example the View component will re render only if the parent component has to be re rendered. So the parent renders and therefore evaluates the it's sub components. This does trigger the render function of the View component and swaps out the function for onLayout.
In your second example (considering my suggested code change above) the View component would not trigger it's render function since the prop value of onLayout did not change.
But that does not mean, that your first example necessarily results in a DOM change and repaint (this is a different algorithm inside React). And the DOM change would be the really expensive part.
Rerender vs Repaint
Rerender is a process that React runs internally. React just triggers the render functions of the component that have to be rerendered. But the repaint inside the actually page during runtime only happens if the resulting DOM is different than before.
You can read more about this in the React Docs: https://reactjs.org/docs/reconciliation.html
So when would you have to use referentially stable functions?
I don't think that you should prematurely optimize such things. It can make the code more bloated and introduce complexity that isn't always necessary.
There is also a whole part on this here: https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation
If it does not drain your performance, why bother micro optimizing things?
Just because you read that inline function are recreated every time (btw. this also applies to non arrow functions. It's the inline declaration that makes it recreate every time) and therefore the render function is triggered every time, doesn't always mean that React has to repaint or that it has a big performance impact.
So never prematurely fiddle around on small performance gains, when there isn't even a problem.
An React based app is a huge beast of code. There will be plenty big picture problems to fix, once you run into performance issues.

ReactJs: change state in response to state change

I've got a React component with an input, and an optional "advanced input":
[ basic ]
Hide Advanced...
[ advanced ]
The advanced on the bottom goes away if you click "Hide Advanced", which changes to "Show Advanced". That's straightforward and working fine, there's a showAdvanced key in the state that controls the text and whether the advanced input is rendered.
External JS code, however, might change the value of advanced, in which case I want to show the [advanced] input if it's currently hidden and the value is different than the default. The user should be able to click "Hide Advanced" to close it again, however.
So, someone external calls cmp.setState({advanced: "20"}), and I want to then show advanced; The most straightforward thing to do would just be to update showAdvanced in my state. However, there doesn't seem to be a way to update some state in response to other state changes in React. I can think of a number of workarounds with slightly different behavior, but I really want to have this specific behavior.
Should I move showAdvanced to props, would that make sense? Can you change props in response to state changes? Thanks.
Okay first up, you mention that a third party outside of your component might call cmp.setState()? This is a huge react no-no. A component should only ever call it's own setState function - nothing outside should access it.
Also another thing to remember is that if you're trying change state again in response to a state change - that means you're doing something wrong.
When you build things in this way it makes your problem much harder than it needs to be. The reason being that if you accept that nothing external can set the state of your component - then basically the only option you have is to allow external things to update your component's props - and then react to them inside your component. This simplifies the problem.
So for example you should look at having whatever external things that used to be calling cmp.setState() instead call React.renderComponent on your component again, giving a new prop or prop value, such as showAdvanced set to true. Your component can then react to this in componentWillReceiveProps and set it's state accordingly. Here's an example bit of code:
var MyComponent = React.createClass({
getInitialState: function() {
return {
showAdvanced: this.props.showAdvanced || false
}
},
componentWillReceiveProps: function(nextProps) {
if (typeof nextProps.showAdvanced === 'boolean') {
this.setState({
showAdvanced: nextProps.showAdvanced
})
}
},
toggleAdvancedClickHandler: function(e) {
this.setState({
showAdvanced: !this.state.showAdvanced
})
},
render: function() {
return (
<div>
<div>Basic stuff</div>
<div>
<button onClick={this.toggleAdvancedClickHandler}>
{(this.state.showAdvanced ? 'Hide' : 'Show') + ' Advanced'}
</button>
</div>
<div style={{display: this.state.showAdvanced ? 'block' : 'none'}}>
Advanced Stuff
</div>
</div>
);
}
});
So the first time you call React.renderComponent(MyComponent({}), elem) the component will mount and the advanced div will be hidden. If you click on the button inside the component, it will toggle and show. If you need to force the component to show the advanced div from outside the component simply call render again like so: React.renderComponent(MyComponent({showAdvanced: true}), elem) and it will show it, regardless of internal state. Likewise if you wanted to hide it from outside, simply call it with showAdvanced: false.
Added bonus to the above code example is that calling setState inside of componentWillReceiveProps does not cause another render cycle, as it catches and changes the state BEFORE render is called. Have a look at the docs here for more info: http://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
Don't forget that calling renderComponent again on an already mounted component doesn't mount it again, it just tells react to update the component's props and react will then make the changes, run the lifecycle and render functions of the component and do it's dom diffing magic.
Revised answer in comment below.
My initial wrong answer:
The lifecycle function componentWillUpdate will be ran when new state or props are received. You can find documentation on it here: http://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate
If, when the external setState is called, you then set showAdvanced to true in componentWillUpdate, you should get the desired result.
EDIT: Another option would be to have the external call to setState include showAdvanced: true in its new state.

Categories

Resources