Can't perform react state in React - reactjs

I'm having a problem. been browsing some questions here but seems doesn't work for me.
I'm getting this error in my three pages when I'm using the useEffect.
This is the code of my useEffect
const UserDetailsPage = () => {
const classes = useStyles()
const [userData, setUserData] = useState({
_id: "",
name: "",
phone: "",
address: "",
birthdate: "",
gender: "",
messenger: "",
photo: "",
email: "",
})
const [open, setOpen] = useState(false)
const [loaded, setLoaded] = useState(false)
const { height, width } = useWindowDimensions()
const {
_id,
name,
phone,
address,
photo,
gender,
messenger,
birthdate,
email,
} = userData
useEffect(() => {
const user = getUser()
getUserById("/user/" + user.userId, user.token)
.then((data) => {
setUserData(data)
setLoaded(true)
})
.catch((error) => {
console.log(error)
})
}, [])

Short of getUserById returning a cancel token to cancel any inflight network requests, or an "unsubscribe" method, you can use a React ref to track if the component is still mounted or not, and not enqueue the state update if the component has already unmounted.
const isMountedRef = React.useRef(false);
useEffect(() => {
isMountedRef.current = true;
return () => isMountedRef.current = false;
}, []);
useEffect(() => {
const user = getUser();
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if (isMountedRef.current) {
setUserData(data);
setLoaded(true);
}
})
.catch((error) => {
console.log(error);
});
}, []);

This is because of the async call in useEffect finishing and then attempting to setState after the page is no longer in focus.
It can be avoided by refactoring the useEffect like so:
useEffect(() => {
// created a boolean to check if the component is mounted, name is arbitrary
let mounted = true;
const user = getUser();
getUserById("/user/" + user.userId, user.token)
.then((data) => {
// only setState if mounted === true
if (mounted) {
setUserData(data);
setLoaded(true);
}
})
.catch((error) => {
console.log(error);
});
// set mounted to false on cleanup
return () => {
mounted = false;
};
}, []);
What's different here is that I use a mounted boolean to check if the page is currently mounted. By wrapping the setState call inside an if state, I can check if it's safe to setState, therefore avoiding the error.
Additional reading

This happens when your component is unmounting before setting your state. Try this code below to check if the component is mounted or not.
useEffect(() => {
let isMounted = true; // add a flag to check component is mounted
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if(mounted) { // set state only when component is mounted
setUserData(data)
setLoaded(true)
}
})
.catch((error) => {
console.log(error)
})
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []);

Don't use async tasks in useEffect. Define an async function and call in your useEffect.
Example:
const getSTH = async() =>{
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if(mounted) { // set state only when component is mounted
setUserData(data)
setLoaded(true)
}
})
.catch((error) => {
console.log(error)
})
}
useEffect (()=>{
getSTH();
},[])
I think this approach will help you.

Related

useEffect is re-rendering async promise again and again

I am trying to log currentUser details to console to see if user is logged in or not. But it is logging again n again and make a loop of currentUser in console.
Here is the code
const [currentUser, setCurrentUser] = useState(null);
const unSubscribeFromAuth = useRef(null);
useEffect(() => {
unSubscribeFromAuth.current = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
})
console.log(currentUser);
})
}
else {
setCurrentUser(userAuth);
}
})
return () => { unSubscribeFromAuth.current() };
}, [currentUser]);
and here is the function i am importing
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapShot = userRef.get();
console.log(snapShot);
if (!snapShot.exists) {
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await userRef.set({
displayName,
email,
createdAt,
...additionalData
})
}
catch (error) {
console.log('error creating user', error.message);
}
}
return userRef;
}
I tried to remove dependency but it says
React Hook useEffect has a missing dependency: 'currentUser'. Either include it or remove the dependency array
Since you are updating currentUser in the useEffect and the useEffect is triggered every time currentUser changes, you are seeing the infinite loop. You need to remove currentUser from the dependency array.
useEffect has a missing dependency is coming from console.log. If you remove it, it will go away.

How to prevent object undefined in React

I have react app requeting a flask server, I can return the objects but when I assing the state to a new variable it log undefined, even though I am able to log it
const [characters, setCharacters] = useState([]);
useEffect(() => {
const getData = async () => {
await fetch("http://127.0.0.1:6789/api/load_img_data")
.then((res) => res.json())
.then(
(result) => {
const arrayOfObj = Object.entries(result.imgData).map((e) => e[1]);
setCharacters(arrayOfObj);
},
(error) => {}
);
};
getData();
}, []);
console.log(characters); ## it works fine and log the object on the console
const columnsFromBackend = {
["1"]: {
name: "Terminator Group",
items: characters, ## doesn't work when I map over it as it will be always empty
}
}
so my question what it the best way to assign a state to variable? thanks
You can declare your columnsFromBacked and initialize it as empty object.After you data from api is stored in the hook, then you can assign the appropriate values to columnsFromBacked
Solution 1
let columnsFromBackend= {}
const [characters, setCharacters] = useState([]);
useEffect(() => {
const getData = async () => {
await fetch("http://127.0.0.1:6789/api/load_img_data")
.then((res) => res.json())
.then(
(result) => {
const arrayOfObj = Object.entries(result.imgData).map((e) => e[1]);
setCharacters(arrayOfObj);
columnsFromBackend = {
["1"]: {
name: "Terminator Group",
items: characters
}
}
},
(error) => {}
);
};
getData();
}, []);
}
Solution 2
You can implement useEffect hook with dependency on character hook.
sample code
useEffect(()=>{
columnsFromBackend = {...} //your code
}, [character]);

Getting undefined value in useEffect (React)

