useEffect dependency is not updating the useEffect - javascript

I have a useEffect hook that is not updating after changing the dependency.
The dependency is a state variable passed through props. Included below is what the code looks like:
const MyComponent = ({resource}) => {
// runs after changing resource state
console.log(resource)
useEffect(() => {
setLoading(true);
// doesnt run after changing resource state
console.log(resource)
setVariable(setDefaultValue());
}, [resource]);
}
MyComponent is being rendered for one of two states 'option1' or 'option2' the component renders differently depending on the state. I call the component like so:
const [resource, setResource] = useState('option1');
const handleChange = (e) => {
setResource(e.target.value);
};
return (
<MyComponent resource={resource} />
)
I don't understand why the useEffect isn't running after resource state is changed. The console.log on the outside of the useEffect show the change state, but the console.log inside of the useffect isn't run after changing state.

Oh, you can change code the following above try:
//Parent Component
const [resource, setResource] = useState('option1');
const [variable,setVariable] = useState();
const [loading,setLoading] = useState(false);
useEffech(()=>{
let fetch_api = true;
setLoading(true);
fetch(URL,options).then(res=>res.json())
.then(res=>{
if(fetch_api){
setVariable(setDefaultValue());
setLoading(false);
}
});
return ()=>{
//remove memory
fetch_api = false;
}
},[resource]);
const handleChange = (e) => {
setResource(e.target.value);
};
return (
<MyComponent variable={variable} />
)
//Child Component
const MyComponent=({variable})=>{
return <>
</>
}

Related

How to stop initial render for useEffect hook

