I would like to use axios to fetch the values from API in react.js, and set it as a form, but it doesn't display any fetched data at all.
export default function Review() {
const [fetchedData, setFetchedData] = useState([]);
const [fetchedlanguage, setlanguage] = useState([]);
useEffect(() => {
const getStudent = async () => {
const stu = await axios.get('http://localhost:8000/students/');
setFetchedData(stu.data.students[0]);
setlanguage(stu.data.students[0].languages)
};
getStudent()
},[]);
console.log("student: ", fetchedData);
const [formdata, setformdata] = useState({
availability: 6,
preference:'201, 301',
experience:'201',
language:fetchedlanguage[0],
background:fetchedData.background,
});
Even though the console.log shows the data correctly, when I set the form here, how come there is no updates on data?
Control it all in one place. You will want to spread the original values over the setformdata because it's immutable. I'm not sure what all the API returns so continue to override each formdata property that you get back from the API.
export default function Review() {
const [formdata, setformdata] = useState({
availability: 6,
preference:'201, 301',
experience:'201',
language: 'english',
background: 'initial-background',
});
useEffect(() => {
const getStudent = async () => {
const stu = await axios.get('http://localhost:8000/students/');
const student = stu.data.students.length > 0 ? stu.data.students[0] : {};
setFormData({
...formdata,
langauge: student.languages,
// TODO: continue to override the formData from student returned from API
});
};
getStudent()
}, []);
// TODO: use formdata to feed into form
return null;
}
Related
SO this is my code, i'm trying to filter my offers by there users , i have called all my offers and all my user and there states are full but when i try to filter offers by there users the state stay empty but when i hit spacebar on my keyboard the state get full like it's the spacebar is triggering useEffect to fill the state
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const[useroffers,setUseroffer]=useState([]);
const isOffer = async () => {
const oflg = await GetAllOff();
setOffer(oflg);
};
const isLoggedIn = async () => {
const userLg = await CurrentUser();
setUser(userLg.data.user);
};
const isUseroffer = async()=>{
setUseroffer(offer.filter((el)=>el.createdbyId === user._id));
};
useEffect( () => {
isOffer();
isLoggedIn();
isUseroffer();
}, []);
console.log(offer);
console.log(user)
console.log(useroffers);
So useEffect is filling the offers and user States but not filling the useroffers state intil i click on the spacebar
useroffers is dependent on both user and offer but you are trying to set it in the same render cycle as those two. Updated state values aren't available until the render cycle after they are set, so setUseroffers doesn't have access to the values it needs to update properly. To solve this you can declare a second useEffect which is dependent on user and offer so that as those values update so does your filtered array.
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const [useroffers, setUseroffer] = useState([]);
const isOffer = async () => {
const oflg = await GetAllOff();
setOffer(oflg);
};
const isLoggedIn = async () => {
const userLg = await CurrentUser();
setUser(userLg.data.user);
};
useEffect(() => {
isOffer();
isLoggedIn();
}, []);
useEffect(() => {
setUseroffer(offer.filter((el) => el.createdbyId === user._id));
}, [user, offer]);
codesandbox
Alternatively you can do it all in a single useEffect by awaiting the offer and user values and using them directly to set all three states once they are available. (This will result in only a single rerender rather than the possible four in the previous example)
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const [useroffers, setUseroffer] = useState([]);
useEffect(() => {
const login = async () => {
const userLg = await CurrentUser();
const ofLg = await GetAllOff();
setUser(userLg.data.user);
setOffer(ofLg);
setUseroffer(
ofLg.filter((el) => el.createdbyId === userLg.data.user._id)
);
};
login();
}, []);
codesandbox
I want to fetch metadata from a website, then upload it to the database.
The website link comes from a form input field.
Since useState update is async, the data is not yet present in the formData object on submit.
What options do I have?
npm package in use: https://www.npmjs.com/package/suq
const [title, setTitle] = useState("");
const [url, setUrl] = useState("");
const [imgUrl, setImgUrl] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
suq(
`${urlToPost}`,
function (err, json, body) {
if (!err) {
setTitle(json.opengraph["og:title"]);
setUrl(json.opengraph["og:url"])
setImgUrl(json.opengraph["og:image"]);
}
}
);
const postData = {
title,
url,
imgUrl
};
db.collection("posts")
.doc()
.set(postData)
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
}
what about this:
const [title, setTitle] = useState("");
const [url, setUrl] = useState("");
const [imgUrl, setImgUrl] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
let submitTitle = '';
let submitUrl = '';
let submitImgUrl = '';
suq(
"https://www.space.com/first-structures-in-universe-revealed",
function (err, json, body) {
if (!err) {
submitTitle = json.opengraph["og:title"];
submitUrl = json.opengraph["og:url"];
submitImgUrl = json.opengraph["og:image"];
setTitle(submitTitle);
setUrl(submitUrl)
setImgUrl(submitImgUrl);
}
}
);
const formData = {
title: submitTitle,
url: submitUrl,
imgUrl: submitImgUrl
};
console.log(formData);
// title, url and imgUrl are still at initial value
}
the local state updates will be available in the next render but the values you need will be available to you in your handle submit function
We can't handle the wait for the state update directly in the same call.
As the state will update its value after the re-render cycle.
So it's upon you what would you like to do with the updated state so I can help you.
My fetch hook:
import { useEffect, useState } from "react";
export const useOurApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const [fetchedData, setFetchedData] = useState(initialData);
useEffect(() => {
let unmounted = false;
const handleFetchResponse = response => {
if (unmounted) return initialData;
setHasError(!response.ok);
setIsLoading(false);
return response.ok && response.json ? response.json() : initialData;
};
const fetchData = () => {
setIsLoading(true);
return fetch(url, { credentials: 'include' })
.then(handleFetchResponse)
.catch(handleFetchResponse);
};
if (initialUrl && !unmounted)
fetchData().then(data => !unmounted && setFetchedData(data));
return () => {
unmounted = true;
};
}, [url]);
return { isLoading, hasError, setUrl, data: fetchedData };
};
I call this hook in a function like so:
//states
const [assignments, setAssignments] = useState([])
const [submissions, setSubmissions] = useState([])
const [bulk_edit, setBulk_edit] = useState(false)
const [ENDPOINT_URL, set_ENDPOINT_URL] = useState('https://jsonplaceholder.typicode.com/comments?postId=1')
let url = 'https://jsonplaceholder.typicode.com/comments?postId=1';
const { data, isLoading, hasError } = useOurApi(ENDPOINT_URL, []);
My question is how can I call this instance of userOurAPI with a different URL. I have tried calling it within a function where I need it but we can't call hooks within functions, so I am not sure how to pass it new url to get new data. I don't want to have many instances of userOurAPI because that is not DRY. Or is this not possible? New to hooks, so go easy on me!
In order to change the URL such that the component updates and fetches new data, you create a set function that changes the URL and you make sure that the useEffect() is run again on the change of URL. Return your setter function for URL so that you can use it outside of the first instance of your hook. In my code, you will see that I return a setUrl, I can use that to update fetch! Silly of me not to notice, but hopefully this will help someone.
You could do it the way you chose to, but there are other ways of working around such a problem.
One other way would be to always re-fetch whenever the URL changes, without an explicit setter returned from the hook. This would look something like this:
export const useOurApi = (url, initialData) => { // URL passed directly through removes the need for a specific internal url `useState`
// const [url, setUrl] = useState(initialUrl); // No longer used
// ...
useEffect(() => {
// Handle fetch
}, [url]);
return { isLoading, hasError, data: fetchedData }; // No more `setUrl`
};
This may not always be what you want though, sometimes you may not want to re-fetch all the data on every url change, for example if the URL is empty, you may not want to update the url. In that case you could just add a useEffect to the useOurApi custom hook to update the internal url and re-fetch:
export const useOurApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
// ...
useEffect(() => {
// Handle fetch
}, [url]);
useEffect(() => {
// ... do some permutation to the URL or validate it
setUrl(initialUrl);
}, [initialUrl]);
return { isLoading, hasError, data: fetchedData }; // No more `setUrl`
};
If you still sometimes want to re-fetch the data, unrelated to the URL, you could output some function from the hook to trigger the data fetching. Something like this:
export const useOurApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const [fetchedData, setFetchedData] = useState(initialData);
const refetch = useCallback(() => {
// Your fetch logic
}, [url]);
useEffect(() => {
refetch(); // In case you still want to automatically refetch the data on url change
}, [url]);
return { isLoading, hasError, refetch, data: fetchedData };
};
Now you can call refetch whenever you want to trigger the re-fetching. You may still want to be able to internally change the url, but this gives you another a bit more flexible access to the fetching and when it occurs.
you confuse the difference between simple function and function component
Function Component are not just simple function. It means that the have to return a component or a html tag
I think you should turn four function to simple function like so
export const useOurApi = (initialUrl, initialData) => {
let url = initialUrl, fetchedData = initialData,
isLoading= true, hasError = false, unmounted = false;
const handleFetchResponse = response => {
if (unmounted) return initialData;
hasError = !response.ok;
isLoading = false;
return response.ok && response.json ? response.json() : initialData;
};
const fetchData = () => {
isLoading = true;
return fetch(url, { credentials: 'include' })
.then(handleFetchResponse)
.catch(handleFetchResponse);
};
if (initialUrl && !unmounted)
fetchData().then(data => {
if(!unmounted) fetchedData =data;
unmounted = true;
});
return { isLoading, hasError, url, data: fetchedData };
};
I'm trying to sign a user in, and update my global context with the user data. To keep the user signed in I'm storing their data in local storage.
I'm using react-hooks to take care of the state, hence I have defined a state: let [userData, setUserData] = useState({});.
Since I wan't to keep the user signed in I store their data in local storage during sign in. This works and the data does in fact get stored in local storage.
My problem is however that I can't set the initial userData state equal to the current data from local storage. In other words the userData state gets reset to default on reload.
I thought that getting the initial data from local storage and assigning it to state inside the useEffect hook would work. But the state does not update when calling setUserData inside useEffect.
AuthContext.js:
import React, { createContext, useState, useEffect } from 'react';
export const AuthContext = createContext();
const AuthContextProvider = props => {
let [userData, setUserData] = useState({});
const loginUser = (data) => {
localStorage.setItem('userData', JSON.stringify({
key: data.key,
id: data.id,
email: data.email,
first_name: data.first_name,
last_name: data.last_name
})); // Save the user object in local storage
setUserData({
key: data.key,
id: data.id,
email: data.email,
first_name: data.first_name,
last_name: data.last_name
}); // Set user data
};
const logoutUser = () => {
localStorage.removeItem('userData');
setUserData({}); // Empty user data state
newToast('Successfully signed out');
};
useEffect(() => {
const localUser = JSON.parse(localStorage.getItem('userData'));
if (localUser && localUser.key) {
setUserData({
key: localUser.key,
id: localUser.id,
email: localUser.email,
first_name: localUser.first_name,
last_name: localUser.last_name
}); // Set user data
}
}, [])
return (
<AuthContext.Provider value={{ userData, loginUser, logoutUser, newToast }}>
{props.children}
</AuthContext.Provider>
)
}
export default AuthContextProvider;
Signin.js:
const Signin = props => {
let [loading, setLoading] = useState(false);
let [formError, setFormError] = useState(false);
const { userData, loginUser, newToast } = useContext(AuthContext);
const { register, handleSubmit, errors, setError, clearError } = useForm();
const onSubmit = e => {
setLoading(true);
setFormError(false);
clearError(); // Clear all erros on form
axios
.post('users/auth/login/', {
headers: { 'Content-Type': 'application/json' },
email: `${e.email}`,
password: `${e.password}`,
})
.then(res => {
const { data } = res
loginUser(data);
newToast('Successfully signed in');
})
.catch((error) => {
const { data } = error.response;
console.log(data);
data.email && setError("email", "", data.email);
data.password1 && setError("password", "", data.password1);
setFormError(true);
})
setLoading(false);
};
return ( ... );
}
Updated answer (Aug. 15, 2022):
Since accessing the local storage on every render is expensive, it is preferred to only access it during the initial render (see Wayne Ellery's comment).
So quoting Erol's solution:
const [user, setUser] = useState([], () => {
const localData = localStorage.getItem('userData');
return localData ? JSON.parse(localData) : [];
});
Original answer:
So I figured out a solution!
In AuthContext.js i didn't need to assign the state in useEffect.
Instead I get the initial data directly when defining the state hooks:
const localUser = JSON.parse(localStorage.getItem('userData')) || {};
let [userData, setUserData] = useState(localUser);
That way I don't need the useEffect hook at all.
I hope this is the recommended way of doing it.
If I understand your question, you could do the following:
const [user, setUser] = useState([], () => {
const localData = localStorage.getItem('userData');
return localData ? JSON.parse(localData) : [];
});
How about to use useReducer like this?
const [user, setUser] = useReducer((prev, cur) => {
localStorage.setItem('userData', JSON.stringify(cur));
return cur;
}, JSON.parse(localStorage.getItem('userData')));
You can call
setUser({ key: '1', ... });
I've successfully implemented a useFetch function to call an API Endpoint. It works perfectly if I add code like this to the root of a functional React component like this:
const [{ data, isLoading, isError }] = useFetch(
'http://some_api_endpoint_path'
);
export const useFetch = (url) => {
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const response = await axios.get(url);
setData(response.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return [{ data, isLoading, isError }];
};
But let's say I want to check if a newly entered username exists, say upon the firing of an onBlur event of an input element. When I've tried implementing this, I get this error:
React Hook "useFetch" is called in function "handleBlur" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
I even tried this approach:
const [isChanged, setIsChanged] = useState(false);
useEffect(() => {
useFetch(
'http://some_api_endpoint_path'
);
}, [isChanged]);
But got the same error.
Then I tried this simplified version, which doesn't do anything useful but I was testing the React Hooks Rules:
useEffect(() => {
useFetch(
'http://some_api_endpoint_path'
);
}, []);
And still I got the same error.
In these last 2 cases especially, I feel that I am following the Rules of Hooks but apparently not!
What is the correct way to call useFetch in such a situation?
I suppose you call useFetch this way, right?
const onBlur = () => {
const [{ data, isLoading, isError }] = useFetch(
'http://some_api_endpoint_path'
);
...
}
If true, this is wrong. Check this link out:
🔴 Do not call in event handlers.
You may implement this way:
// Pass common initial for all fetches.
export const useFetch = (awsConfig, apiRoot, apiPathDefault) => {
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
// Just pass the variables that changes in each new fetch requisition
const fetchData = async (apiPath) => {
setIsError(false);
setIsLoading(true);
try {
const response = await axios.get(apiRoot + apiPath);
setData(response.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
useEffect(() => {
fetchData(apiRoot + apiPathDefault);
}, [awsConfig, apiRoot, apiPathDefault]);
return [{ data, isLoading, isError }, fetchData];
};
And whenever you want to fetch again, you just call fetchData:
const [{ data, isLoading, isError }, fetchData] = useFetch(API_ROOT(), appStore.awsConfig, defaultPath);
const onBlur = () => {
fetchData(newPath);
...
}
I've used the same principle that Apollo team used when created useLazyQuey (open this link and search for useLazyQuery, please). Also, note that I pass all common and immutable variables when I call the hooks and pass just the mutable ones in the single fetch.