I am storing some data in the state. I first call 1 API and get the update the data in the state 3 times. After the data update, I have to call another API using the updated state data. I am unable to do so . If the state was updated just once, I would have use useEffect. But, here it is changing 3 times.
const [data, setData] = useState(() => getDataInit());
const setDataKey = key => value =>
setData(d => ({
...d,
[key]: value,
}));
const postFn = () => {
const response = await updateData({ body: data });
onSave({
response,
widget,
wrapperId,
});
};
const resetFn = () => {
const defaultData = await getDefaultData({
query: { id: data.default_id },
});
setDataKey('quantity')(defaultData.quantity);
setDataKey('name')(defaultData.name);
setDataKey('amount')(defaultData.amount);
postFn();
};
You can update to call only one setData. So you can add useEffect to call postFn
const resetFn = () => {
const defaultData = await getDefaultData({
query: { id: data.default_id },
});
const newData = {
quantity: defaultData.quantity,
name: defaultData.name,
amount: defaultData.amount,
};
setData((d) => ({
...d,
...newData,
}));
}
useEffect(() => {
postFn();
}, [data]);
Related
I have a component that gets a value from the local storage and does a useQuery to get some data:
const DashboardComponent = () => {
const [filterState, setFilter] = useState(false);
const returnFilteredState = async () => {
return await localforage.getItem<boolean>('watchedAndReviewedFilterd') || false;
};
useEffect(() => {
returnFilteredState().then((value) => {
setFilter(value);
});
}, []);
const {error, loading, data: {moviesFromUser: movies} = {}} =
useQuery(resolvers.queries.ReturnMoviesFromUser, {
variables: {
userId: currentUserVar().id,
filter: filterState,
},
});
The problem is that the ReturnMoviesFromUser query is called twice. I think it's because of the filterState variable. If I set the filter: true the ReturnMoviesFromUser is only called once.
React code
import React, { useEffect, useState } from "react";
import { getDocs, collection } from "firebase/firestore";
import { auth, db } from "../firebase-config";
import { useNavigate } from "react-router-dom";
function Load() {
const navigate = useNavigate();
const [accountList, setAccountList] = useState([]);
const [hasEmail, setHasEmail] = useState(false);
const accountRef = collection(db, "accounts");
Am i using useEffect correctly?
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
checking whether email exists
const emailCheck = () => {
if (accountList.filter((e) => e.email === auth.currentUser.email)) {
setHasEmail(true);
} else {
setHasEmail(false);
}
};
Redirecting based on current user
const direct = () => {
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
return <div></div>;
}
The code compiles but doesn't redirect properly to any of the pages.
What changes should I make?
First question posted excuse me if format is wrong.
There are two problems here:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
In order:
Since getAccounts is asynchronous, you need to use await when calling it.
But even then, setting state is an asynchronous operation too, so the account list won't be updated immediately after getAccounts completes - even when you use await when calling it.
If you don't use the accountList for rendering UI, you should probably get rid of it as a useState hook altogether, and just use regular JavaScript variables to pass the value around.
But even if you use it in the UI, you'll need to use different logic to check its results. For example, you could run the extra checks inside the getAccounts function and have them use the same results as a regular variable:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
const result = data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setAccountList(result);
emailCheck(result);
direct();
};
getAccounts();
}, []);
const emailCheck = (accounts) => {
setHasEmail(accounts.some((e) => e.email === auth.currentUser.email));
};
Alternatively, you can use a second effect that depends on the accountList state variable to perform the check and redirect:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
});
useEffect(() => {
emailCheck();
direct();
}, [accountList]);
Now the second effect will be triggered each time the accountList is updated in the state.
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]);
I'm not understanding why the following code, the callback onSocketMessage is not using the new acquisition state. inside the useEffect the state is correctly updated, but the function is not evaluated again...i've also tryed using useCallback with acquisition as dependency but nothing changed.
const Ac = () => {
const [acquisition, setAcquisition] = useState({ data: {} })
const [loading, setLoading] = useState(true)
const socket = useRef(null);
const onSocketMessage = (message) => {
console.log(acquisition) // this is always initial state
let { data } = acquisition
data.input[message.index] = message.input
setAcquisition(prevState => ({ ...prevState, data }));
}
useEffect(() => {
fetchCurrentAcquisition(acquisition => {
setAcquisition(acquisition)
setLoading(false)
socket.current = newSocket('/acquisition', () => console.log('connected'), onSocketMessage);
})
return () => socket.current.disconnect()
}, [])
console.log(acquisition)
You are logging a stale closure you should try the following instead:
const onSocketMessage = useCallback((message) => {
setAcquisition((acquisition) => {
//use acquisition in the callback
console.log(acquisition);
//you were mutating state here before
return {
...acquisition,
data: {
...acquisition.data,
input: {
//not sure if this is an array or not
//assimung it is an object
...acquisition.data.input,
[message.index]: message.input,
},
},
};
});
}, []); //only created on mount
useEffect(() => {
fetchCurrentAcquisition((acquisition) => {
setAcquisition(acquisition);
setLoading(false);
socket.current = newSocket(
'/acquisition',
() => console.log('connected'),
onSocketMessage
);
});
return () => socket.current.disconnect();
//onSocketMessage is a dependency of the effect
}, [onSocketMessage]);
I'm trying to update my old Todo List React app so that it uses hooks, and can't figure this out for the life of me. setTodos is successfully updating the state on load as shown in React developer tools, but the component doesn't re-render and a blank list of todos is displayed on the screen. Only when I add a Todo to the list does it re-render and all of the todos show up. Here's what I've got:
const App = () => {
// Todos are the only state in App
const [ todos, setTodos ] = useState([]);
// Fetches Todos from Firestore database on load
useEffect(() => {
const initialState = [];
dbTodos.get().then((snapshot) => {
snapshot.forEach((doc) => {
const currentTodo = doc.data();
currentTodo['id'] = doc.id;
initialState.push(currentTodo);
});
});
setTodos(initialState);
// eslint-disable-next-line
}, []);
// Add Todo
const addTodo = (title) => {
const newTodo = {
title : title,
completed : false
};
dbTodos.add(newTodo).then((doc) => {
newTodo['id'] = doc.id;
setTodos([ ...todos, newTodo ]);
});
};
Here is the code I used before implementing hooks:
componentDidMount() {
dbTodos.get().then((snapshot) => {
snapshot.forEach((doc) => {
const currentTodo = doc.data();
currentTodo['id'] = doc.id;
setState({ ...state, todos: [ ...state.todos, currentTodo ] });
});
});
};
You should move your setTodos in UseEffect
useEffect(() => {
dbTodos.get().then((snapshot) => {
const initialState = []; // Also this you can move here
snapshot.forEach((doc) => {
const currentTodo = doc.data();
currentTodo['id'] = doc.id;
initialState.push(currentTodo);
});
setTodos(initialState); /// here
});
}, []);
When you setState in useEffect then you have to add setState in dependency.
Instead of doing that use another arrow function and call it from useEffect.
use the code below
const App = () => {
// Todos are the only state in App
const [ todos, setTodos ] = useState([]);
// Fetches Todos from Firestore database on load
const fetchData = () => {
const initialState = [];
dbTodos.get().then((snapshot) => {
snapshot.forEach((doc) => {
const currentTodo = doc.data();
currentTodo['id'] = doc.id;
initialState.push(currentTodo);
});
});
setTodos(initialState);
}
useEffect(() => {
fetchData();
}, []);
// Add Todo
const addTodo = (title) => {
const newTodo = {
title : title,
completed : false
};
dbTodos.add(newTodo).then((doc) => {
newTodo['id'] = doc.id;
setTodos([ ...todos, newTodo ]);
});
};
In useEffect you create empty array. Make a request. set empty array to state. When request was fulfilled you make changes in the array by ref. Therefore you see you array in dev tool and react does'n rerender you component.
const App = () => {
// Todos are the only state in App
const [ todos, setTodos ] = useState([]);
// Fetches Todos from Firestore database on load
useEffect(() => {
dbTodos.get().then((snapshot) => {
setTodos(snapshot.map((doc) => ({
id: doc.id,
...doc.data(),
})));
});
}, []);
// Add Todo
const addTodo = (title) => {
const newTodo = {
title : title,
completed : false
};
dbTodos.add(newTodo).then((doc) => {
newTodo['id'] = doc.id;
setTodos([ ...todos, newTodo ]);
});
};