The below function gets the current location of a user:
const getCurrentLocation = () => {
fetch("https://ipinfo.io/json?token=$TOKEN")
.then((response) => response.json())
.then((jsonResponse) => {
console.log(jsonResponse)
return jsonResponse;
});
};
useEffect(() => {
console.log(getCurrentLocation());
}, []);
logging in useEffect is showing undefined and it is appearing first in the console, then jsonResponse shows next in the console with the corresponding object. Why is that ?
getCurrentLocation doesn't return anything, that's why you got undefined.
Moreover, fetch returns a Promise, which is asynchronous, meaning you don't get the result immediately, you must pass a calback to then to get the result when it is available.
const getCurrentLocation = () => {
return fetch("https://ipinfo.io/json?token=$TOKEN")
.then(response => response.json());
};
useEffect(() => {
getCurrentLocation()
.then(location => console.log(location));
}, []);
The getCurrentLocation function is not returning anything. Try saving the location in the state, so that you can access it when needed:
const [currentLocation, setCurrentLocation] = useState(null);
const getCurrentLocation = () => {
fetch("https://ipinfo.io/json?token=$TOKEN")
.then((response) => response.json())
.then((jsonResponse) => {
setCurrentLocation(jsonResponse); // <- save the location in state
});
};
useEffect(() => {
getCurrentLocation();
}, []);
return <div>{currentLocation}</div>
If you need the location in a useEffect, you could do:
useEffect(() => {
if (currentLocation !== null) {
// ...
}
}, [currentLocation])
you can simply use async/await to get response, take a look at this one:
const getCurrentLocation = async () => {
const result = await fetch("https://ipinfo.io/json?token=$TOKEN");
return result.json();
};
const handleGetLocation = async () => {
const result = await getCurrentLocation();
console.log(result);
};
useEffect(() => {
handleGetLocation();
}, []);

React : i can't access to my state from my useEffect

in the return of my react function I want to do a response.data.map(...), but I can't, "response" is undefined because it's in my useEffect (scope problem).
So I try to create a state with useState which will contain response.data, but the problem is that my console.log always returns undefined, the default state of my state.
So I try to use prevstate because I believe the problem is that the previous state is taken into account, but apparently the syntax is not good. :
const Comments = ({ postId }) => {
// States
const [allComments, setAllComments] = useState()
useEffect(() => {
async function fetchData() {
const data = {
postId: postId,
};
const response = await POST(ENDPOINTS.GET_ALL_COMMENTS, data);
if (response.data[0]) {
setAllComments((prevState) => ({
...prevState,
response.data
}))
} else {
}
}
fetchData();
console.log(allComments)
}, []);
return (
<div>
{allComments.map(...)}
</div>
);
};
I finally try to do like this:
setAllComments ((prevState) => ({
... prevState,
response
}))
This time the syntax is good, but my console.log from allComments is still undefined ...
How do I access my response.data from my return? Should we use useState, prevstate, other?
You can't .map() over an object ({}).
If your comments will be an array, you'll need to use the array spread operator ([..., ...]):
const Comments = ({ postId }) => {
const [allComments, setAllComments] = useState([]);
useEffect(() => {
async function fetchData() {
const response = await POST(ENDPOINTS.GET_ALL_COMMENTS, {
postId,
});
const data = response.data;
if (Array.isArray(data)) {
setAllComments((prevState) => [...prevState, ...data]);
} else {
throw new Error("Oops, didn't get an array.");
}
}
fetchData();
}, [postId]);
return <div>{JSON.stringify(allComments)}</div>;
};

React unable to use useEffect with setState inside function

I have a scenario where an async function is called on button click and the return value is setting the state value. After that, another function is called which needs the previously set value. As it is inside function I am not able to use useEffect. How to achieve this?
const [user, setUser] = React.useState(null);
const handleSignIn = async () => {
const result = await Google.logInAsync(config);
const { type, idToken } = result;
setUser(result?.user);
if (type === "success") {
AuthService.googleSignIn(idToken)
.then((result) => {
const displayName = `${user?.givenName} ${user?.familyName}`;
signIn({
uid: user.uid,
displayName: displayName,
photoURL: user.photoUrl,
});
})
.catch((error) => {
});
}
};
Here, handleSignIn is called on the button click and user state value is set from the result achieved from the Google.logInAsync. Then AuthService.googleSignIn is called and when success the user object is used there but it not available sometimes.
cbr's comment hits the nail on the head. You need to wrap everything following setUser in its own useEffect, which will depend on the user state variable. Like this:
const [result, setResult] = React.useState(null);
const handleSignIn = async () => {
const result = await Google.logInAsync(config);
setUser(result);
};
useEffect( () => {
if (result) {
const { type, idToken, user } = result;
if (type === "success") {
AuthService.googleSignIn(idToken)
.then((result) => {
const displayName = `${user?.givenName} ${user?.familyName}`;
signIn({
uid: user.uid,
displayName: displayName,
photoURL: user.photoUrl,
});
})
.catch((error) => {
});
}
}
}, [result])
What happens here is that your handleSignIn sets the user variable. Your useEffect runs whenever the user variable is updated. If it exists, it will run your AuthService code with the new user value.
Alternatively, you can skip using useEffect altogether by just referencing your result.user directly. Extract it from result along with the type and idToken, and use it directly. You can still save it to state with your setUser function if you need it later:
const [user, setUser] = React.useState(null);
const handleSignIn = async () => {
const result = await Google.logInAsync(config);
const { type, idToken, user } = result;
setUser(user);
if (type === "success") {
AuthService.googleSignIn(idToken)
.then((result) => {
const displayName = `${user?.givenName} ${user?.familyName}`;
signIn({
uid: user.uid,
displayName: displayName,
photoURL: user.photoUrl,
});
})
.catch((error) => {
});
}
};

Resources