The issue: On startup after the splashscreen is shown the List screen shows all users. When app is first loaded useEffect() is being called but the state update is not causing a rerender. However this only happen on startup, if I navigate on another screen and come back to it eveything happens as it should be.
const fetch = async () => {
useStore.setState({ allUsers: await getAllUsers() });
};
useEffect(() => {
fetch();
}, []);
and my zustand store is defined as below :
type Store = {
allUsers: User[];
};
export const useStore = create<Store>((set) => ({
allUsers: [],
}));
The useEffect has no dependency to trigger a re-render. useEffect with no dependency array will be only called once when the component mounts.
If the desired effect is to display updated data after fetch() is complete add the state as a dependency to the useEffect hook.
Like so:
useEffect(() => {
fetch()
},[])
Change to:
useEffect(() => {
fetch()
}, [useMazlsStore]);
For more information on how useEffect lifecycles work chekc out this article
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 am quite new in react native and found simple solution from tutorial for fetching&loading case
export const useFetching = (callback) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const fetching = async(data) => {
try{
setIsLoading(true);
await callback(data);
} catch(e) {
setError(e.message);
} finally {
setIsLoading(false);
}
}
return [fetching, isLoading, error];
}
I use it with signUp func from react-navigation docs as a callback, which is supposed to change the screen to main app:
signIn: async (data) => {
const token = await AuthManager.login(data);
if (token)
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
But when I call it I get "Can't perform a React state update on an unmounted component". I found out that:
The "finally" block from useFetching causes the error
Empty useEffect useEffect(() => {return () => {}},[]) inserted into login screen removes the problem
I'm wondering, is there any normal solution in this case and why useEffect helps here?
Changing state on unmounted component is illegal measure in react.
This is simple events plan in your component life cycle:
Api request sended
Component was unmounted
Api request returns with data to your app
Ooooops, api caller (in your case it is fetch function) see that component was unmounted and return error, so your app execute this block of code: catch(e) { setError(e.message); }, but there is problem - setState (setError).
finally block code executed too, so you must check is component mounted before setState.
Simple solution
I am trying to load data for my component in useEffect. How can I ensure the state is defined before I go to the next screen?
const [rescueMeContent, setRescueMeContent] = useState({});
useEffect(() => {
async function fetchData() {
const { payload } = await get('xxx');
setRescueMeContent(payload);
}
fetchData();
});
When I first run the app, on the next screen when I try and access the state like this I get an error saying its undefined:
const {
content: {
splashScreen: {
backgroundImage: { url },
buttonText,
informationText,
title: pageTitle,
},
},
} = useSelector((state) => state);
useState works in inside only component. You can't reach that from other component. You must use react redux for to use useSelector. You must assign your fetch data to redux state with action. Then you can get that data with useSelector.
I am trying to save a value from a custom hook, which is fetching data for the server, to functional component state with useState, because I later need to change this value and after the change it needs to rerender. So desired behaviour is:
Set State variable to value from custom hook
Render stuff with this state variable
Modify state on button click
Rerender with new state
What I tried is:
Set the inital value of useState to my hook:
const [data, setData] = useState<DataType[] | null>(useLoadData(id).data)
but then data is always empty.
Set the state in a useEffect() hook:
useEffect(()=>{
const d = useLoadData(id).data
setData(d)
}, [id])
But this is showing me the Error warning: Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
Doing this:
const [data, setData] = useState<DocumentType[]>([])
const dataFromServer = useLoadData(id).data
useEffect(()=>{
if (dataFromServer){
setData(dataFromServer)
}
}, [dataFromServer])
Leading to: ERROR: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
What would be a proper solution for my use case?
It looks like your custom hook returns a new array every time it is used.
Solution 1: change your hook to return a 'cached' instance of an array.
function useLoadData(id) {
const [data, setData] = useState([]);
useEffect(() => {
loadData(id).then(setData);
}, [id]);
// good
return data;
//bad
//return data.map(...)
//bad
//return data.filter(...)
//etc
}
codesandbox.io link
Solution 2: change your hook to accept setData as a parameter.
function useLoadData(id, setData) {
useEffect(() => {
loadData(id).then(setData);
}, [id]);
}
Here I am telling the hook where to store data so that both custom hook and a button in a component can write to a same place.
codesandbox.io link
Full example:
import React from "react";
import ReactDOM from "react-dom";
import { useState, useEffect } from "react";
// simulates async data loading
function loadData(id) {
return new Promise((resolve) => setTimeout(resolve, 1000, [id, id, id]));
}
// a specialized 'stateless' version of custom hook
function useLoadData(id, setData) {
useEffect(() => {
loadData(id).then(setData);
}, [id]);
}
function App() {
const [data, setData] = useState(null);
useLoadData(123, setData);
return (
<div>
<div>Data: {data == null ? "Loading..." : data.join()}</div>
<div>
<button onClick={() => setData([456, 456, 456])}>Change data</button>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
I have a provider component that sets the initial auth context from firebase-auth.
Everything works fine until I try to add persistence in the form of setting up an observer with onAuthStateChanged. This checks for auth and I update my state via dispatch method.
But this is causing an infinite loop. I added an unsubscribe function call, but this makes no difference,
Can anyone advise? thanks
AuthContext.js
import React from "react";
import * as firebase from "firebase/app";
//firebaseauth reducer
import { firebaseAuth } from "../reducers/AuthReducer";
export const Auth = React.createContext();
const initialState = { user: {} };
export const AuthProvider = (props) => {
const [state, dispatch] = React.useReducer(firebaseAuth, initialState);
const value = { state, dispatch };
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
dispatch({
type: "HYDRATE_AUTH",
payload: user,
});
});
unsubscribe();
return <Auth.Provider value={value}>{props.children}</Auth.Provider>;
};
What is happening is:
The component renders
It sets the auth listener
The listener fires and sets the state of the component
State update causes the component to rerender
The component adds another listener and everything repeats
Unsubscribing doesn't help because the component keeps rerendering and adding a new listener every time.
We can tell the component to set the listener only once by using useEffect():
AuthContext.js
import React from "react";
import * as firebase from "firebase/app";
//firebaseauth reducer
import { firebaseAuth } from "../reducers/AuthReducer";
export const Auth = React.createContext();
const initialState = { user: {} };
export const AuthProvider = (props) => {
const [state, dispatch] = React.useReducer(firebaseAuth, initialState);
const value = { state, dispatch };
React.useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
dispatch({
type: "HYDRATE_AUTH",
payload: user,
});
});
}, []);
return <Auth.Provider value={value}>{props.children}</Auth.Provider>;
};
By providing an empty dependency array to useEffect(), we tell it to run the callback only once, when the component initially renders, so the auth listener is set only once.
Thanks to #tomek-ch -- an alternate solution would be to prevent the re-render by storing the state (eg: keep the user bits in the component state) and then do not update the state if its already there (or the same).
In my case I just keep the user the first time, set a boolean state flag, and ignore subsequent events.