App.js
import React from "react";
import CounterOne from "./CounterOne";
function App() {
const [display, setDisplay] = React.useState(true);
return (
<>
{display && <CounterOne />}
<button
onClick={() => {
setDisplay(!display);
}}
>
display
</button>
</>
);
}
export default App;
CounterOne.js
import React, { useState } from "react";
function CounterOne() {
const [count, setCount] = useState(0);
React.useEffect(() => {
console.log("component did update");
return () => {
console.log("component will unmount");
};
}, [count]);
return (
<div>
<p>Count is {count}</p>
<button
onClick={() => {
setCount(0);
}}
>
reset
</button>
<button
onClick={() => {
setCount(count + 5);
}}
>
Add 5
</button>
<button
onClick={() => {
setCount(count - 1);
}}
>
Sub 1
</button>
</div>
);
}
export default CounterOne;
When i hit the Add 5 or sub 1 button, the component re-render then in browser console it prints
component will unmount
component did update
i am confuse why will unmount part execute when the update of state taking place
Because when you change a state in a component, React will re-render the whole component. So the old component will be unmounted first, triggering the return callback in useEffect. When a new component is mounted, the logic inside useEffect will be triggered again, hence you see the "component did update" after the "component will unmount" message. More on useEffect here What is the expected return of `useEffect` used for?.
Related
I've created a basic React component as a sandbox. I added a doSomething() function to the component which simply displays an alert on useEffect(). However, the alert is getting called in an infinite loop. So obviously, there's something wrong with the design. Any idea what the design issue is with this component which is causing the infinite loop? What steps would you recommend to redesign this component in order to fix that issue?
import {Repository} from '../data/Repository';
import { useEffect , useState} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { decrement, increment } from '../features/counter/counterSlice';
import { setIsValid } from '../features/userManagement/userManagementSlice';
export const UserManagement = () => {
const [userTestData, setUserTestData] = useState([]);
const [userCount, setUserCount] = useState(0);
const count = useSelector((state) => state.counter.value)
const isValid2 = useSelector((state) => state.userManagement.isValid2)
const dispatch = useDispatch()
const doSomething = () => {
alert('about to do something');
}
useEffect(() => {
doSomething();
setUserTestData(Repository.getUserTestData());
})
useEffect(() => {
setUserCount(userTestData.length);
}, [userTestData])
//debugger;
return(
<>
<div>
{
userTestData.map((x) => {
return <div key={x.Id}>{x.FirstName}</div>
})
}
</div>
<div>there are {userCount} users</div>
<div>
{/* PARENT INPUT VALIDATION */}
<div style={{height:100}}>
<button onClick={() => dispatch(setIsValid())}>
TOGGLE VALIDATION
</button>
{isValid2 ? 'true' : 'false'}
</div>
{/* COUNTER */}
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
</>
)
}
The issue here.
useEffect(() => {
doSomething();
setUserTestData(Repository.getUserTestData());
})
You didn't add array of dependences to useEffect, so it's callback will be excuted on each render of the component, and as you set a State by setUserTestData, so the component will still re-render in infinite loop.
To fix.
useEffect(() => {
doSomething();
setUserTestData(Repository.getUserTestData());
}, [])
This question already has answers here:
Make React useEffect hook not run on initial render
(16 answers)
Closed 9 months ago.
I want to prevent calling my useEffect on the first mount.
how can I do this?
I want the best practice.
I don't want to use if condition
import { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(5);
useEffect(() => {
console.log(count);
}, [count]);
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount(count + 1)}> click to increase </button>
</div>
);
};
export default Counter;
I found it.
It should be handled by useLayoutEffect and useRef
import { useState, useEffect, useLayoutEffect, useRef } from "react";
const Counter = () => {
const [count, setCount] = useState(5);
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
console.log(count);
});
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount(count + 1)}> click to increase </button>
</div>
);
};
export default Counter;
how we can observe if a JSX element mounted or not. for example I have a simple component with useEffect on. it inside of my App.js I can mount and unmount my component and the useEffect inside of that component will log if it is mounted or unmounted.
but I wonder if there is way to that with JSX elements. for example , can we implement that for an h2 tag inside of an App.js without creating component ?
App.js
import React, { useState } from "react";
import "./App.css";
import Mycomponent from "./Mycomponent";
const App = () => {
const [mount, setMount] = useState(true);
return (
<div>
<b>Mounting and Unmounting</b>
<button
onClick={() => {
setMount(!mount);
}}
>
{mount ? "click to unmount" : "click to mount"}
</button>
{mount && <Mycomponent />}
</div>
);
};
export default App;
Mycomponent.js :
import React, { useEffect } from "react";
const Mycomponent = () => {
useEffect(() => {
console.log("mounted");
return () => {
console.log("unmounted");
};
}, []);
return (
<div>
<h1>component mounted</h1>
</div>
);
};
export default Mycomponent;
I think you can use callback refs for that:
export default function App() {
const [counter, setCounter] = React.useState(0);
const measuredRef = (node) => {
if (node == null) {
console.log('I was removed');
} else {
console.log('I was mounted');
}
};
return (
<div
onClick={() => {
setCounter(counter + 1);
}}
>
{counter % 2 == 0 && <h1 ref={measuredRef}>Hello, world</h1>}
<p>Start editing to see some magic happen :)</p>
</div>
);
}
There is a somewhat related example in the docs about that:
In this example, the callback ref will be called only when the
component mounts and unmounts, since the rendered <h1> component stays
present throughout any rerenders.
export const App2 =()=>{
const [counter,setCounter]=useState(0)
setTimeout(()=>setCounter(counter+1),1000)
return(
<div> {counter}
<button onClick={()=>{setCounter(counter+1)}}> Plus</button>
</div>
)
}
I recorded a video and uploaded it to youtube. I have no idea why this instability happens and switching tabs made it normal again. Anyone to enlighten? I just made a slight modification to this code
setTimeout causes an effect, so we should put it inside a useEffect() hook. Also, since setCounter depends on the previous counter state, we can pass an updater function to setCounter which receives the previous state value.
import React, { useCallback, useEffect } from "react";
export const App2 = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => {
setCounter((counter) => counter + 1);
}, []);
useEffect(() => {
const id = setTimeout(increment, 1000);
return () => clearTimeout(id);
}, [increment]);
return (
<div>
{" "}
{counter}
<button onClick={increment}> Plus</button>
</div>
);
};
import React, { useState, useEffect } from 'react';
const Test = () => {
const [ count, setCount ] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count up!</button>
<ChildComponent />
</div>
)
}
const ChildComponent = () => {
useEffect(() => {
console.log('render!');
return () => console.log('unmounted...');
});
return (
<div>children</div>
)
};
export default Test;
Press the "Count up!" button.
log was output.
unmounted...
render!
ChildComponent is unchanged.
But rendered again.
why?
And how to prevent re-rendering?
thanks.
it re render because it's nested in a component that re render to prevent a nested component re render use React.memo()
const Test = () => {
const [ count, setCount ] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count up! {count} </button>
<ChildComponent />
</div>
)
}
const ChildComponent = React.memo(() => {
useEffect(() => {
console.log('render!');
return () => console.log('unmounted...');
});
return (
<div>children</div>
)
});
export default Test;
you can read more about memo
Because the parent state is updated, the whole component will be re-rendered, include its children too, no matter if its children are changed or unchanged. You can use React.memo() to prevent your components from re-rendering. useCallback and useMemo can be used to memoize too.
See more here: How to memoize in React.