I've been reading on why useRef is useful (e.g. in this SO answer and in the articles it links to) and it does make sense to me. However I notice that in my code I've "simply" solved the issue of how to store state in a functional component in a way that does not trigger re-renders by keeping the state as a global-scoped variable declared in the same file as the functional component.
I realize this isn't appropriate if the same component is rendered at the same time in multiple places on the DOM, as useRef supplies different state to different simultaneously rendered components whereas a file-scoped variable would be shared.
Is my mental model and assumptions correct and are there any other use cases or distinct advantages of useRef versus a file-scoped variable?
Is my mental model and assumptions correct ...
Not in the general case, even with your caveat. There are a couple of issues:
As you say, components can be instantiated more than once at the same thing (think: items in a list), but with your file-scoped (I assume you mean module-scoped) variable, all instances would use the same variable, causing cross-talk between the instances. With useRef, they'll each have their own non-state instance data.
Here's an example of the difference:
const { useState, useRef } = React;
let moduleScopedVariable = 0;
const TheComponent = () => {
const ref = useRef(0);
// Synthetic use case: counting renders
++ref.current;
++moduleScopedVariable;
return (
<div className="the-component">
<div>Render count (ref): {ref.current}</div>
<div>Render count (var): {moduleScopedVariable}</div>
</div>
);
};
const ids = [0, 1, 2];
const Example = () => {
const [counter, setCounter] = useState(0);
return (
<div>
Counter: {counter}
<input type="button" value="Increment" onClick={() => setCounter(c => c + 1)} />
<div>{ids.map((id) => <TheComponent key={id} />)}</div>
</div>
)
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
.the-component {
border: 1px solid black;
margin: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
It's not just at the same time, there's crosstalk with a single instance that's mounted and unmounted over time:
Here's an example:
const { useState, useRef } = React;
let moduleScopedVariable = 0;
const TheComponent = () => {
const ref = useRef(0);
// Synthetic use case: counting renders
++ref.current;
++moduleScopedVariable;
return (
<div className="the-component">
<div>Render count (ref): {ref.current}</div>
<div>Render count (var): {moduleScopedVariable}</div>
</div>
);
};
const Example = () => {
const [flag, setFlag] = useState(true);
return (
<div>
Flag: {String(flag)}
<input type="button" value="Toggle" onClick={() => setFlag(b => !b)} />
{flag && <TheComponent />}
</div>
)
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
.the-component {
border: 1px solid black;
margin: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
It also means the data remains lying around when the component is unmounted, which depending on what the data is could be an issue.
Fundamentally, it's great to reuse static data by closing over a module-scoped constant, but anything that changes within the component should be stored in state (in any of various guises) if it affects how the component renders, or a ref (usually) if not.
Related
I'm trying to pass a state that is generated inside of an async function that itself is inside of a useState. I learned that useRef might be the best way to go about this since it can reference a mutable state, and in the process learned about react-useStateRef, which finally solved another issue I had of state never updating inside my main component (would constantly get "too many renders" errors). So it essentially acts as useRef and useState in one.
But while my state does finally update, it still doesn't pass to my Canvas component. I'm trying to update the BG graphic of the canvas depending on the temperature I get from my dataset.
import { useEffect } from 'react';
import useState from 'react-usestateref';
import { getServerData } from './serviceData';
import "./App.css"
import Canvas from './components/Canvas';
function App() {
const [dataset, setDataset] = useState(null);
const [units, setUnits] = useState('metric');
// canvas background graphic stuff
var [currentBG, setCurrentBG, currentBGref] = useState(null);
useEffect(() => {
const fetchServerData = async () => {
const data = await getServerData(city, units);
setDataset(data);
function updateBG() {
const threshold = units === "metric" ? 20 : 60;
if (data.temp <= threshold) {
setCurrentBG('snow');
}
else {
setCurrentBG('sunny');
}
}
updateBG();
}
fetchServerData();
console.log(currentBG)
}, [city, units, currentBGref.current, currentFGref.current])
const isCelsius = currentUnit === "C";
button.innerText = isCelsius ? "°F" : "°C";
setUnits(isCelsius ? "metric" : "imperial");
};
return (
<div className="app">
{ dataset && ( <Canvas width={640} height={480} currentBG={currentBGref.current}></Canvas> )}
</div>
);
}
export default App;
I can only pass the initial value and it never updates past that, although the console.log inside of the useEffect shows that it definitely is updating. So why isn't it passing to my component?
useStateRef appears to be an anti-pattern. You decide what the new state is so in the off-chance you need another reference to it, you can always create one yourself. I would suggest minimizing properties on your canvas to prevent unnecessary re-renders.
function App({ width, height }) {
const canvas = React.useRef()
const [color, setColor] = React.useState("white")
// when color changes..
React.useEffect(() => {
if (canvas.current) {
const context = canvas.current.getContext('2d');
context.fillStyle = color
context.fillRect(0, 0, width, height)
}
}, [color, width, height])
return <div>
<canvas ref={canvas} width={width} height={height} />
<button onClick={_ => setColor("blue")} children="blue" />
<button onClick={_ => setColor("green")} children="green" />
<button onClick={_ => setColor("red")} children="red" />
</div>
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App width={200} height={150} />)
canvas { display: block; border: 1px solid black; }
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
Or maybe there's no reason to store the color as state. You could make your own setColor function and attach it as an event listener -
function App({ width, height }) {
const canvas = React.useRef()
const setColor = color => event => {
if (canvas.current) {
const context = canvas.current.getContext('2d');
context.fillStyle = color
context.fillRect(0, 0, width, height)
}
}
return <div>
<canvas ref={canvas} width={width} height={height} />
<button onClick={setColor("blue")} children="blue" />
<button onClick={setColor("green")} children="green" />
<button onClick={setColor("red")} children="red" />
</div>
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App width={200} height={150} />)
canvas { display: block; border: 1px solid black; }
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
I would also suggest you look at SVG. I find the API gels with the React pattern much better than the Canvas API. The capabilities of each differ, but it's worth considering if SVG handles your needs.
I have a component, something like,
const ComponentA = ({ heading })=> {
return (<h1>{ heading }</h>);
};
Is there any difference in rendering this component using below two options,
Option 1
const ComponentB = ()=> {
return ComponentA({ heading: 'Heading test' });
};
Option 2
const ComponentB = ()=> {
return <ComponentA heading='Heading test' />;
};
Yes. There is a very important difference. In option 1, ComponentA is not actually a component as far as React is concerned. In option 2 it is.
The best way to illustrate the difference is to put state or another hook inside of ComponentA. Option 1 will not work as expected. (Note: if it does happen to work as expected, you still shouldn't trust it. This is where bugs can sneak in because they don't cause issues until later).
Here is an example where using hooks appears to work, but breaks after you get the counter past 5. This is because React is treating the hooks inside ComponentA as if they belong to Example. Notice how the JSX version works as expected even after it disappears.
const {useState, useEffect} = React;
const ComponentA = ({ id, heading })=> {
React.useEffect(() => {
console.log(id, 'mounted');
}, []);
return (
<h1>
{ heading }
</h1>
);
};
const Example = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Re-render me</button> {count}
{count < 5 ? ComponentA({ heading: 'Heading test1', id: 1 }) : null}
{count < 3 ? <ComponentA heading='Heading test2' id={2} /> : null}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The reason is JSX (< /> syntax) is actually just a syntax for calling React.createElement. If this is not called, the function component does not get it's own lifecycle, etc. You've bypassed the way React tracks components.
This is my code.
export default MainContent = () => {
handleClick = (e) => {
// This is where I got confused
};
return (
<>
<div>
<div onClick={handleClick}>1</div>
</div>
<div>
<div onClick={handleClick}>2</div>
</div>
<div>
<div onClick={handleClick}>3</div>
</div>
</>
);
}
What I want is to add a class to parent div when child element is clicked. I couldn't use useState() since I only need one element to update. Couldn't use setAttribute since it changes the same element. Is there any solution for that?
I take it you want to apply the class only to direct parent of clicked child.
create a state to oversee different clicked child div
apply the class only to direct parent of clicked* child div based on the state
make use of clsx npm package (since we don't wanna overwrite parent div styling)
you may see the working examples here: https://codesandbox.io/s/happy-babbage-3eczt
import { useState } from "react";
import "./styles.css";
import styled from "styled-components";
import classnames from "clsx";
export default function App() {
const [styling, setstyling] = useState({
status: false,
from: "",
style: ""
});
function handleClick(childNo) {
setstyling({ status: true, from: childNo, style: "applyBgColor" });
}
return (
<div className="App">
<Styling>
<div
className={
styling?.status && styling?.from == "child-1"
? classnames("indentText", styling?.style)
: "indentText"
}
>
<div
className="whenHoverPointer"
onClick={() => handleClick(`child-1`)}
>1</div>
</div>
<div
className={
styling?.status && styling?.from == "child-2"
? styling?.style
: ""
}
>
<div
className="whenHoverPointer"
onClick={() => handleClick(`child-2`)}
>2</div>
</div>
</Styling>
</div>
);
}
const Styling = styled.div`
.indentText {
font-style: italic;
}
.applyBgColor {
background-color: grey;
}
.whenHoverPointer {
cursor: pointer;
}
`;
function Item({ children }) {
const [checked, isChecked] = useState(false);
const onClick = () => isChecked(true);
return (
<div {...(isChecked && { className: 'foo' })}>
<button type="button" onClick={onClick}>{children}</button>
</div>
);
}
function MainContent() {
return [1, 2, 3].map(n => <Item key={n}>{n}</Item>);
}
I think theirs something wrong, useState and JSX will update related part, react will handling that itself, but base on logic, may you need to prevent re-render to stop issue, for example, here on handleClick, will keep re-render in each state since function will re-addressed in memory each update..
any way, base on your question, you can do that by this:
const handleClick = useCallback((e) => {
e.target.parentElement.classList.add('yourClass')
}, []);
But I believe its a Bad solution.
What I recommend is solve issue by state to keep your react life cycle is fully work and listen to any update, also you can use ref to your wrapper div and add class by ref.
I have this code on Codesandbox the goal is to be able to pass 5 Divs, on load using use Effect.
and a second option to add a div on click when if the user feels like it. the code is partially working, but it has a anti-patter issue which is putting the component in the state instead of changing the state using map to pass the changes..
please take a look I would like to hear your opinion on this, what I do understand is importing the Div element like this could affect performance, I want to avoid bad practice as much as possible.
import React, { useEffect, useState } from "react";
import Div from "./Div";
import "./styles.css";
import { v4 as uuidv4 } from "uuid";
export default function App() {
useEffect(() => {
// on start add 5 divs in to the local state Array on the frist load
});
const [div, setDiv] = useState([]);
const addDiv = () => {
// add an extra div on click if needed with id using the right pattern
setDiv([...div, <Div id={uuidv4()} />]);
};
return (
<div className="App">
{div}
<button onClick={addDiv} type="button">
Click Me!
</button>
</div>
);
}
//Dev dependencise
"uuidv4": "6.2.12"
Codesandbox
Putting JSX elements into state is a bad idea because they won't be reactive - you won't be able to (reliably) pass down state, state setters, and other useful things as props.
It's not so much a performance issue as a code maintainability issue - if you add additional functionality to your Div component and to your App you may find that your current approach won't work due to stale values that the JSX elements in state close over.
If you need the ability to delete a value, use the index of the div in the array and pass it down as needed. For a quick and dirty example:
function App() {
const [texts, setTexts] = React.useState([]);
const [text, setText] = React.useState('');
React.useEffect(() => {
setTexts(['a', 'b', 'c', 'd', 'e']);
}, []);
const addDiv = () => {
setTexts([...texts, text]);
setText('');
};
return (
<div className="App">
{
texts.map((text, i) => (
<div>
<span>{text}</span>
<button onClick={() => setTexts(texts.filter((_, j) => j !== i))}>delete</button>
</div>
))
}
<button onClick={addDiv} type="button">
Click Me!
</button>
<input value={text} onChange={e => setText(e.target.value)} />
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
Just add id in the array and using map to render
{div.map(id => (
<Div key={id} id={id} />
))}
const addDiv = () => {
setDiv([...div, uuidv4()]);
};
I cannot understand why the following useImperativeHandle, useLayoutEffect, and useDebugValue hooks are needed, can you give examples when they can be used, but not examples from the documentation please.
Allow me to preface this answer by stating that all of these hooks are very rarely used. 99% of the time, you won't need these. They are only meant to cover some rare corner-case scenarios.
useImperativeHandle
Usually when you use useRef you are given the instance value of the component the ref is attached to. This allows you to interact with the DOM element directly.
useImperativeHandle is very similar, but it lets you do two things:
It gives you control over the value that is returned. Instead of returning the instance element, you explicitly state what the return value will be (see snippet below).
It allows you to replace native functions (such as blur, focus, etc) with functions of your own, thus allowing side-effects to the normal behavior, or a different behavior altogether. Though, you can call the function whatever you like.
There could be many reasons you want might to do either of the above; you might not want to expose native properties to the parent or maybe you want to change the behavior of a native function. There could be many reasons. However, useImperativeHandle is rarely used.
useImperativeHandle customizes the instance value that is exposed to parent components when using ref
Example
In this example, the value we'll get from the ref will only contain the function blur which we declared in our useImperativeHandle. It will not contain any other properties (I am logging the value to demonstrate this). The function itself is also "customized" to behave differently than what you'd normally expect. Here, it sets document.title and blurs the input when blur is invoked.
const MyInput = React.forwardRef((props, ref) => {
const [val, setVal] = React.useState('');
const inputRef = React.useRef();
React.useImperativeHandle(ref, () => ({
blur: () => {
document.title = val;
inputRef.current.blur();
}
}));
return (
<input
ref={inputRef}
val={val}
onChange={e => setVal(e.target.value)}
{...props}
/>
);
});
const App = () => {
const ref = React.useRef(null);
const onBlur = () => {
console.log(ref.current); // Only contains one property!
ref.current.blur();
};
return <MyInput ref={ref} onBlur={onBlur} />;
};
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
useLayoutEffect
While similar to some extent to useEffect(), it differs in that it will run after React has committed updates to the DOM. Used in rare cases when you need to calculate the distance between elements after an update or do other post-update calculations / side-effects.
The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Example
Suppose you have an absolutely positioned element whose height might vary and you want to position another div beneath it. You could use getBoundingClientRect() to calculate the parent's height and top properties and then just apply those to the top property of the child.
Here you would want to use useLayoutEffect rather than useEffect. See why in the examples below:
With useEffect: (notice the jumpy behavior)
const Message = ({boxRef, children}) => {
const msgRef = React.useRef(null);
React.useEffect(() => {
const rect = boxRef.current.getBoundingClientRect();
msgRef.current.style.top = `${rect.height + rect.top}px`;
}, []);
return <span ref={msgRef} className="msg">{children}</span>;
};
const App = () => {
const [show, setShow] = React.useState(false);
const boxRef = React.useRef(null);
return (
<div>
<div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
{show && <Message boxRef={boxRef}>Foo bar baz</Message>}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
.box {
position: absolute;
width: 100px;
height: 100px;
background: green;
color: white;
}
.msg {
position: relative;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
With useLayoutEffect:
const Message = ({boxRef, children}) => {
const msgRef = React.useRef(null);
React.useLayoutEffect(() => {
const rect = boxRef.current.getBoundingClientRect();
msgRef.current.style.top = `${rect.height + rect.top}px`;
}, []);
return <span ref={msgRef} className="msg">{children}</span>;
};
const App = () => {
const [show, setShow] = React.useState(false);
const boxRef = React.useRef(null);
return (
<div>
<div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
{show && <Message boxRef={boxRef}>Foo bar baz</Message>}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
.box {
position: absolute;
width: 100px;
height: 100px;
background: green;
color: white;
}
.msg {
position: relative;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
useDebugValue
Sometimes you might want to debug certain values or properties, but doing so might require expensive operations which might impact performance.
useDebugValue is only called when the React DevTools are open and the related hook is inspected, preventing any impact on performance.
useDebugValue can be used to display a label for custom hooks in React DevTools.
I have personally never used this hook though. Maybe someone in the comments can give some insight with a good example.
useImperativeHandle
useImperativeHandle allows you to determine which properties will be exposed on a ref. In the example below, we have a button component, and we'd like to expose the someExposedProperty property on that ref:
[index.tsx]
import React, { useRef } from "react";
import { render } from "react-dom";
import Button from "./Button";
import "./styles.css";
function App() {
const buttonRef = useRef(null);
const handleClick = () => {
console.log(Object.keys(buttonRef.current)); // ['someExposedProperty']
console.log("click in index.tsx");
buttonRef.current.someExposedProperty();
};
return (
<div>
<Button onClick={handleClick} ref={buttonRef} />
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
[Button.tsx]
import React, { useRef, useImperativeHandle, forwardRef } from "react";
function Button(props, ref) {
const buttonRef = useRef();
useImperativeHandle(ref, () => ({
someExposedProperty: () => {
console.log(`we're inside the exposed property function!`);
}
}));
return (
<button ref={buttonRef} {...props}>
Button
</button>
);
}
export default forwardRef(Button);
Available here.
useLayoutEffect
This is the same as useEffect, but only fires once all DOM mutations are completed. This article From Kent C. Dodds explains the difference as well as anyone, regarding these two, he says:
99% of the time [useEffect] is what you want to use.
I haven't seen any examples which illustrate this particularly well, and I'm not sure I'd be able to create anything either. It's probably best to say that you ought to only use useLayoutEffect when useEffect has issues.
useDebugValue
I feel like the docs do a pretty good example of explaining this one. If you have a custom hook, and you'd like to label it within React DevTools, then this is what you use.
If you have any specific issues with this then it'd probably be best to either comment or ask another question, because I feel like anything people put here will just be reiterating the docs, at least until we reach a more specific problem.
The useImperativeHandle hook helped me a lot with a use case of mine.
I created a grid component which uses a third-party library component. The library itself has a big data layer with built-in functionality that can be used by accessing the instance of the element.
However, in my own grid component, I want to extend it with methods which perform actions on the grid. Furthermore, I also want to be able to execute those methods from outside of my grid component.
This is easily achievable by adding the methods inside of the useImperativeHandle hook, and then they will be exposed and usable by its parent.
My grid component looks kind of like this:
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import ThirdPartyGrid from 'some-library';
export default forwardRef((props, forwardedRef) => {
const gridRef = useRef(null);
useImperativeHandle(forwardedRef,
() => ({
storeExpandedRecords () {
// Some code
},
restoreExpandedRecords () {
// Some code
},
}));
return (
<div ref={forwardedRef}>
<ThirdPartyGrid
ref={gridRef}
{...props.config}
/>
</div>
);
});
And then in my parent, I can execute those methods:
import React, { useRef, useEffect } from 'react';
export default function Parent () {
const gridRef = useRef(null);
const storeRecords = () => {
gridRef.current.storeExpandedRecords();
};
useEffect(() => {
storeRecords();
}, []);
return <GridWrapper ref={gridRef} config={{ something: true }} />
};
useImperativeHandle
usually hook expose your functional based component method and properties to other component by putting functional component inside forwardRef
example
const Sidebar=forwardRef((props,ref)=>{
const [visibility,setVisibility]=useState(null)
const opensideBar=()=>{
setVisibility(!visibility)
}
useImperativeHandle(ref,()=>({
opensideBar:()=>{
set()
}
}))
return(
<Fragment>
<button onClick={opensideBar}>SHOW_SIDEBAR</button>
{visibility==true?(
<aside className="sidebar">
<ul className="list-group ">
<li className=" list-group-item">HOME</li>
<li className=" list-group-item">ABOUT</li>
<li className=" list-group-item">SERVICES</li>
<li className=" list-group-item">CONTACT</li>
<li className=" list-group-item">GALLERY</li>
</ul>
</aside>
):null}
</Fragment>
)
}
//using sidebar component
class Main extends Component{
myRef=createRef();
render(){
return(
<Fragment>
<button onClick={()=>{
///hear we calling sidebar component
this.myRef?.current?.opensideBar()
}}>
Show Sidebar
</button>
<Sidebar ref={this.myRef}/>
</Fragment>
)
}
}
I see that there are already answers to this question. So, I just want to share my articles about useImperativeHandle, useEffect, and useLayoutEffect.
I have written an example-based article aboutuseImperativeHandle hook, you can check it out here: useImperativeHandle by Examples
I also wrote an article about effects in React which explains useEffect hook and useLayoutEffect hook differences in detail: A Beginner’s Guide to Effects in React