function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count; });
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
In the above snippet from the React Hoks FAQS useRef is used for saving the count. However whenever render is called, will not the prevCount be set to the current count as the useEffect will be called on each render, so how are count and prevCount different ?
That's because the useEffect callback function will be called after the rendering time of Counter Component has finished. and that's why prevCount is always one tick behind.
one point to consider is the change in in a React ref won't cause a rerender in React Component only change in state and props will cause a rerender.
see the working example here: https://codesandbox.io/s/lucid-butterfly-y66tc?file=/src/App.js
export default function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
console.log('me socond')
});
console.log('me first')
const prevCount = prevCountRef.current;
return (
<h1>
Now: {count}, before: {prevCount}
<div onClick={() => setCount((v) => v + 1)}>click me</div>
</h1>
);
}
you see me first then me second in the console
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.
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]);
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);
}
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;
}
Why and when should I useEffect if I have no deps?
What is the difference between (from React docs):
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
And without useEffect?
function usePrevious(value) {
const ref = useRef();
ref.current = value;
return ref.current;
}
The difference in the two methods is that useEffect is run after the render cycle is complete and hence ref.current will hold the previous values whereas in the second method, your ref.current will be updated immediately and hence the previous will always be equal to the current value
Sample demo
const {useRef, useEffect, useState} = React;
function usePreviousWithEffect(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function usePrevious(value) {
const ref = useRef();
ref.current = value;
return ref.current;
}
const App = () => {
const [count, setCount] = useState(0);
const previousWithEffect = usePreviousWithEffect(count);
const previous = usePrevious(count);
return (
<div>
<div>Count: {count}</div>
<div>Prev Count with Effect: {previousWithEffect}</div>
<div>Prev Count without Effect: {previous}</div>
<button type="button" onClick={() => setCount(count => count + 1)}>Increment</button>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>
Also to answer your question, you pass useEffect without dependency when you want to perform some operation on each render. You however cannot set state or perform an action that will cause a re-render otherwise your app will go in a loop