Do variables also get updated on rerender triggered by state change? - reactjs

I have a state change which causes rerender of my functional component. I have noticed the variables (let, var) sometime get updated and sometime do not, which are displayed on my DOM. Are updated value of variables picked up on rerender triggered by state. Can i use state for rerender trigger and then just variables for changes as it removes overhead of keeping everything in state? Or variables maintain their initial value at the time of mount?
For example doing this on Onclick event -
let display;
const [state, setState] = useState();
...
OnClick () =>{
setState(newvalue);
display = newdisplay;
}
...
// In DOM
return (
<div>
{state}
{display}
</div>
)

The variables are already updated before state change but rerendering the page just shows your changes, so if it's not updating it probably means you are setting state before you change the variable.
Also, don't set state within the return of your render function, that may also cause problems when it comes to DOM updates.
Example of what you should do:
class MyComponent extends React.Component {
state = {
myStateVariable: "I'm in state"
};
myVariable = "Something cool"; // This variable is accessible from anywhere in your component using 'this'
updateVars = () => {
this.myVariable = "Something else thats cool";
this.setState({ myStateVariable: "I was now changed" });
}
render() {
let myLocalVariable = "Locally grown"; // This variable is only accessible within your render function
console.log(myLocalVariable);
myLocalVariable = "I was locally grown"; // Changing the variable
return (
<>
<p>{this.state.myStateVariable} is within state</p>
<p>{this.myVariable} will not update its self</p>
<p>{myLocalVariable} will always stay the same</p>
<button onClick={this.updateVars}>Click Me</button>
</>
);
}
}
Thank you for posting your question, I hope this clears things up for you.

Related

React: why is that changing the current value of ref from useRef doesn't trigger the useEffect here