Earlier I had a Class component, so I didn't face any issues while using lifecycle methods, but after converting to useEffect hooks, I am facing the initial render issue which I don't want to happen.
Class
componentDidMount() {
this.setState({
patchVal:this.props.patchTaskVal,
startTime:this.props.patchStartTime,
setEndTime:this.props.patchEndTime
})
}
componentDidUpdate(prevProps) {
if (prevProps.patchTaskVal !== this.props.patchTaskVal) {
this.callValidation()
}
if (prevProps.closeTask !== this.props.closeTask) {
this.setState({
showValue:false,
progressValue:[],
startTime:new Date(),
setEndTime:""
})
}
if (prevProps.patchStartTime !== this.props.patchStartTime || prevProps.endTime !== this.props.endTime && this.props.endTime !== "") {
this.setState({
startTime:this.props.patchStartTime,
setEndTime:parseInt(this.props.endTime)
})
}
}
Functional
const [patchTaskVal, setPatchTaskVal]=useState(/*initial value */)
const [startTime, setStartTime]=useState()
const [endTime, setEndTime] = useState()
**// I want only this useEffect to run on the initial render**
useEffect(() => {
setPatchTaskVal(props.patchTaskVal)
...//set other states
}, [])
useEffect(() => {
callValidation()
}, [props.patchTaskVal])
useEffect(() => {
//setShowValue...
}, [props.closeTask])
useEffect(() => {
if (props.endTime != "") {
// set states...
}
}, [props.patchStartTime,props.endTime])
Here I am facing an issue where all the useEffects are running on the initial render, Please suggest a solution for this so that only the first useEffect will run on the initial render and all other useEffects will run according to its dependency prop values.
You basically need a ref which will tell you whether this is the first render on not. Refs values persist over rerenders. You can start with a truthy value and toggle it to false after the first render (using a useEffect with an empty array[]). Based on that you can run your desired code.
You can also put the whole thing in a custom hook:
import { useEffect, useRef } from "react";
const useOnUpdate = (callback, deps) => {
const isFirst = useRef(true);
useEffect(() => {
if (!isFirst.current) {
callback();
}
}, deps);
useEffect(() => {
isFirst.current = false;
}, []);
};
export default useOnUpdate;
You can call this hook in your component like :
useOnUpdate(() => {
console.log(prop);
}, [prop]);
In the hook:
After the initial render, both useEffects run. But when the first effect runs the value of the isFirst.current is true. So the callback is not called. The second useEffect also runs and sets isFirst.current to false.
Now in subsequent renders only the first useEffect run (when dependencies change), and isFirst.current is false now so callback is executed.
The order of the two useEffects is very important here. Otherwise, in the useEffect with deps, isFirst.current will be true even after the first render.
Link
If you compare the functional and the class component you can notice that there is one part missing - previous props.
Functional component does not have previous props in scope, but you can save them yourself with a small trick: save them to reference so it will not impact you render cycle.
Since now you have the previous props and the current props you can apply the same logic you did for class component.
import React, { useRef, useEffect, useState } from "react";
default function App() {
const [input, setInput] = useState("");
const [commitInput, setCommitInput] = useState("");
return (
<>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button onClick={() => setCommitInput(input)}>apply</button>
<Child test={commitInput} />
</>
);
}
function Child(props) {
const prev = useRef(props.test);
useEffect(() => {
if (prev.current !== props.test) {
alert("only when changes");
}
}, [props.test]);
return <div>{props.test}</div>;
}
try this...
let init = true;
useEffect( ()=>{
if(init) {
setPatchTaskVal(props.patchTaskVal)
init = false;
...//set other states}
}, [])
useEffect( ()=> {
!init && callValidation()
},[props.patchTaskVal])
useEffect( ()=>{
//!init && setShowValue...
},[props.closeTask])
useEffect( ()=>{
if(props.endTime!="" && !init){
// set states...
}
},[props.patchStartTime,props.endTime])
Hope my understanding is right about your question.
Why not just add a if statement to check the state is not undefined or default value
useEffect( ()=> {
if (props.patchTaskVal) {
callValidation()
}
},[props.patchTaskVal])
useEffect( ()=>{
if (props.closeTask) {
//setShowValue...
}
},[props.closeTask])
useEffect( ()=>{
if(props.patchStartTime){
// set states...
}
if(props.endTime){
// set states...
}
},[props.patchStartTime,props.endTime]
And according your class component,
this.setState({
patchVal:this.props.patchTaskVal,
startTime:this.props.patchStartTime,
setEndTime:this.props.patchEndTime
})
The function component should map props to component's state. Like this
const [patchTaskVal, setPatchTaskVal]=useState(props.patchTaskVal)
const [startTime, setStartTime]=useState(props.patchStartTime)
const [endTime, setEndTime] = useState(props.patchEndTime)

Infinite console log in react js component

I have made two simple straight forward component is React, used a open source API to test API integration. React is showing this weird behavior of infinite console logs. I don't understand the issue. I'm using the fetch function for making API calls and functional component.
App component:
function App() {
const [characters, setCharac] = useState([])
const URL = "https://swapi.dev/api/";
fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
}))
return (
<div className="App">
{characters.map(charac => {
return <Character {...charac} />
})}
</div>
);
}
Character component:
const Character = (props) => {
console.log(props);
return (
<div key={props.name}>
<h1>{props.name}</h1>
<p>{props.height}</p>
</div>
);
}
console.log('Test'); in App component and console.log(props); in Character component are being executed infinitely.
This is the render method
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Your components are rendering multiple times because your state is changed every time you fetch data (because of setState).
Try creating a function fetchData(). Make this function async as well to wait for data to be retrieved.
const fetchData = async () => {
const result = await fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
return data;
}));
return result;
}
and then use it inside useEffect (Read more about useEffects: React hooks: What/Why `useEffect`?)
useEffect(() => {
fetchData();
}, []);
Note the usage of [] in useEffect. The data will be fetched only once when you load the component.
Try wrapping it in a useEffect
e.g.
useEffect(()=>{
fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
}))
},[])
otherwise every time the state is set it is firing off the fetch again because a re-render is being triggered.
Because you fetch some data, update the state, which causes a re-render, which does another fetch, updates the state, which causes another render...etc.
Call your fetch function from inside a useEffect with an empty dependency array so that it only gets called once when the component is initially rendered.
Note 1: you can't immediately log the state after setting it as setting the state is an async process. You can, however, use another useEffect to watch for changes in the state, and log its updated value.
Note 2: I've used async/await in this example as the syntax is a little cleaner.
// Fetch the data and set the state
async function getData(endpoint) {
const json = await fetch(`${endpoint}/people`);
const data = await response.json();
setCharac(data.results);
}
// Call `getData` when the component initially renders
useEffect(() => {
const endpoint = 'https://swapi.dev/api';
getData(endpoint);
}, []);
// Watch for a change in the character state, and log it
useEffect(() => console.log(characters), [characters]);
You can do something like this:
import React, { useState, useEffect, useCallback } from "react";
const Character = (props) => {
console.log(props);
return (
<div key={props.name}>
<h1>{props.name}</h1>
<p>{props.height}</p>
</div>
);
};
export default function App() {
const [characters, setCharac] = useState([]);
const makeFetch = useCallback(() => {
const URL = "https://swapi.dev/api/";
fetch(URL + "people").then((response) =>
response.json().then((data) => {
setCharac(data.results);
console.log("Test");
})
);
}, []);
useEffect(() => {
makeFetch();
}, []);
return (
<div className="App">
{characters.map((charac) => {
return <Character {...charac} />;
})}
</div>
);
}

Mapping on useState object

