const Root = () => {
/** useState */
const [count, setCount] = useState(1);
const [times, setTimes] = useState(1);
const handleCount = useCallback(() => {
setCount(count * 2);
}, []);
const handleTimes = useCallback(() => {
setTimes(times + 2);
}, []);
return (
<div>
<div>Root. counts: {count}</div>
<Counter onClick={handleCount} value={count} />
<Counter onClick={handleTimes} value={times} />
</div>
);
};
The child component will re-render when parent component re-renders, so I don't know why I should use useCallback. When will the function in useCallback change without my component re-rendering?
I suggest you to use React.memo in your child component.
This code will help you,
export const Counter = React.memo(props => {
return ...
})
Here is the doc link.
First, you haven't defined a dependency array for either of your useCallbacks. The child components would rerender if the function were rebuilt, or if the value were to change. Use should also be using the lazy set method of state.
const handleCount = useCallback(() => {
setCount(prev => prev * 2);
}, [setCount]);
Related
I have some problem with useEffect. When the counter changes it causes the whole table to be rerendered, but i dont pass timer as props in table. How i can prevent this behavior?
function App() {
const dispatch = useDispatch();
const data = useSelector(state => state.data);
const [error, setError] = useState("");
const [counter, setCounter] = useState();
useEffect(() => {
const fetchData = async (setError, setCounter) => {
try {
const response = await axios(url, token);
dispatch(getData(response.data.value));
setError("");
setCounter(180);
} catch(e) {
setError("Error!");
setCounter(180);
}}
fetchData(setError, setCounter);
const interval = setInterval(() => {
fetchData(setError, setCounter);
}, timeToReload * 1000);
const countInterval = setInterval(() =>
setCounter((prev) => prev - 1), 1000)
return () => {
clearInterval(interval);
clearInterval(countInterval);
}
},[dispatch])
const dataForTable = selectorData([...data], {name: sortArrow.columnName, order: sortArrow.sortOrder, type: sortArrow.type})
return (
<div className="App">
<div className="headerWrapper">
<div
className={error ? "LoadingStatus disconnect": "LoadingStatus connect"}>
{error && <div>{error}</div>}
{isFinite(counter) && <div>{"Reload " + counter + " sec"}</div> }
</div>
</div>
<Table specialCategory={specialCategory} data={dataForTable} sortArrow={sortArrow} setSortArrow={setSortArrow}/>
</div>
);
}
export default App;
I trued to useRef without useState, but nothing has changed. Maybe another props in Table component trigger the change?
Imptortant notice: only the body of the table is changed.
When you update the state (e.g. setCounter(...)) of the App component, it causes the entire component with all of it's child-components, including <Table/>, to be re-rendered.
You can either create a new component for everything except the table and put the states which are changing (error and counter) into that file, or memoize the Table component like this:
import { memo } from "react";
function Table(props) {
return (
// [...] Your Table component.
);
}
export default memo(Table);
And import it just as you do already. This will avoid re-rendering the table unless its props change.
See Here the Reasons of React Re-rendering:
If a Parent Component Re-renders, the Children re-render automatically. & because your counter state is on the Table Components parent, It will re-render every time Counter changes.
I am pretty new to React Hooks and could not understand how to place a useState() function inside a child component, both in a child's body and in a child's inner function, like this:
function Parent () {
const [counter, setCounter] = React.useState();
return (
<Child counter={counter} setCounter={setCounter}/>
)
}
function Child (props) {
const counter = props.counter;
const setCounter = props.setCounter;
React.useEffect(() => {
setCounter(0);
})
const increment = () => {
setCounter(1);
}
return (
<div> {counter} <button onClick={increment}>Increase Count</button> </div>
)
}
My code keeps reupdating the state to 0. What's wrong?
Please check my fiddle.
Thanks!
You missed the second argument in useEffect.
useEffect will trigger on every render if the second argument is not there and it will reset the changed value back to 0 again
React.useEffect(() => {
setCounter(0);
},[])
Full code sample
function Parent () {
const [counter, setCounter] = React.useState();
return (
<Child counter={counter} setCounter={setCounter}/>
)
}
function Child (props) {
const counter = props.counter;
const setCounter = props.setCounter;
React.useEffect(() => {
setCounter(0);
}, [])
const increment = () => {
setCounter(counter + 1);
}
return (
<div> {counter} <button onClick={increment}>Increase Count</button> </div>
)
}
Your code works: https://codesandbox.io/s/billowing-bird-p9xzp?file=/src/App.js
But there are several things to consider here:
1- you can initialize the state to 0 in const [counter, setCounter] = React.useState(0);
2- You dont need useEffect in child component
Your useEffect hook is missing a dependency array, so it is being called every time the child component renders. This means every time you click increment, the state gets set to 1, but then your useEffect immediately fires again and resets the count to 0. Fix this by placing an empty dependency array at the end of your useEffect (this means it will only fire once when the component is first mounted):
React.useEffect(() => {
setCounter(0);
}, [])
Also if you are want your button to actually increment the count by one every time you have to setCounter to 1 + the previous state, otherwise the counter will just stay at 1 every time it is clicked:
const increment = () => {
setCounter(prevCounter => prevCounter + 1);
}
Suppose I have the following code snippet (Please consider it as a pseudo code)
Parent.js
const [state,action]=useState(0);
return <View><Child1/><Button onPress={()=>action(1)}/></View>
Child1.js
const [state]=useState(Math.random());
return <Text>{state}</Text>
So my question is when I click the button in the parent will the Chil1 state change or not.
On my local machine it seems it changes.
The benefit of useState is that once a component is mounted, the state value does not change across re-renders until the update state function is called, passing a new state value.
Therefore, even though your parent component Button press state change triggers a rerender of the child, since the child component is only being rerendered and not unmounted/remounted, the initial state of Math.random() would remain the same.
See useState in React: A complete guide
I don't know what exact scenario is, but if you just set default state, the state will be memorized like Scenario 1
Scenario 1
In this way, the state of Child will not be changed even if Parent re-render
const Child = () => {
const [state] = useState(Math.random());
return <div>{state}</div>
}
const Parent = () => {
const [, action] = useState(true);
return (
<>
<button onClick={() => action(false)}>Not Change</button>
<Child />
</>
);
}
Scenario 2
Unless you remove it and then re-render Parent even if memorize all Child, that is
const Child = () => {
const [state] = useState(Math.random());
return <div>{state}</div>
}
const Parent = () => {
const [state, action] = useState(true);
useEffect(() => {
if (!state) action(true)
}, [state])
return (
<>
<button onClick={() => action(false)}>Change</button>
{state && <Child />}
</>
);
}
Scenario 3
By the may, if you don't use default state, in this way, it will be changed every rendering like that
const Child = () => {
return <div>{Math.random()}</div>
}
const Parent = () => {
const [, action] = useState(true);
return (
<>
<button onClick={() => action(prev => !prev)}>Change</button>
<Child />
</>
);
}
Scenario 4
If we don't want Child to re-render, we can try memo to memorize it
const Child = memo(() => {
return <div>{Math.random()}</div>
})
Scenario 5
However, when Child has props, perhaps we should invole useCallback or useMemo to make sure the values or memory addresses of props are "fixed" like constant, so that Child won't re-render
(We don't have to use useCallback or useMemo all the time, it doesn't much matter when there is no performance problem)
const Child = memo((props) => {
return <div {...props}>{Math.random()}</div>
})
const Parent = () => {
const [, action] = useState(true);
const style = useMemo(() => ({}), [])
const onOK = useCallback(() => alert(1), [])
return (
<>
<button onClick={() => action(prev => !prev)}>Change</button>
<Child className="test" style={style} onClick={onOK} />
</>
);
}
I'm aware that ref is a mutable container so it should not be listed in useEffect's dependencies, however ref.current could be a changing value.
When a ref is used to store a DOM element like <div ref={ref}>, and when I develop a custom hook that relies on that element, to suppose ref.current can change over time if a component returns conditionally like:
const Foo = ({inline}) => {
const ref = useRef(null);
return inline ? <span ref={ref} /> : <div ref={ref} />;
};
Is it safe that my custom effect receiving a ref object and use ref.current as a dependency?
const useFoo = ref => {
useEffect(
() => {
const element = ref.current;
// Maybe observe the resize of element
},
[ref.current]
);
};
I've read this comment saying ref should be used in useEffect, but I can't figure out any case where ref.current is changed but an effect will not trigger.
As that issue suggested, I should use a callback ref, but a ref as argument is very friendly to integrate multiple hooks:
const ref = useRef(null);
useFoo(ref);
useBar(ref);
While callback refs are harder to use since users are enforced to compose them:
const fooRef = useFoo();
const barRef = useBar();
const ref = element => {
fooRef(element);
barRef(element);
};
<div ref={ref} />
This is why I'm asking whether it is safe to use ref.current in useEffect.
It isn't safe because mutating the reference won't trigger a render, therefore, won't trigger the useEffect.
React Hook useEffect has an unnecessary dependency: 'ref.current'.
Either exclude it or remove the dependency array. Mutable values like
'ref.current' aren't valid dependencies because mutating them doesn't
re-render the component. (react-hooks/exhaustive-deps)
An anti-pattern example:
const Foo = () => {
const [, render] = useReducer(p => !p, false);
const ref = useRef(0);
const onClickRender = () => {
ref.current += 1;
render();
};
const onClickNoRender = () => {
ref.current += 1;
};
useEffect(() => {
console.log('ref changed');
}, [ref.current]);
return (
<>
<button onClick={onClickRender}>Render</button>
<button onClick={onClickNoRender}>No Render</button>
</>
);
};
A real life use case related to this pattern is when we want to have a persistent reference, even when the element unmounts.
Check the next example where we can't persist with element sizing when it unmounts. We will try to use useRef with useEffect combo as above, but it won't work.
// BAD EXAMPLE, SEE SOLUTION BELOW
const Component = () => {
const ref = useRef();
const [isMounted, toggle] = useReducer((p) => !p, true);
const [elementRect, setElementRect] = useState();
useEffect(() => {
console.log(ref.current);
setElementRect(ref.current?.getBoundingClientRect());
}, [ref.current]);
return (
<>
{isMounted && <div ref={ref}>Example</div>}
<button onClick={toggle}>Toggle</button>
<pre>{JSON.stringify(elementRect, null, 2)}</pre>
</>
);
};
Surprisingly, to fix it we need to handle the node directly while memoizing the function with useCallback:
// GOOD EXAMPLE
const Component = () => {
const [isMounted, toggle] = useReducer((p) => !p, true);
const [elementRect, setElementRect] = useState();
const handleRect = useCallback((node) => {
setElementRect(node?.getBoundingClientRect());
}, []);
return (
<>
{isMounted && <div ref={handleRect}>Example</div>}
<button onClick={toggle}>Toggle</button>
<pre>{JSON.stringify(elementRect, null, 2)}</pre>
</>
);
};
See another example in React Docs: How can I measure a DOM node?
Further reading and more examples see uses of useEffect
2021 answer:
This article explains the issue with using refs along with useEffect: Ref objects inside useEffect Hooks:
The useRef hook can be a trap for your custom hook, if you combine it with a useEffect that skips rendering. Your first instinct will be to add ref.current to the second argument of useEffect, so it will update once the ref changes.
But the ref isn’t updated till after your component has rendered — meaning, any useEffect that skips rendering, won’t see any changes to the ref before the next render pass.
Also as mentioned in this article, the official react docs have now been updated with the recommended approach (which is to use a callback instead of a ref + effect). See How can I measure a DOM node?:
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>
</>
);
}
I faced the same problem and I created a custom hook with Typescript and an official approach with ref callback. Hope that it will be helpful.
export const useRefHeightMeasure = <T extends HTMLElement>() => {
const [height, setHeight] = useState(0)
const refCallback = useCallback((node: T) => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height)
}
}, [])
return { height, refCallback }
}
I faced a similar problem wherein my ESLint complained about ref.current usage inside a useCallback. I added a custom hook to my project to circumvent this eslint warning. It toggles a variable to force re-computation of the useCallback whenever ref object changes.
import { RefObject, useCallback, useRef, useState } from "react";
/**
* This hook can be used when using ref inside useCallbacks
*
* Usage
* ```ts
* const [toggle, refCallback, myRef] = useRefWithCallback<HTMLSpanElement>();
* const onClick = useCallback(() => {
if (myRef.current) {
myRef.current.scrollIntoView({ behavior: "smooth" });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [toggle]);
return (<span ref={refCallback} />);
```
* #returns
*/
function useRefWithCallback<T extends HTMLSpanElement | HTMLDivElement | HTMLParagraphElement>(): [
boolean,
(node: any) => void,
RefObject<T>
] {
const ref = useRef<T | null>(null);
const [toggle, setToggle] = useState(false);
const refCallback = useCallback(node => {
ref.current = node;
setToggle(val => !val);
}, []);
return [toggle, refCallback, ref];
}
export default useRefWithCallback;
I've stopped using useRef and now just use useState once or twice:
const [myChart, setMyChart] = useState(null)
const [el, setEl] = useState(null)
useEffect(() => {
if (!el) {
return
}
// attach to element
const myChart = echarts.init(el)
setMyChart(myChart)
return () => {
myChart.dispose()
setMyChart(null)
}
}, [el])
useEffect(() => {
if (!myChart) {
return
}
// do things with attached object
myChart.setOption(... data ...)
}, [myChart, data])
return <div key='chart' ref={setEl} style={{ width: '100%', height: 1024 }} />
Useful for charting, auth and other non-react libraries, because it keeps an element ref and the initialized object around and can dispose of it directly as needed.
I'm now not sure why useRef exists in the first place...?
I have created a custom hook to scroll the element back into view when the component is scrolled.
export const useComponentIntoView = () => {
const ref = useRef();
const {current} = ref;
if (current) {
window.scrollTo(0, current.offsetTop );
}
return ref;
}
Now i am making use of this in a functional component like
<div ref={useComponentIntoView()}>
So for the first time the current always comes null, i understand that the component is still not mounted so the value is null . but what can we do to get this values always in my custom hook as only for the first navigation the component scroll doesn't work . Is there any work around to this problem .
We need to read the ref from useEffect, when it has already been assigned. To call it only on mount, we pass an empty array of dependencies:
const MyComponent = props => {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
window.scrollTo(0, ref.current.offsetTop);
}
}, []);
return <div ref={ref} />;
};
In order to have this functionality out of the component, in its own Hook, we can do it this way:
const useComponentIntoView = () => {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
window.scrollTo(0, ref.current.offsetTop);
}
}, []);
return ref;
};
const MyComponent = props => {
const ref = useComponentIntoView();
return <div ref={ref} />;
};
We could also run the useEffect hook after a certain change. In this case we would need to pass to its array of dependencies, a variable that belongs to a state. This variable can belong to the same Component or an ancestor one. For example:
const MyComponent = props => {
const [counter, setCounter] = useState(0);
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
window.scrollTo(0, ref.current.offsetTop);
}
}, [counter]);
return (
<div ref={ref}>
<button onClick={() => setCounter(counter => counter + 1)}>
Click me
</button>
</div>
);
};
In the above example each time the button is clicked it updates the counter state. This update triggers a new render and, as the counter value changed since the last time useEffect was called, it runs the useEffect callback.
As you mention, ref.current is null until after the component is mounted. This is where you can use useEffect - which will fire after the component is mounted, i.e.:
const useComponentIntoView = () => {
const ref = useRef();
useEffect(() => {
if (ref.current) {
window.scrollTo(0, ref.current.offsetTop );
}
});
return ref;
}