Why useEffect does not behave like what i expect? - reactjs

I was wondering Why this isn't an infinite loop?
I set the state inside of the useEffect hook and that should re-render the component and useEffect will be ran again and again and agian...
why this isn't what i expected?
import React, { useState, useEffect } from "react";
import axios from "axios";
const App = () => {
const [data, setData] = useState();
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Checking...");
setData(r.data[0]);
});
});
return (
<div>
<h1>{data}</h1>
</div>
);
};
export default App;

useEffect also accepts an array of dependencies i.e., useEffect will get executed when any values inside the array changes. So, if you want useEffect to be called whenever data changes, do this:
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Hello world");
setData(r.data[0]);
});
}, [data]);

The useEffect hook needs a second parameter, the dependency array.
When this dependency array is empty it will have the same behavior as the componentDidMount
If you add some dependencies to your array as data this useEffect will be executed every time that the data state changes.
So, if you want to fetch the data when the component loads you must do something like this.
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Hello world");
setData(r.data[0]);
});
}, []);

Related

React useState is initially unchanged

Take this piece of code:
import React from 'react';
import { useState, useEffect } from 'react'
export function App() {
let [isAdmin, setAdmin] = useState(false)
const checkIfAdmin = async() => {
setAdmin(true)
}
useEffect(() => {
checkIfAdmin()
}, []);
console.log(isAdmin)
return (
<div className='App'>
<h1>test</h1>
</div>
);
}
When console logging isAdmin, it comes out as false initially, but when checked again (such as in an onClick() event), it comes out as true. Why does it take 2 checks to finally output the desired result? How can I make it so that in checkIfAdmin the changes immediately take place, and isAdmin comes out as true on the first time?
Passing a function to setAdmin() to toggle in between states will immediately update the state variable.
const checkIfAdmin = () => {
setAdmin(isAdmin => !isAdmin)
}
You should be getting 2 console.logs, one that returns false and another that returns true. This is due to the way useEffect, and setState work.
When useEffect is used with an empty array as the second arg, it only runs once when the component mounts. And on the first mount of your component, isAdmin is initialized to false
useEffect(() => {
checkIfAdmin()
}, []);
Once setAdmin(true) executes, it updates the dom, causing the component to render for a second time, with the isAdmin value being true
You can visualize how the dom is being updated by adding another console.log() in your useEffect.
useEffect(() => {
console.log('component is mounting...')
checkIfAdmin();
}, []);

Some insight on React behavior would be appreciated. Issue with custom hook

