Because this component changes frequently, I decided to use useRef to store values (for example, a counter). On an event (such as onclick), the log shows that the number did increment by 1 each time the button was clicked, but the value isn't updated on the screen (inside the div). It's showing 0. What am I missing here?
Intended output: on click button, add() increases counter, and display value in <div>.
const counter = useRef(0);
function add() {
counter.current += 1;
console.log(counter.current); // this shows that the number did increment
}
return (
<div>
<div>{counter.current}</div> {/* this shows '0' */}
<button onClick={() => add()}>Add</button>
</div>
);
As you stated in the comments:
I am interacting with this variable in third party library listener functions. These libraries loads on page load, and receiving events from javascript listener.
Means that you want to render component on 3rd party reference change, usually you mock a render like so:
const reducer = p => !p;
const App = () => {
const counter = useRef(0);
const [, render] = useReducer(reducer, false);
function add() {
// Some 3rd party ref
counter.current += 1;
// Render and update the UI
render();
}
return (
<div>
<div>{counter.current}</div>
<button onClick={() => add()}>Add</button>
</div>
);
};
read the doc
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.
using the useState is the best thing in our case
Related
I've been learning react over the last few days and for the most part it makes sense, however there is one thing that is stumping me.
If I have
A Parent element with some state variables and a callback method
A child element that takes a callback method as a prop
The callback method relies on some piece of state that is in the parent element
I don't want to re-create the view object every time any state changes
Every time I try to do this, it seems like the child element is calling some older version of the parent element (presumably the instance of the parent that actually created the child) instead of the current version.
I'm getting the feeling that what I want is just wrong on a fundamental level and isnt The React Way
The reason that I am trying to do this is that my main parent contains 17 divs, each of which represent a key on a musical instrument, and each of which contains at least 20-30 divs. The lowest div (of which there are at least a few hundred) has an onClick event that I want to modify the functionality of based on whether modifier keys are held down (shift, control etc).
Currently I have Raised the state of the shiftPressed to be on the single parent element then passed down the value of that into each child through props, however re-rendering hundreds of divs whenever a user pushes shift takes quite a while.
I've made a code sandbox to show the current problem sandbox
Sandbox code:
import "./styles.css";
import { useState, useEffect, useRef } from "react";
export default function App() {
//Our state holding data
const [state, setState] = useState(false);
//Our state holding the view
const [view, setView] = useState(<div></div>);
const printState = useRef(null);
//Component did mount hook
useEffect(() => {
reGenerate();
}, []);
//state update hook
useEffect(() => {
printState.current();
}, [state]);
//function to flip the state
const flipState = () => {
setState(!state);
};
//The method that updates the view
//(The idea being that I don't want to update the view on every state change)
const reGenerate = () => {
setView(
<>
<p>
State: {state && "true"} {state || "false"}
</p>
<Child callback={printState} />
</>
);
};
//Method for validation
printState.current = () => {
console.log("Printed state: " + state);
};
return (
<div className="App">
<h1>Parent-child-prop-problem (prop-lem)</h1>
<ol>
<li>click "force regeneration"</li>
<li>
click "flip state" and the value of state after the flip will be
printed in console, but it won't show up on the HTML element
</li>
<li>
Click "print state (from child)" and observe that the console is
printing the old version of the state
</li>
</ol>
<button onClick={flipState}>Flip State</button>
<button onClick={reGenerate}>Force Regeneration</button>
{view}
</div>
);
}
function Child(props) {
return (
<div>
<button onClick={props.callback.current}>Print State (from child)</button>
</div>
);
}
Taking a quick peek at your sandbox code and I see that you are storing JSX in state, which is anti-pattern and often leads to stale enclosures like you describe.
I don't want to re-create the view object every time any state changes
"Recreating" the view is a necessary step in rendering UI in React as a result of state or props updating. State should only ever store data and the UI should be rendered from the state. In other words, treat your UI like a function of state and props. Toggle the state state value and render the UI from state.
Example:
export default function App() {
//Our state holding data
const [state, setState] = useState(false);
const printState = useRef(null);
//state update hook
useEffect(() => {
printState.current();
}, [state]);
//function to flip the state
const flipState = () => {
setState(!state);
};
//Method for validation
printState.current = () => {
console.log("Printed state: " + state);
};
return (
<div className="App">
<h1>Parent-child-prop-problem (prop-lem)</h1>
<ol>
<li>
click "flip state" and the value of state after the flip will be
printed in console, but it won't show up on the HTML element
</li>
<li>
Click "print state (from child)" and observe that the console is
printing the old version of the state
</li>
</ol>
<button onClick={flipState}>Flip State</button>
<p>State: {state ? "true" : "false"}</p>
<Child callback={printState} />
</div>
);
}
function Child(props) {
return (
<div>
<button onClick={props.callback.current}>Print State (from child)</button>
</div>
);
}
It's also generally considered anti-pattern to use any sort of "forceUpdate" function and is a code smell. In almost all circumstances if you hit a point where you need to force React to rerender you are doing something incorrect. This is the time you step back and trace through your code to find where a certain piece of state or some prop isn't updated correctly to trigger a rerender naturally.
Im doing a React small training app using Hooks. Here's the example:
There is a MainPage.js and it has 3 similar child components Card.js. I have global state in MainPage and each Card has its own local state. Every Card has prop "id" from MainPage and clickButton func.
When I click button in any Card there are 2 operations:
Local variable 'clicked' becomes true.
The function from parent component is invoked and sets value to global state variable 'firstCard'.
Each file contains console.log() for testing. And when I click the button it shows actual global variable "firstCard", and 3x times false(default value of variable "clicked" in Card).
It means that component MainPage is rendered after clicking button ? And every Card is rendered too with default value of "clicked".
Why MainPage componenet is rendered, after all we dont use variable "firsCard", except console.log()?
How to make that after clicking any button, there will be changes in exactly component local state, and in the same time make global state variable "firstCard" changed too, but without render parent component(we dont use in jsx variable "firstCard")
Thanks for your help !
import Card from "../Card/Card";
const Main = () => {
const [cards, setCards] = useState([]);
const [firstCard, setFirstCard] = useState(null);
useEffect(() => {
setCards([1, 2, 3]);
}, []);
const onClickHandler = (id) => {
setFirstCard(id);
};
console.log(firstCard); // Showing corrrect result
return (
<div>
{cards.map((card, i) => {
return (
<Card
key={Date.now() + i}
id={card}
clickButton={(id) => onClickHandler(id)}
></Card>
);
})}
</div>
);
};
import React, { useState } from "react";
const Card = ({ id, clickButton }) => {
const [clicked, setClicked] = useState(false);
const onClickHandler = () => {
setClicked(true);
clickButton(id);
};
console.log(clicked); // 3x false
return (
<div>
<h1>Card number {id}</h1>
<button onClick={() => onClickHandler()}> Set ID</button>
</div>
);
};
export default Card;
You have wrong idea how react works.
When you change something in state that component will re render, regardless if you use that state variable in render or not.
Moreover, react will also re render all children of this component recursively.
Now you can prevent the children from re rendering (not the actual component where state update happened though) in some cases, for that you can look into React.memo.
That said prior to React hooks there was a method shouldComponentUpdate which you could have used to skip render depending on change in state or props.
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.
I wrote the following React exercise which uses no hooks and renders a button.
const Button = ({ onClick }) => <button onClick={onClick}>Do Nothing</button>;
const Base = () => {
const onClickFunction = (() => {
console.log("Creating OnClick Function");
return () => {};
})();
return (
<div className="App">
<h1>Hello</h1>
<Button onClick={onClickFunction} />
</div>
);
};
onClickFunction uses a self-invoking function, so that I can place a console.log to see the following behaviour. In this example, when Base is rendered, the message Creating OnClick Function appears only once 👍
If I change Base to the following however, adding a hook usage:
const Button = ({ onClick }) => <button onClick={onClick}>Do Nothing</button>;
const Base = () => {
const notUsedRef = React.useRef();
const onClickFunction = (() => {
console.log("Creating OnClick Function");
return () => {};
})();
return (
<div className="App">
<h1>Hello</h1>
<Button onClick={onClickFunction} />
</div>
);
};
You will see the Creating OnClick Function message twice.
This CodeSandbox illustrates what I've been seeing: https://codesandbox.io/s/dawn-forest-99clo?file=/src/App.js
Using React DevTools Profiler, we can see there is no rerender of this component.
Using <React.Profiler, it reports this component also didn't update.
I know that using React.useCallback wouldn't trigger a second invokation, however the question would still stand why we are in the situation Base is called twice.
My question is: why and what is triggering Base to be invoked when there is no need for a rerender.
This is due to the way React implements hooks.
If you invoke any hook, even if you don't use the resulting value, you are telling React to render twice before mounting, even if the props don't change. You can substitute the usage of useRef by useState, useEffect, etc. Try below.
You can also wrap your component with React.memo. Every function defined inside the function is recreated in every render.
https://codesandbox.io/s/elastic-water-y18w0?file=/src/App.js
EDIT: Only happens during development and in components wrapped by React.StrictMode. In the words of gaearon:
It's an intentional feature of the StrictMode. This only happens in
development, and helps find accidental side effects put into the
render phase. We only do this for components with Hooks because those
are more likely to accidentally have side effects in the wrong place.
https://github.com/facebook/react/issues/15074
I occasionally have react components that are conceptually stateful which I want to reset. The ideal behavior would be equivalent to removing the old component and readding a new, pristine component.
React provides a method setState which allows setting the components own explicit state, but that excludes implicit state such as browser focus and form state, and it also excludes the state of its children. Catching all that indirect state can be a tricky task, and I'd prefer to solve it rigorously and completely rather that playing whack-a-mole with every new bit of surprising state.
Is there an API or pattern to do this?
Edit: I made a trivial example demonstrating the this.replaceState(this.getInitialState()) approach and contrasting it with the this.setState(this.getInitialState()) approach: jsfiddle - replaceState is more robust.
To ensure that the implicit browser state you mention and state of children is reset, you can add a key attribute to the root-level component returned by render; when it changes, that component will be thrown away and created from scratch.
render: function() {
// ...
return <div key={uniqueId}>
{children}
</div>;
}
There's no shortcut to reset the individual component's local state.
Adding a key attribute to the element that you need to reinitialize, will reload it every time the props or state associate to the element change.
key={new Date().getTime()}
Here is an example:
render() {
const items = (this.props.resources) || [];
const totalNumberOfItems = (this.props.resources.noOfItems) || 0;
return (
<div className="items-container">
<PaginationContainer
key={new Date().getTime()}
totalNumberOfItems={totalNumberOfItems}
items={items}
onPageChange={this.onPageChange}
/>
</div>
);
}
You should actually avoid replaceState and use setState instead.
The docs say that replaceState "may be removed entirely in a future version of React." I think it will most definitely be removed because replaceState doesn't really jive with the philosophy of React. It facilitates making a React component begin to feel kinda swiss knife-y.
This grates against the natural growth of a React component of becoming smaller, and more purpose-made.
In React, if you have to err on generalization or specialization: aim for specialization. As a corollary, the state tree for your component should have a certain parsimony (it's fine to tastefully break this rule if you're scaffolding out a brand-spanking new product though).
Anyway this is how you do it. Similar to Ben's (accepted) answer above, but like this:
this.setState(this.getInitialState());
Also (like Ben also said) in order to reset the "browser state" you need to remove that DOM node. Harness the power of the vdom and use a new key prop for that component. The new render will replace that component wholesale.
Reference: https://facebook.github.io/react/docs/component-api.html#replacestate
The approach where you add a key property to the element and control its value from the parent works correctly. Here is an example of how you use a component to reset itself.
The key is controlled in the parent element, but the function that updates the key is passed as a prop to the main element. That way, the button that resets a form can reside in the form component itself.
const InnerForm = (props) => {
const { resetForm } = props;
const [value, setValue] = useState('initialValue');
return (
<>
Value: {value}
<button onClick={() => { setValue('newValue'); }}>
Change Value
</button>
<button onClick={resetForm}>
Reset Form
</button>
</>
);
};
export const App = (props) => {
const [resetHeuristicKey, setResetHeuristicKey] = useState(false);
const resetForm = () => setResetHeuristicKey(!resetHeuristicKey);
return (
<>
<h1>Form</h1>
<InnerForm key={resetHeuristicKey} resetForm={resetForm} />
</>
);
};
Example code (reset the MyFormComponent and it's state after submitted successfully):
function render() {
const [formkey, setFormkey] = useState( Date.now() )
return <>
<MyFormComponent key={formkey} handleSubmitted={()=>{
setFormkey( Date.now() )
}}/>
</>
}
Maybe you can use the method reset() of the form:
import { useRef } from 'react';
interface Props {
data: string;
}
function Demo(props: Props) {
const formRef = useRef<HTMLFormElement | null>(null);
function resetHandler() {
formRef.current?.reset();
}
return(
<form ref={formRef}>
<input defaultValue={props.data}/>
<button onClick={resetHandler}>reset</button>
</form>
);
}