React state is empty after setting - javascript

I have simple nextjs app where i want to save and print state to fetchData
This is my code
const Room = () => {
const router = useRouter();
const [fetchData, setFetchData] = useState("");
useEffect(() => {
if (router.asPath !== router.route) {
getDataNames();
console.log(fetchData); // Empty
}
}, [router]);
const getDataNames = async () => {
try {
await fetch("http://localhost:1337/rooms?_id=" + router.query.id)
.then((response) => response.json())
.then((jsonData) => setFetchData(jsonData) & console.log(jsonData)); // Data are logged in console
} catch (e) {
console.error(e);
}
};
Problem is that fetchData is empty on console log but jsonData gaves me actual data. And i have no idea what can be problem.

Ok so 3 things here. Firstly, your useEffect doesn't re-run when your fetchData value changes. You need to add it to the dependency list:
useEffect(() => {
if (router.asPath !== router.route) {
getDataNames();
console.log(fetchData); // Empty
}
}, [router, fetchData]);
this way the effect will run when fetchData changes. And of course it will only console.log it if that condition inside the effect's if condition is satisfied.
Secondly, you're mixing async/await and .then/.catch syntax, don't do that. Use one or the other. In this case you can just remove the await keyword and try/catch block, and use your existing .then code, and add a .catch to it to catch any errors.
Finally, & in Javascript is the bitwise AND operator, so not sure why you were using it there. If you think it means "and also do this", then that's incorrect. Just put the .then function inside curly braces and call the setFetchData and console.log statements one after the other:
.then((jsonData) => {
setFetchData(jsonData);
console.log(jsonData)
});

useEffect(() => {
console.log(fetchData);
}, [fetchData]);
use this hook to log your data

Related

React: How to update a state prior to another function?

I have a state variable called list that updates when setList is called. SetList lives under the function AddToList, which adds a value to the existing values in the list. As of this moment, the function handleList executes prior to the state variable setList even though I have setList added prior to the function handleList. What I am trying to achieve is for the setList to update its list prior to running the handleList. Could you provide insights on how to fix this?
If you want to test the code, https://codesandbox.io/s/asynchronous-test-mp2fq?file=/Form.js
export default function Form() {
const [list, setList] = useState([]);
const addToList = (name) => {
let newDataList = list.concat(name);
setList(newDataList);
console.log("List: ", list);
handleList();
};
const handleList = async () => {
console.log("Handle List Triggered");
await axios
// .put("", list)
.get("https://api.publicapis.org/entries")
.then((response) => {
console.log("Response: ", response);
})
.catch((error) => {});
};
return (
<AutoComplete
name="list"
label="Add to List"
onChange={(events, values) => {
addToList(values.title);
}}
/>
);
}
As you can tell, the get response is made prior to updating the list.
It's not clear what you want to do with the updated list, but you know what the new list will be, so you can just pass that around if you need it immediately.
const addToList = (name) => {
let newDataList = list.concat(name);
setList(newDataList);
console.log("List: ", list);
handleList(newDataList);
};
const handleList = async (list) => {
console.log("Handle List Triggered");
await axios
// .put("", list)
.get("https://api.publicapis.org/entries")
.then((response) => {
console.log("Response: ", response);
})
.catch((error) => {});
};
React's useEffect hook has an array of dependencies that it watches for changes. While a lot of the time it's used with no dependencies (i.e. no second parameter or empty array) to replicate the functionality of componentDidMount and componentDidUpdate, you may be able to use that to trigger handleList by specifying list as a dependency like this:
useEffect(() => {
handleList(list);
}, [list]);
I think there may be a redundant request when page loads though because list will be populated which you'll most likely want to account for to prevent unnecessary requests.
first of all you have to understand setState is not synchronized that means when you call setList(newDataList) that not gonna triggered refer why setState is async
therefore you can use #spender solution
or useStateCallback hook but it's important understand setState is not sync
const [state, setState] = useStateCallback([]);
const addToList = (name) => {
........... your code
setList(newDataList, () => {
// call handleList function here
handleList();
});
}

React-Native onSubmitEditting taking too much time to execute function

I am following a react-native tutorial but am having some trouble with React contexts and ActivityIndicator, I dont know where the problem lies, but I will try to be as descriptive as possible.
The problem:-
The code :
I am using contexts to provide the app with the location that has been searched and then searching for that location within my mock data, later returning the restaurants around that location.
complete source code at https://github.com/diivi/KiloBite/blob/main/src/services/location/location.context.js
Here I am using the onSearch function and passing it as a context prop to my search box to use with onSubmitEditing.
In your code you call onSearch and there you setILoading(true)
and setKeyword(searchKeyword).
Then in the useEffect you use the keyword you set in onSearch. Your useEffect runs only in keyword changes (see dependencies).
Try to add onSearch in your dependencies (look below).
Or maybe even locationRequest, locationTransform.
I would also try setIsLoading and generally try to put as dependencies everything you use in your useEffect.
const onSearch = (searchKeyword) => {
setIsLoading(true);
setKeyword(searchKeyword);
};
useEffect(() => {
if (!keyword.length) {
return;
}
locationRequest(keyword.toLowerCase())
.then(locationTransform)
.then((result) => {
setIsLoading(false);
setLocation(result);
})
.catch((err) => {
setIsLoading(false);
setError(err);
});
}, [keyword, onSearch]);
BUT, at the end I wonder, why you use a useEffect?
Why don't you just move all the code in onSearch:
const onSearch = (searchKeyword) => {
setIsLoading(true);
// setKeyword(searchKeyword); // not needed
if (!searchKeyword.length) {
return;
}
locationRequest(searchKeyword.toLowerCase())
.then(locationTransform)
.then((result) => {
setIsLoading(false);
setLocation(result);
})
.catch((err) => {
setIsLoading(false);
setError(err);
});
};

React: String automatically converted to [object promise] when called from another component

I'm developing the front-end for my spring boot application. I set up an initial call wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
The data returned isn't comprehensive, and needs further call to retrieve other piece of information, for example this initial call return an employee id, but if I want to retrieve his name and display it I need a sub-sequential call, and here I'm experiencing tons of issues:
const getEmployeeName = async id => {
try {
const name = await fetchContext.authAxios.get(
'/employeeName/' + id
);
console.log((name["data"])); // <= Correctly display the name
return name["data"]; // return an [Object promise],
} catch (err) {
console.log(err);
}
};
I tried to wrap the return call inside a Promise.resolve() function, but didn't solve the problem. Upon reading to similar questions here on stackoverflow, most of the answers suggested to create a callback function or use the await keyword (as I've done), but unfortunately didn't solve the issue. I admit that this may not be the most elegant way to do it, as I'm still learning JS/React I'm open to suggestions on how to improve the api calls.
var output = Object.values(data).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Async functions always return promises. Any code that needs to interact with the value needs to either call .then on the promise, or be in an async function and await the promise.
In your case, you should just need to move your code into the existing useEffect, and setState when you're done. I'm assuming that the employeeID is part of the data returned by the first fetch:
const [name, setName] = useState('');
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
"/myapi/" + auth.authState.id
);
setData(data);
const name = await fetchContext.authAxios.get(
'/employeeName/' + data.employeeID
);
setName(name.data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
// ...
var output = Object.values(appointmentsData).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Note that the above code will do a rerender once it has the data (but no name), and another later when you have the name. If you want to wait until both fetches are complete, simply move the setData(data) down next to the setName

How to use an async database call to set a variable with useState() and useEffect()?

I'm trying to set a variable with a simple GET database call. The database call is returning the data correctly, but the variable remains undefined after every re-render. Code is below... the getMyThing() function in the useState() function is working correctly and returning the data I want.
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(getMyThing(id));
useEffect(() => {
setMyThing(myThing)
}, [myThing]);
}
My thinking here was to use useState() to set the initial state of the myThing variable with the data returned from my database. I assume it's not immediately working since a database call is asynchronous, so I thought I could use useEffect() to update the myThing variable after the response of the database call completes, since that would trigger the useEffect() function because I have the myThing variable included as a dependency.
What am I missing here? Thanks!
EDIT: Thanks for the answers everyone, but I still can't get it to work by calling the getMyThing function asynchronously inside useEffect(). Is something wrong with my database call function? I guess it's not set up to a return a promise? Here's what that looks like:
export const getMyThing = (id) => {
axios.get('http://localhost:4000/thing/' + id)
.then(response => {
return(response.data);
})
.catch(function (error){
console.log(error);
})
}
You should do all your side effects(fetching data, subscriptions and such) in useEffect hooks and event handlers. Don't execute async logic in useState as you just assign the promise itself to the variable and not the result of it. In any case, it is a bad practice and it won't work. You should either:
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await getMyThing(id);
setMyThing(result);
};
fetchData();
}, [id, getMyThing]);
}
Or if you don't want to introduce an async function:
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(null);
useEffect(() => {
getMyThing()
.then(result => setMyThing(result));
}, [id, getMyThing]);
}
Also, take note of the [id, getMyThing] part as it is important. This is a dependency array determining when your useEffect hooks are gonna execute/re-execute.
If getMyThing returns a Promise, the myThing will be set to that Promise on the first render, and then myThing will stay referring to that Promise. setMyThing(myThing) just sets the state to the Promise again - it's superfluous.
Call the asynchronous method inside the effect hook instead:
const [myThing, setMyThing] = useState();
useEffect(() => {
getMyThing(id)
.then(setMyThing);
}, []);
Here, myThing will start out undefined, and will be then set to the result of the async call as soon as it resolves.
You can't set the initial state with a value obtained asynchronously because you can't have the value in time.
myThing cannot both return the value you want and be asynchronous. Maybe it returns a promise that resolves to what you want.
Set an initial value with some default data. This might be null data (and later when you return some JSX from your component you can special case myThing === null by, for example, returning a Loading message).
const [myThing, setMyThing] = useState(null);
Trigger the asynchronous call in useEffect, much like you are doing now, but:
Make it rerun when the data it depends on changes, not when the data it sets changes.
Deal with whatever asynchronous mechanism your code uses. In this example I'll assume it returns a promise.
Thus:
useEffect(async () => {
const myNewThing = await getMyThing(id);
setMyThing(myNewThing)
}, [id]);