I have a small react example that is puzzling me.
You can run the code at codesanbox
The code is very simple, there is a component TestComponent that displays 'wowzers', and calls a custom hook useCustomHook.
useCustomHook does three things. It declares a useState [list, set_list], then a useEffect based on list to inform when there is a change, and then a 1 second timer that will update the list.
What I expected was that the useEffect would initially run and then a second later it would run again when the list is updated. However, it updates every second. Not only this, but the useCustomHook is being re-entered from TestComponent starting the process all over again, and this happens repeatedly.
Can someone explain please why, when the list is updated with set_list in the timer callback that this causes TestComponent to call useCustomHook again (and again)
I thought I understood that principles of using react and have developed numerous but small applications. This simple example is really throwing me off. Any help would be appreciated.
The code in index.js is a follows.
import { useState, useEffect } from "react";
import React from "react";
import ReactDOM from "react-dom";
const useCustomHook = () => {
const [list, set_list] = useState([]);
useEffect(() => console.log("useEffect", "list updated"), [list]);
setTimeout(() => set_list(() => []), 1000);
};
const TestComponent = () => {
const hook = useCustomHook();
return <span>wowzers</span>;
};
class App extends React.Component {
render() {
return <TestComponent />;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
setTimeout is right in the hook body... each time the hook is called it runs the entire body. The timeout is enqueueing a state update which triggers a rerender. Rerenders run all the hooks again.
const useCustomHook = () => {
const [list, set_list] = useState([]);
useEffect(() => console.log("useEffect", "list updated"), [list]);
setTimeout(() => set_list(() => []), 1000); // <-- unintentional side-effect!
};
What I expected was that the useEffect would initially run and then a
second later it would run again when the list is updated.
Place the setTimeout in a mounting useEffect, i.e. empty dependency array, so it runs exactly once after the initial mounting render cycle.
const useCustomHook = () => {
const [list, set_list] = useState([]);
useEffect(() => console.log("useEffect", "list updated"), [list]);
useEffect(() => {
setTimeout(() => set_list(() => []), 1000);
}, []);
};
might skiped some step and not exactly how it gos, surely it will keep updating and the sequenced steps can help you some
import { useState, useEffect } from "react";
import React from "react";
import ReactDOM from "react-dom";
// called every render, because function component is called every render
const useCustomHook = () => {
// 4 first useState, list=[] by default, alloc memory X
// 9,15... useState, list=[] changed by set_list, loaded from memory X
const [list, set_list] = useState([]);
// 5 register effect, callback called every time list change
// 10,16... effect alread registered, skip
useEffect(() =>
// 12,18... list updated because []!==[]
console.log("useEffect", "list updated"), [list]);
// 6,11,17... settimeout, callback in 1 sec
setTimeout(() =>
// 7,13,19... update memory X, triggering rerender
set_list(() => []), 1000);
};
// function component is called every render
const TestComponent = () => {
// 3,8,14...
const hook = useCustomHook();
return <span>wowzers</span>;
};
class App extends React.Component {
render() {
return <TestComponent />; //2
}
}
ReactDOM.render(<App />, document.getElementById("root")); // 1

useEffect causing infinite loop or getting errors

I am trying to learn React hooks. I'm creating a simple news app that leverages the NY times api.
When I leave the dependency empty, nothing loads and when I use data as the dependency it goes into an infinite loop.
When I use isLoading, it works but then I receive an error "localhost/:1 Unchecked runtime.lastError: The message port closed before a response was received." and "localhost/:1 Error handling response: TypeError: Cannot read property 'level' of undefined"
main.js
import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const fetchData = async () => {
const result = await nyTimesApi();
setData(result);
setIsLoading(false);
console.log(data.results);
};
useEffect(() => {
fetchData();
}, [isLoading]);
return <div className="main">work</div>;
};
export default Main;
I am also receiving a warning, when using isLoading, in the terminal saying "React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps"
What am I doing wrong?
The infinite loop is caused by the combination of using setData(result) and [data]:
The component mounts and the useEffect is run.
setData(result) will asynchronously update the data value and trigger a rerender.
During the rerender the useEffect will be run again as data will not successfully complete the reference comparison.
Repeat 2 to 3.
The warning "React Hook useEffect has a missing dependency" is self explanatory to an extent.
Making use of an external (to the useEffect) variable that is not included in the dependency array may mean that the value of the variable changes and the useEffect will not be retriggered or that the value may not be the expected value.
Below is a an example of how the original snippet might be fixed:
import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
// Create function inside useEffect so that the function is only
// created everytime the useEffect runs and not every render.
const fetchData = async () => {
const result = await nyTimesApi();
setData(result);
setIsLoading(false);
// setData will update state asynchronously.
// Log the value stored instead.
console.log(result.results);
};
//Run data fetching function.
fetchData();
},
// Both of these are set functions created by useState and will
// not change for the life of the component, but adding them to
// the dependency array will make your linter happy.
// Do not need to check isLoading as it is always true on component
// mount.
[setData, setIsLoading]);
return <div className="main">work</div>;
};
export default Main;
The second argument to useEffect is an array of variables, which trigger the function within useEffect to be called every time they change.
You have [isLoading] as the second argument to useEffect and update the value of this within fetchData(). This is going to cause the useEffect trigger to happen again and again and again.
If you only want to have useEffect call once (in a similar way to ComponentDidMount in class-based components), then you need to specify an empty array as the second argument.
useEffect(() => {
fetchData();
}, []);

useState setter not updating state when called in useEffect