I was trying to convert my class based component to functional style. I have this code:
const [foo, setFoo] = useState(null);
const [roomList, setRoomList] = useState([]);
useEffect(() => {
setRoomList(props.onFetchRooms(props.token));
}, [props]);
let roomLists = <Spinner />;
if (!props.loading) {
roomLists = roomList.map(room => <Room key={room._id} roomName={room.name} />);
}
Previously I had:
class Rooms extends Component {
state = {
foo: null
};
componentDidMount() {
this.props.onFetchRooms(this.props.token);
}
render() {
let roomList = <Spinner />;
if (!this.props.loading) {
roomList = this.props.rooms.map(room => <Room key={room._id} roomName={room.name} />);
}
The onFetchRooms is a function I am using from Redux in mapDispatchToProps. And rooms is also coming from the Redux store in mapStateToProps With the above new code, I get Cannot read property 'map' of undefined. What am I doing wrong please?
I have also tried without using state:
useEffect(() => {
props.onFetchRooms(props.token);
}, [props]);
let roomList = <Spinner />;
if (!props.loading) {
roomList = props.rooms.map(room => <Room key={room._id} roomName={room.name} />);
}
But that goes into infinite loop.
useEffect set dependency array to []
try this
useEffect(() => {
props.onFetchRooms(props.token);
}, []);
I think the props object will change for each rerender (that's why you get an infinite loop).
You should declare each variables individually into the array :
const {onFetchRooms, token} = props;
useEffect(() => onFetchRooms(token), [onFetchRooms, token]);
It seems like you're probably starting the "Functional" version of your component like this:
const Rooms = (props) => {
...
}
Instead, why don't you destructure props like this:
const Rooms = ({onFetchRooms, rooms, token}) => {
...
}
Now you can do this in your useEffect dependency array:
useEffect(() => {
onFetchRooms(token);
}, [onFetchRooms, token]);
This should make the infinite loop stop happening.
Note: Sometimes to get rid of the "React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array." error, you "can" use:
// eslint-disable-next-line react-hooks/exhaustive-deps
For example, if you only want to add token in your dependency array and not worry about onFetchRooms, a function that will never change (i assume)

How to fix React Redux and React Hook useEffect has a missing dependency: 'dispatch'

React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
I use useDispatch() Hook from React Redux on a functional component like this:
const Component = () => {
const dispatch = useDispatch();
const userName = useSelect(state => state.user.name);
useEffect(() => {
dispatch(getUserInformation());
}, [userId]);
return (
<div>Hello {userName}</div>
);
};
export default Component;
How to remove this warning without removing the dependency array react-hooks/exhaustive-deps which can be useful to avoid other errors.
To avoid that warning simply add dispatch to the dependency array. That will not invoke re-renders because dispatch value will not change.
const Component = () => {
const dispatch = useDispatch();
const userName = useSelect(state => state.user.name);
useEffect(() => {
dispatch(getUserInformation());
}, [userId, dispatch]);
return (
<div>Hello {userName}</div>
);
};
export default Component;
Simply add dispatch to your dependency array or make the dependency array empty.
First Case:
const Component = () => {
const dispatch = useDispatch();
const userName = useSelect(state => state.user.name);
useEffect(() => {
dispatch(getUserInformation());
}, [userId, dispatch]);
return (
<div>Hello {userName}</div>
);
};
export default Component;
Second Case:
const Component = () => {
const dispatch = useDispatch();
const userName = useSelect(state => state.user.name);
useEffect(() => {
dispatch(getUserInformation());
}, []);
return (
<div>Hello {userName}</div>
);
};
export default Component;
By adding these dependencies, your useEffect may cause re-rendering or not re-render at all. It all depends on your data and the context in which you use it.
Anyways, if you do not wish to follow both the above methods then //ts-ignore can work but I will not recommend this as this may create bugs in the long run.

React hooks - onClick and useEffect

React Hooks is not updating to use the prop passed down and then stored. Usually I would resolve useState issues by calling functionality inside useEffect but in this case I need to update after a click event:
const [currentLayer, setCurrentLayer] = useState();
useEffect(() => {
console.log(props.currentLayer) // props.currentLayer is defined
setCurrentLayer(props.currentLayer);
}, [props.currentLayer]);
useEffect(() => {
console.log(currentLayer); // currentLayer state is defined
}, [currentLayer]);
/*
* Called when the timeline product is clicked
*/
const clickHandler = e => {
console.log(currentLayer); // currentLayer state is undefined
currentLayer.getSource().updateParams({ TIME: e.time });
};
return <Timeline options={props.timelineOptions} items={items} clickHandler={e => clickHandler(e)} />;
When clickHandler is called currentLayer state is undefined despite having been set earlier.
What is the best way to combine useEffect and the clickHandler, or am I missing something else?
import React from 'react'
const Component = (props) => {
// ... your other logic
const [currentLayer, setCurrentLayer] = useState(props.currentLayer)
const clickHandler = e => {
currentLayer.getSource().updateParams({ TIME: e.time });
}
return <Timeline options={props.timelineOptions} items={items} clickHandler={clickHandler} />
}
I don't see a reason why you need the useEffect hook. In fact, you should not set the currentLayer props in this component but rather use it as it is. This is so that when there is a change in the props.currentLayer, this component will also re-render.
const clickHandler = e => {
props.currentLayer.getSource().updateParams({ TIME: e.time });
};

Categories