React : Updating and then accessing state after a network request in useEffect hook. State remains stale

Im trying to update and reference hasError state field inside of the initialization function of my component in order to control if a redirect happens after successful initialization or if error gets displayed.
Here is a condensed version of the issue:
const [hasError, setHasError] = useState(false);
useEffect(() => {
initialize();
}, []);
async function initialize(){
try {
await networkRequest();
} catch (err) {
setHasError(true);
}
console.log(hasError); // <- is still false
if(!hasError){
redirect() // <- causes redirect even with error
}
}
function networkRequest() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject();
}, 1000);
});
}
The initialization function should only be called once on component mount which is why I'm passing [] to useEffect. Passing [hasError] to useEffect also doesn't make sense since I don't want initialization to run everytime hasError updates.
I have seen people recommend using useReducer but that seems hacky since I'm already using Redux on this component and I'll need to use 2 different dispatch instances then.
How is a use case like this typically handled?
You will have to create another useEffect hook that "listens" for changes to hasError. setHasError in this case is asynchronous, so the new value won't be available to you immediately.
I don't know what the rest of your component looks like, but it sounds like you should have some sort of isLoading state that will be used to display a loading message, then once your request finishes and it fails, you render what you need, or if it succeeds, you redirect.
Here's an example:
function App() {
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
useEffect(() => {
(async () => {
try {
await networkRequest();
isLoading(false);
} catch (error) {
setHasError(error);
isLoading(false);
}
})()
}, [])
useEffect(() => {
if (!isLoading && !hasError) {
redirect();
}
}, [isLoading, hasError]);
if (isLoading) { return "Loading"; }
// else, do whatever you need to do here
}

Categories