Pretty much what it says on the title. When I console.log(repos) it returns an empty array. Why is the repos state not updating?
import React, { useEffect, useState } from "react";
import axios from "axios";
export default () => {
const [repos, setRepos] = useState([]);
useEffect(() => {
(async () => {
try {
let repo_lists = await axios.get(
"https://api.github.com/users/Coddielam/repos"
// { params: { sort: "created" } }
);
setRepos(repo_lists.data.slice(1).slice(-10));
console.log(repo_lists.data.slice(1).slice(-10));
console.log(repos);
} catch (err) {
setRepos([
"Something went wrong while fetching GitHub Api./nPlease use the link below to view my git repos instead ",
]);
}
})();
}, []);
return (
<div className="content">
<h2>View my recent git repos:</h2>
<ul>
...
</ul>
</div>
);
};
Answer is very simple. Your useState is updating .. believe me. The reason why you don't see it when you console.log() is because SetRespos is an asynchronous function.
Basically when you declare a function to update you useState value, react will use it as an async function
EXAMPLE
const [example, setExample] = useState('');
useEffect(() => {
setExample('Hello');
console.log('I'm coming first'); // This will be executed first
console.log(example); // This will come after this
}, [])
The output will be :
I'm coming first
// Blank Line
But still your useState will update after this. If you want to see that do this :
useEffect(() => {
console.log(respose); // This will give you the value
}, [respos])
I'm using a separate useEffect to console.log() the value. In the [] (dependency array) we pass respos which simply means that the useEffect will run every time the value of respos changes.
Read more about useStates and useEffects in react's documentation
State updates are async. You will only see them reflected on the next render. If you console log the state immediately after calling setState it will always log the current state, not the future state.
You can log the state in an effect every time it changes and you will see it changing:
useEffect(() => console.log(repos), [repos]);
This effect will be called after the state update has been applied.

What is the best way to use redux action in useEffect?

I have a React Component like shown bellow (some parts are ommited) and I'm using redux for state management. The getRecentSheets action contains an AJAX request and dispatches the response to redux which updates state.sheets.recentSheets with the response's data.
All this works as expected, but on building it throws warning about useEffect has a missing dependency: 'getRecentSheets'. But if I add getRecentSheets to useEffect's dependency array it starts to rerun indefinitely and thus freezes the app.
I've read React documentation about the useEffect hook https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies but it doesn't provide a good example for such usecase. I suppose it is something with useCallback or react-redux useDispatch, but without examples I'm not sure how to implement it.
Can someone please tell me what the most concise and idiomatic way to use redux action in useEffect would be and how to avoid warnings about missing dependencies?
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import SheetList from '../components/sheets/SheetList';
import { getRecentSheets } from '../store/actions/sheetsActions';
const mapStateToProps = (state) => {
return {
recentSheets: state.sheets.recentSheets,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getRecentSheets: () => dispatch(getRecentSheets()),
}
}
const Home = (props) => {
const {recentSheets, getRecentSheets} = props;
useEffect(() => {
getRecentSheets();
}, [])
return <SheetList sheets={ recentSheets } />
};
export default connect(mapStateToProps, mapDispatchToProps) (Home);
After all, it seems that correct way will be as follows:
// ...
import { useDispatch } from 'react-redux';
import { getRecentSheets } from '../store/actions/sheetsActions';
const Home = props => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getRecentSheets());
}, [dispatch])
// ...
};
This way it doesn't complain about getRecentSheets missing in dependencies array. As I understood from reading React doc on hooks that's because it's not defined inside the component. Though I'm new to frontend and I hope I didn't mess something up here.
Passing an empty array in your hook tells React your hook function will not have any dependent values from either props or state.
useEffect(() => {
getRecentSheets();
}, [])
The infinite loop arises when you declare the dispatcher as a dependency on the hook. When the component is initialized, props.recentSheets hasn't been set, and will rerender once you make your AJAX call.
useEffect(() => {
getRecentSheets();
}, [getRecentSheets])
You could try something like this:
const Home = ({recentSheets}) => {
const getRecentSheetsCallback = useCallback(() => {
getRecentSheets();
})
useEffect(() => {
getRecentSheetsCallback();
}, [recentSheets]) // We only run this effect again if recentSheets changes
return <SheetList sheets={ recentSheets } />
};
No matter how many times Homes re-renders, you retain the memoized function to your dispatch call.
Alternatively, you may have encountered find similar patterns utilizing local state and then make your effect "depend" on sheets.
const [sheets, setSheets] = useState(recentSheets)
Hope this helps
I would add a check to see if recentSheets exists or not, using that as my dependency.
useEffect(() => {
if (!recentSheets) getRecentSheets();
}, [recentSheets])

Resources