I have a peculiar problem when using the useEffect-hook in React Native. I have a functional component, which has one useEffect-hook that fetches data for pinpoints and another that then rearranges the pinpoints (filteredPinpoints) into a useable format. filteredPinpoints is updated three times, but the first two times, the object is empty.
Now the weird behaviour: if I comment out dispatch(organiseRoutes(...)) in the second useEffect, this useEffect is called three times, but if I want to execute the dispatch function, the useEffect is only called twice. Since I return early if filteredPinpoints is empty, the code never reaches the dispatch.
EDIT: Also, when I implement dispatch(organiseRoutes(...)), the app freezes, only showing the (spinning) ActivityIndicator, but leaving me unable to navigate to the previous screen again.
What do I have to change, so that the useEffect is run every single time filteredPinpoints is updated?
import { View, ActivityIndicator } from 'react-native';
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getRouteData, organiseRoutes } from '../utils';
export default function RoutePreviewScreen() {
const dispatch = useDispatch();
const [loadingData, setLoadingData] = useState(true);
const currentRouteID = useSelector(state => state.currentRouteID);
const filteredPinpoints = useSelector(state =>
// Uses ObjectFilter from https://stackoverflow.com/questions/5072136/javascript-filter-for-objects/37616104
ObjectFilter(state.allPinpoints, pinpoint => pinpoint.Route_ID == state.currentRouteID)
);
const dispatch = useDispatch();
// This updates state.allPinpoints.
useEffect(() => {
(async function myFirstAsyncFunction() {
await dispatch(getRouteData(currentRouteID));
})();
}, [currentRouteID]);
useEffect(() => {
if (Object.keys(filteredPinpoints).length === 0) {
return
}
console.log("Could EXECUTE now!!")
// If the following line is commented out, the useEffect executes a third time.
// However, only in the third run, filteredPinpoints is not a empty object.
// If it is not commented out, it simply refuses to execute a third time.
dispatch(organiseRoutes(filteredPinpoints));
setLoadingData(false)
}, [filteredPinpoints]);
if (loadingData) { return (<View><ActivityIndicator/></View>)}
return(<ComponentUsingOrganisedRoutes/>)
Looks like your filteredPinpoints is an object. useEffect does not do a deep equality check on object, for that you'll have to deal with it differently.
You can use a dependency like: [JSON.stringify(filteredPinpoints)], which would do a better check and won't be as slow as a deep equality check.
ref: https://twitter.com/dan_abramov/status/1104414272753487872
Okay, fixed it. Pretty stupid mistake. Basically, organiseRoutes() affects filteredPinpoints, so useEffect is caught in an infinite loop. I guess in these scenarios, console.log() is not called because it's being blocked.
I introduced a new state-hook organisedData aside to loadingData, to give the second useEffect another value to check for before it executes the dispatch. Once organisedData is true, it's going to stop executing and thereby stop triggering itself now.
import React, { useState, useEffect } from 'react';
import { View, ActivityIndicator } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { getRouteData, organiseRoutes } from '../utils';
export default function RoutePreviewScreen() {
const dispatch = useDispatch();
const [loadingData, setLoadingData] = useState(true);
const currentRouteID = useSelector(state => state.currentRouteID);
const filteredPinpoints = useSelector(state =>
ObjectFilter(state.allPinpoints, pinpoint => pinpoint.Route_ID == state.currentRouteID)
);
// New:
const [organisedData, setOrganisedData] = useState(false);
useEffect(() => {
(async function myFirstAsyncFunction() {
// New:
setLoadingData(true)
await dispatch(getRouteData(currentRouteID));
// New:
setOrganisedData(false)
})();
}, [currentRouteID]);
useEffect(() => {
// New:
if (!loadingData || organisedData) {
return
}
if (Object.keys(filteredPinpoints).length === 0) {
return
}
// New:
setOrganisedData(true)
dispatch(organiseRoutes(filteredPinpoints));
// New:
setLoadingData(false)
// Adjusted:
}, [organisedData, loadingData, filteredPinpoints]);
if (loadingData) { return (<View><ActivityIndicator/></View>)}
return(<ComponentUsingOrganisedRoutes/>)
Related
I want to display some datas from API using functional component instead of class component in react. In order to do so, I write useEffect and apparently work properly. The problem is, if I write console log, it would return an infinite value.
Any one can help me to solve this? I want the console log stop looping the value from my API. This is my source code. Thank you.
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
export default function FoodDetail() {
const { id } = useParams();
const [detail, setDetail] = useState([]);
useEffect(() => {
axios
.get("http://localhost:3004/foods/" + id)
.then((res) => {
setDetail(res.data);
console.log(detail)
})
.catch((error) => {
console.log(error);
});
});
return ()
}
if you want the get to only run once on component mount, you can use the code below:
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
export default function FoodDetail() {
const { id } = useParams();
const [detail, setDetail] = useState([]);
useEffect(() => {
axios
.get("http://localhost:3004/foods/" + id)
.then((res) => {
setDetail(res.data);
console.log(detail)
})
.catch((error) => {
console.log(error);
});
}, []);
return ()
}
The only difference is the inclusion of an empty dependency array as the second argument for useEffect(). Going forward, if you want to refetch this data based on the state of another variable changing, you can add that variable to the dependency array and this will refetch the data each time the value of that variable changes.
** edit **
To see the value of detail properly, you can remove the console.log from the first useEffect loop and add something like this to the code:
useEffect(()=> {
console.log(detail)
}, [detail])
your useeffect is running infinitely since you console log your call stack gets
full and you notice it so use following code so it will exactly run once like
componentdidmount
useEffect(() => {
axios
.get("http://localhost:3004/foods/" + id)
.then((res) => {
setDetail(res.data);
console.log(detail)
})
.catch((error) => {
console.log(error);
});
},[]); //add empty array as another parameter of useEffect so it run only
//once
Try adding an empty array to the UseEffect refreshing dependency.
UseEffect(() => {}, []) this empty array means UseEffect will only be triggered at component mounting, i guess yours would be called everytime component is re-rendered.
Example : UseEffect(() => {console.count('refresehd'), [detail]}) would be triggered everytime your detail changes
for more info check the UseEffect Documentation
Docs useEffect
I came across this error int he console. I had a look through the previous questions but they were not much help. Anyone know how I have caused this infinite loop?
"Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."
import logo from './logo.svg';
import './App.css';
import Movie from './components/Movie'
import React, { useEffect, useState } from 'react';
const FEATURED_API = "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=04c35731a5ee918f014970082a0088b1&page=1"
const IMG_API = "https://image.tmdb.org/t/p/w1280"
const SEARCH_API = "https://api.themoviedb.org/3/search/movie?&api_key=04c35731a5ee918f014970082a0088b1&query="
function App() {
const [movies, setMovies] = useState([]);
useEffect(() => {
fetch(FEATURED_API)
.then((res) => res.json())
.then((data) => {
console.log(data)
setMovies(data.results);
});
},[])
setMovies(movies)
return <div>{ movies.length > 0 && movies.map((movie) => <Movie/>)}</div>
}
export default App;
You can't set states outside functions or hooks in components.
Just remove the setMovies(movies) and it should work ;)
Keep in mind that the setMovies function queues a re-render that will take place as soon as the component has finished rendering. Given this behavior, if the setMovies function is placed outside of a function or a useEffect, it will get you stuck in a loop!
export default function MyQuestions() {
const router = useRouter();
const [auth, setAuth] = useState(false);
const checkAuth = async () => {
const loggedInUsername = await getUsername();
if (router.query.username === loggedInUsername) return setAuth(true);
return;
};
checkAuth();
This is a part of a React component where I execute the checkAuth function. I thought it should execute only once but that is not the case. It is executed 4 times and if I remove the returns it is executed even more than 10 times and I don't understand why. In js a function that reaches the end should stop automatically.
Why does this happen?
In this code router is of Next.js
What are the conditions under which the check should be re-run? This is what useEffect is intended for. useEffect accepts a function to run the desired effect, and a list of dependencies to specify when an effect should be run -
import { useRouter } from ...
import { useEffect, useState } from "react"
function MyQuestions() {
const router = useRouter()
const [auth, setAuth] = useState(false)
useEffect(async () => {
const loggedInUsername = await getUsername()
if (router.query.username === loggedInUsername)
setAuth(true)
}, [getUsername, router.query.username, setAuth])
return <>...</>
}
Any free variable inside the effect must be listed as a dependency of the effect. There's one issue however. setAuth will be a new function each time MyQuestions is rendered. To ensure setAuth will be the same for each render, we can use useCallback -
import { useRouter } from ...
import { useEffect, useCallback, useState } from "react"
function MyQuestions() {
const router = useRouter()
const [auth, setAuth] = useState(false)
const authenticate =
useCallback(_ => setAuth(true), [])
useEffect(async () => {
const loggedInUsername = await getUsername()
if (router.query.username === loggedInUsername)
authenticate()
}, [getUsername, router.query.username, authenticate])
return <>...</>
}
Now the effect will only re-run when getUsername, router.query.username or authenticate changes. Considering getUsername and authenticate are functions and should not change, we can expect that the effect will only re-run when router.query.username changes.
I haven't used nextjs but i suppose it happens because it is executed on every render of the router component.
If you want to use it once, just call it in a use effect when the component mounts.
useEffect(() => {
checkAuth();
}, []) // This will run once, when the component mounts
There is no need to return the setState call:
const checkAuth = async () => {
const loggedInUsername = await getUsername();
if (router.query.username === loggedInUsername) setAuth(true);
};
Also because you are calling the checkAuth() function right after you call it. Setting state in React causes a re-render. So the reason it is executed 4 to 10 times is because your MyQuestion component re-renders when you setState(), and when it rerenders, is hits the checkAuth() function again and the cycle repeats.
As mentioned put the functionality in a useEffect() with an empty dependency array:
useEffect(() => {
const loggedInUsername = await getUsername();
if (router.query.username === loggedInUsername) return setAuth(true);
}, [])
Here is how I have created a Context for simple program I am writing
import React, { useState, createContext, useEffect } from "react";
export const PhotoContext = createContext();
export const PhotoProvider = (props) => {
const [photo, setPhoto] = useState([]);
useEffect(() => {
console.log("Use Effect Runs HAHAHAH");
console.log("HAHAHAHAHAHAHAH");
fetchPhotos();
async function fetchPhotos() {
const url =
"https://raw.githubusercontent.com/bobziroll/scrimba-react-bootcamp-images/master/images.json";
fetch(url)
.then((res) => res.json())
.then((arr) => {
setPhoto(arr);
})
.catch(console.log("ERROR"));
}
}, []);
return (
<PhotoContext.Provider value={[photo, setPhoto]}>
{props.children}
</PhotoContext.Provider>
);
};
There is another file where I want to load the data in the photos variable. Here is the code for it. I have used setTimeout to see where exactly is the problem. It seems whenever the statement in setTimeout runs, the value in console in returned twice. First, it is empty and the second has the actual value. But since, I try to access the photos.url, and since the first time it is undefined, the program collapses.
import React, { useState, useContext } from "react";
import { PhotoContext } from "../Context/PhotoContext";
const Photos = (props) => {
const [photos, values] = useContext(PhotoContext);
setTimeout(() => {
console.log(photos[0].url);
}, 3000);
return <div>{}</div>;
};
export default Photos;
Help would be really appreciated.
Didn't see the problem. I created sandbox for your example.
https://codesandbox.io/s/inspiring-lovelace-5r1gb?file=/src/App.js
I know this question has been answered a bunch of times already. I just cannot find the answer that solves my problem, leading me to believe, that I am either stupid or my problem has not been had because it is even more stupid than me.
So aside from that, this is my problem:
I am trying to create a functional component that takes some information from a redux state in order to render the correct language labels into a login form.
Let's start with the code:
import React, { useState, useEffect } from "react";
import { Paper, TextField } from "#material-ui/core";
import { changeLanguage } from "../redux/actions";
import { useDispatch, useSelector } from "react-redux";
const Login = () => {
const dispatch = useDispatch();
const [language, setLanguage] = useState("de");
const [strings, setStrings] = useState({});
useEffect(() => {
console.log("I have been called", language, strings);
setLanguage(["de", "en"].includes(navigator.language.split("-")[0]) ? navigator.language.split("-")[0] : "de");
}, [language]);
dispatch(changeLanguage(language));
const str = useSelector(state => state.base.strings.login);
console.log(str);
setStrings(str); // <- THIS ONE IS CAUSING THE ERROR
return (
<div className={"App"}>
<Paper>
<TextField label={strings.username}/><br/>
<TextField label={strings.password} type={"password"}/>
</Paper>
</div>
);
};
export default Login;
This is what I used to get the app working at all. I realize that setting the strings on every render will cause an infinite loop. That much is clear. However when using this code:
import React, { useState, useEffect } from "react";
import { Paper, TextField } from "#material-ui/core";
import { changeLanguage } from "../redux/actions";
import { useDispatch, useSelector } from "react-redux";
const Login = () => {
const dispatch = useDispatch();
const [language, setLanguage] = useState("de");
const [strings, setStrings] = useState({});
useEffect(() => {
console.log("I have been called", language, strings);
setLanguage(["de", "en"].includes(navigator.language.split("-")[0]) ? navigator.language.split("-")[0] : "de");
dispatch(changeLanguage(language));
const str = useSelector(state => state.base.strings.login);
console.log(str);
setStrings(str);
}, [language]);
return (
<div className={"App"}>
<Paper>
<TextField label={strings.username}/><br/>
<TextField label={strings.password} type={"password"}/>
</Paper>
</div>
);
};
export default Login;
I get this error:
/src/App/pages/login.js
Line 17:15: React Hook "useSelector" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.
And yes, I do understand what it is telling me but I do believe that useEffect is a React Hook or am I missing something here?
I am simply looking for a way to get this to work. It cannot be that hard because I can make it work with class components no problem.
If the question is stupid, please elaborate on why instead of just voting it down. This would be a lot more helpful in developing and understanding for the matter.
I have consulted the docs for two hours and tried a bunch of stuff. Nothing has worked.
Thank you for taking the time!
useEffect(() => {
console.log("I have been called", language, strings);
setLanguage(["de", "en"].includes(navigator.language.split("-")[0]) ? navigator.language.split("-")[0] : "de");
dispatch(changeLanguage(language));
const str = useSelector(state => state.base.strings.login);
console.log(str);
setStrings(str);
}, [language]);
You are using one hook inside another.That is not allowed.Only place you can place hooks is inside Functional Component and outside any method.For more info https://reactjs.org/docs/hooks-rules.html