I have a question about useRef: if I added ref.current into the dependency list of useEffect, and when I changed the value of ref.current, the callback inside of useEffect won't get triggered.
for example:
export default function App() {
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<div className="App">
<button
onClick={() => {
myRef.current = myRef.current + 1;
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
</div>
);
}
Shouldn't it be when useRef.current changes, the stuff in useEffect gets run?
Also I know I can use useState here. This is not what I am asking. And also I know that ref stay referentially the same during re-renders so it doesn't change. But I am not doing something like
const myRef = useRef(1);
useEffect(() => {
//...
}, [myRef]);
I am putting the current value in the dep list so that should be changing.
I know I am a little late, but since you don't seem to have accepted any of the other answers I'd figure I'd give it a shot too, maybe this is the one that helps you.
Shouldn't it be when useRef.current changes, the stuff in useEffect gets run?
Short answer, no.
The only things that cause a re-render in React are the following:
A state change within the component (via the useState or useReducer hooks)
A prop change
A parent render (due to 1. 2. or 3.) if the component is not memoized or otherwise referentially the same (see this question and answer for more info on this rabbit hole)
Let's see what happens in the code example you shared:
export default function App() {
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<div className="App">
<button
onClick={() => {
myRef.current = myRef.current + 1;
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
</div>
);
}
Initial render
myRef gets set to {current: 1}
The effect callback function gets registered
React elements get rendered
React flushes to the DOM (this is the part where you see the result on the screen)
The effect callback function gets executed, "myRef current changed" gets printed in the console
And that's it. None of the above 3 conditions is satisfied, so no more rerenders.
But what happens when you click the button? You run an effect. This effect changes the current value of the ref object, but does not trigger a change that would cause a rerender (any of either 1. 2. or 3.). You can think of refs as part of an "effect". They do not abide by the lifecycle of React components and they do not affect it either.
If the component was to rerender now (say, due to its parent rerendering), the following would happen:
Normal render
myRef gets set to {current: 1} - Set up of refs only happens on initial render, so the line const myRef = useRef(1); has no further effect.
The effect callback function gets registered
React elements get rendered
React flushes to the DOM if necessary
The previous effect's cleanup function gets executed (here there is none)
The effect callback function gets executed, "myRef current changed" gets printed in the console. If you had a console.log(myRef.current) inside the effect callback, you would now see that the printed value would be 2 (or however many times you have pressed the button between the initial render and this render)
All in all, the only way to trigger a re-render due to a ref change (with the ref being either a value or even a ref to a DOM element) is to use a ref callback (as suggested in this answer) and inside that callback store the ref value to a state provided by useState.
https://reactjs.org/docs/hooks-reference.html#useref
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
use useCallBack instead, here is the explanation from React docs:
We didn’t choose useRef in this example because an object ref doesn’t
notify us about changes to the current ref value. Using a callback ref
ensures that even if a child component displays the measured node
later (e.g. in response to a click), we still get notified about it in
the parent component and can update the measurements.
Note that we pass [] as a dependency array to useCallback. This
ensures that our ref callback doesn’t change between the re-renders,
and so React won’t call it unnecessarily.
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
Ok so I think what you're missing here is that changing a ref's value doesn't cause a re-render. So if it doesn't cause re-renders, then the function doesn't get run again. Which means useEffect isn't run again. Which means it never gets a chance to compare the values. If you trigger a re-render with a state change you will see that the effect will now get run. So try something like this:
export default function App() {
const [x, setX] = useState();
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<button
onClick={() => {
myRef.current = myRef.current + 1;
// Update state too, to trigger a re-render
setX(Math.random());
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
);
}
Now you can see it will trigger the effect.

React: useState value doesn't update on parameter value change

In the example below, the value of the display state on Child component never updates, even if the show parameter toggles between true and false.
I expect it to receive the value and to update accordingly. Can someone please elaborate on why this is not working?
(I know I can use a useEffect callback and setDisplay(show) from inside it, but I'd like to know why a simpler approach like this doesn't work)
function Child({ show }) {
const [display] = React.useState(show);
console.log({ show, display });
return display ? "Message!" : null;
}
function Parent() {
const [show, setShow] = React.useState(false);
const handleClick = () => {
setShow(!show);
};
return (
<div>
<div>
<button onClick={handleClick}>Toggle</button>
</div>
<Child show={show} />
</div>
);
}
Working example: https://codesandbox.io/s/react-boilerplate-4hexp?file=/src/index.js
Well the value of display is set only on the first render of the component (because it is state and state doesnt change with renders, but only when you tell it to change). If you want it to be changing with changing props just use a normal constant instead.
I believe it's because the useState in the Child component is reading show when it first loads but then never updates because it's just set, it doesn't automatically update.
You could either just use show directly which should be used for return show ? 'message' : <></>
Or you could still use the local state with useState, but you would need to add a useEffect to listen to the props change then change the local state of that child.
Update:
Third option for your current code to work would also be to do:
{show && <Child show={show} />}
That way at the time when it's true, the component will read the latest data.
display is local component state of Child, given an initial value from props.show when Child mounted. There is never a state update within Child to render any other value of display. This is actually an anti-pattern to store passed props in local component state, but there are two alternatives/solutions to getting display to update.
Use an effect to update state when the props update
function Child({ show }) {
const [display, setDisplay] = React.useState(show);
useEffect(() => setDisplay(show), [show]);
console.log(show, display);
return display ? "Message!" : null;
}
Or better, just consume the prop show directly
function Child({ show }) {
console.log(show);
return show ? "Message!" : null;
}
The benefit of the latter is that the new value of show and the updated/rerendered UI occur in the same render cycle. With the former (the anti-pattern) the state needs to update then the component rerenders, so the updated UI is a render cycle delayed.

Hooks setter not setting state with object variable

I'm trying to build a simple tree menu with react. I found this video showing exactly what I want to achieve (his Codepen). I was able to implement his approach, but then, out of curiosity, tried to replicate the same using hooks. I ended up with this (simplified):
my Codepen
const App2 = () => {
const [selectedOptions, setSelectedOptions] = React.useState({});
React.useEffect(() => {
console.log(selectedOptions);
},[selectedOptions]);
const updateSelection = (sel) => {
console.log(sel)
setSelectedOptions(sel);
}
return (
<div className="wrapper">
<h1>Toppings</h1>
<OptionsList
options={options}
onChange={updateSelection}
selectedOptions={selectedOptions}
/>
</div>
);
}
const OptionsList = ({ selectedOptions, onChange }) => {
const handleCheckboxClicked = (selectedOptionId) => {
if(selectedOptions[selectedOptionId]){
delete selectedOptions[selectedOptionId];
} else {
selectedOptions[selectedOptionId] = {}
}
onChange(selectedOptions);
}
return (
<div>
<button onClick={() => (handleCheckboxClicked("chicken-id"))} >
Set Chicken
</button>
</div>
)
}
ReactDOM.render(<App2 />, document.querySelector('#app'));
The problem is setSelectedOptions(sel), inside the function updateSelection is not doing absolutely anything (and of course useEffect is not being fired).
I'm not able to figure out why. I put a console.log just above it to check whether the variable ("sel") was okay or not, but it seems fine. I tried hardcoding the value of "sel", {chicken-id: {}}, and it works when I do so.
The problem is that you are directly mutating the state object:
if(selectedOptions[selectedOptionId]){
delete selectedOptions[selectedOptionId];
} else {
selectedOptions[selectedOptionId] = {}
}
onChange(selectedOptions);
In both outcomes you alter the state object and then set it as itself, so naturally the useEffect will not fire as the setter has effected no actual change. You have to be careful of doing this in React as it's an easy way to end up with stale values. Re-renders are only triggered when there is a difference between the current state and the value passed by the setter, not whenever the values of the state change, for whatever reason.
A comment suggested that you use spread - ... - to destructure the state object so that you can set it as itself, but this seems like a bit of an anti-pattern to me especially as it leaves the state mutation in place. If you want to delete an entry from a state object then a general approach would be to clone the object first, mutate the clone, then set that as the new state:
const clone = Object.assign({}, selectedOptions);
if(clone[selectedOptionId]){
delete clone[selectedOptionId];
} else {
clone[selectedOptionId] = {}
};
onChange(clone);
A word of caution though, any nested objects inside this object will retain their references to the original state and could still mutate it. You would have to clone these nested objects as well to avoid the issue.

How to update react state without re-rendering component?

I am building a gallery app where I need to create multiple HTTP requests to pull gallery entries(images & videos).
As gallery will be auto scrolling entries, I am trying to prevent re-rendering component when I make subsequent HTTP requests and update the state.
Thanks
Here's an example of only re-rendering when a particular condition is fulfilled (e.g. finished fetching).
For example, here we only re-render if the value reaches 3.
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends React.Component {
state = {
value: 0,
}
add = () => {
this.setState({ value: this.state.value + 1});
}
shouldComponentUpdate(nextProps, nextState) {
if (nextState.value !== 3) {
return false;
}
return true;
}
render() {
return (
<React.Fragment>
<p>Value is: {this.state.value}</p>
<button onClick={this.add}>add</button>
</React.Fragment>
)
}
}
render(<App />, document.getElementById('root'));
Live example here.
All data types
useState returns a pair - an array with two elements. The first element is the current value and the second is a function that allows us to update it. If we update the current value, then no rendering is called. If we use a function, then the rendering is called.
const stateVariable = React.useState("value");
stateVariable[0]="newValue"; //update without rendering
stateVariable[1]("newValue");//update with rendering
Object
If a state variable is declared as an object, then we can change its first element. In this case, rendering is not called.
const [myVariable, setMyVariable] = React.useState({ key1: "value" });
myVariable.key1 = "newValue"; //update without rendering
setMyVariable({ key1:"newValue"}); //update with rendering
Array
If a state variable is declared as an array, then we can change its first element. In this case, rendering is not called.
const [myVariable, setMyVariable] = React.useState(["value"]);
myVariable[0] = "newValue"; //update without rendering
setMyVariable(["newValue"]); //update with rendering
None of the answers work for TypeScript, so I'll add this. One method is to instead use the useRef hook and edit the value directly by accessing the 'current' property. See here:
const [myState, setMyState] = useState<string>("");
becomes
let myState = useRef<string>("");
and you can access it via:
myState.current = "foobar";
So far I'm not seeing any drawbacks. However, if this is to prevent a child component from updating, you should consider using the useMemo hook instead for readability. The useMemo hook is essentially a component that's given an explicit dependency array.
It's as easy as using this.state.stateName = value. This will change the state without re-rendering, unlike using this.setState({stateName:value}), which will re-render. For example;
class Button extends React.Component {
constructor( props ){
super(props);
this.state = {
message:"Hello World!"
};
this.method = this.method.bind(this);
}
method(e){
e.preventDefault();
this.state.message = "This message would be stored but not rendered";
}
render() {
return (
<div >
{this.state.message}
<form onSubmit={this.method}>
<button type="submit">change state</button>
</form>
</div>
)
}
}
ReactDOM.render(<Button />, document.getElementById('myDiv'));
If you just need a container to store the values, try useRef. Changing the value of ref.current doesn't lead to re-rendering.
const [ loading,setLoading] = useState(false)
loading=true //does not rerender
setLoading(true) //will rerender
In functional component refer above code, for class use componentShouldUpdate lifecycle

Accessing and setting state in an react/flux application

I'm working on a react app with a flux implementation.
I have a store which is bound to a component and in the ctor I set some default (blank) state values. In componentWillMount I populate the state by firing some actions which update the store data.
The store emits a change and the component handles that change by putting bits of the store data into state.
In my render method, I'm wanting the render to depend on the state data.
At the moment I have a couple of issues.
If in my render method I do something like this.state.MyThing.AProperty then the render method is called too early when MyThing hasn't been populated yet. This seems to occur in a lot of places where I want a render to use state data. Is there a sensible guard against this or am I doing this wrong?
I'm using a store to emit a change, and handling that change by getting data from the store and setting it to the state of the component. My thinking here is that if I set it as state then the component will know to re-render when the state changes. Is this correct? or should I be getting the data from the store in the emit handler and using it directly? or setting it to a local var in the component?
The reason I ask is that I seem to encounter issues with setState calls not being immediate and wanting to use state as soon as I set it. With this in mind it seems like I might be doing it wrong.
Any thoughts greatly appreciated.
If you use conditionals in your render, then you can guard against unpopulated data being rendered.
<div>
{typeof this.state.myThing == 'object' ?
<strong>this.state.myThing.aProperty</strong> :
<span>Nothing to see here</span>}
</div>
And with regards to your second question, yeah. That's totally fine and it's the expected way to work with Flux. You can even take inspiration from Redux & Co and make higher order components that map store state to props.
function connect(store, mapStateToProps, Component) {
return React.createClass({
getInitialState() {
const state = store.getState();
return { state };
},
componentWillMount() {
store.listen(state => this.setState({ state }));
},
render() {
const stateProps = mapStateToProps(this.state);
const passedProps = this.props;
const props = Object.assign({}, stateProps, passedProps);
return <Component {...props} />;
}
});
}
This pattern allows you to take an existing component and wrap it in a container that will re-render whenever the store changes, then use the mapStateToProps function to work out which props to pass down to your original component.
const MyStore = { ... };
const MyComponent = React.createClass( ... );
function mapStateToProps(state) {
return { foo: state.bar.foo };
}
export default connect(MyStore, mapStateToProps, MyComponent);
setState is an asychronous method as it needs to be batched to keep React apps from being delaying repaints when they trigger lots of updates. You can reliably wait for the state to change by passing a callback as the second argument.
this.setState({ foo: 'bar' }, () => this.state.foo);

Resources