react page doesnt render when getting the data back from server - reactjs

react won't work when rendering the page, but when i changed code in the vscode (added a line of console or comment one line out), the page is rendered. or when page is not rendered. when i hit refresh. i can see some of my content but it won't render. the usestate doesnt seem like to successfully save the value
const ParkDetail = () => {
const [parkId, setParkId] = useState('')
const [park, setpark] = useState('')
const [areas, setAreas] = useState([])
const [ridesName, setridesName] = useState([])
const [rides, setrides] = useState([])
let { id } = useParams()
console.log(id)
useEffect(() => {
async function getPark() {
try {
await setParkId(id)
const res = await axios.get(`/parks/details/${parkId}`)
console.log(res)
const park1 = await res.data.park
console.log(park1)
await setpark(park1)
console.log(park)
await setAreas(park1.serviceAnimalRelief)
// await setridesName(park1.topRides)
// console.log(ridesName)
} catch (e) {
console.log(e.message)
}
}
getPark()
}, [parkId])
}

I believe the problem is you using the state as parameters in your get requests. Since state setter functions do not return promises, using await on them is basically of no use. You should rather use the variables that you obtain from the get requests directly. For example, use id instead of parkId and so on.

Related

fetch vs. axios - what is the difference in error handling?

I have an app where I'm updating the API URL via searchbox and hence displaying certain items based on their name. The name parameter is being updated via onChange event in the input and then useEffect re-renders the list of the items as it observes the URL.
If the name parameters does not match any items, the page should show that no data was found.
Now, the thing is, that the feature works when I'm using fetch API as even if there's 404 code status, the fetchedData state seems to still get updated with the object below:
fetch
and "no data found" can be displayed.
However, when I use axios, fetchedData keeps the latest displayable list of items and I'm not able to show the communicate. Here's what I see in the console:
enter image description here
const [fetchedData, setFetchedData] = useState('');
const [search, setSearch] = useState('');
const { info, results } = fetchedData;
const url = `https://rickandmortyapi.com/api/character/?name=${search}`;
// useEffect(() => {
// (async function () {
// try {
// const response = await axios.get(url);
// console.log(response.data);
// setFetchedData(response.data);
// } catch (err) {
// console.log(err);
// }
// })();
// }, [url]);
useEffect(() => {
(async function () {
const response = await fetch(url);
const data = await response.json();
console.log(data);
setFetchedData(data);
})();
}, [url]);
Does anyone have any idea what's behind it and how that mechanism can be implemented with axios? When working the axios way, I tried to set fetcheData state to null in case of error but this did not work.

useEffect is causing infinite loop when use state as dependency

Here simply I am fetching data from mysql DB and storing it in state and in order to fetch this data:
const [orders, setOrders] = useState([]);
To fetch data I am using different functions and finally I am calling those functions using useEffect simple enough and so for everything is working perfectly but the problem comes whenever I use the state as dependency where I am storing data beacause if I dont do that then I have to manually refresh the page for latest changes and I have tried every given solution on stackoverflow but any of the solution didnt work so someone can please help me how can I use this state as dependencey without causing infinite loop:
const [orders, setOrders] = useState([]);
const loadData = async () => {
const response = await fetch("http://localhost/k-shop/load.php");
const result = await response.json();
setOrders(result);
};
const loadTotal = async () => {
const response = await fetch("http://localhost/k-shop/amount.php");
const result = await response.json();
setTotal(result);
};
useEffect(() => {
loadData();
loadTotal();
}, [orders]);
console.log(orders);
If you move the state into the useEffect dependency, you can then check if it is empty, and only set it when that check passes.
It will set the state once to populate and not pass the check again.
const [orders, setOrders] = useState([]);
const loadData = async () => {
const response = await fetch("http://localhost/k-shop/load.php");
const result = await response.json();
setOrders(result);
};
const loadTotal = async () => {
const response = await fetch("http://localhost/k-shop/amount.php");
const result = await response.json();
setTotal(result);
};
useEffect(() => {
if(orders.length === 0) {
loadData();
}
// you can do the same with checking loadTotal() state
}, [orders]);
console.log(orders);
Avoid ,non-primitive data types in dependencyArray ,
useEffect(() => {
loadTotal();
loadData();
}, [total, orders.length]);
every times you "setOrders" means you change the state,every times you change the state,means the "useEffect" will do again.that cause infinite loops.why not try useEffect(() => {loadData()}, [])?

How to display custom Nextjs error page when api call fails?

I created a custom Nextjs error page that I would like to display when the api call fails. What is currently happening is even if the api call fails, it still displays the same page as a successful route. For example, I have a route that is companies/neimans that pulls data from an api to display certain text on the page. If I type, companies/neiman I want my custom error page to show, but it is displaying the same page as if going to companies/neimans just without the data coming from the api. I do get a 404 in the console when visiting a url that is invalid but it doesn't display the custom error page or the default next js 404 page.
In my file system I have a pages directory and inside that a directory called companies with a file [companydata].tsx and one called 404.tsx. [companydata].tsx is the page that dynamically displays information about the company from the api.
This is what my api call currently looks like:
export const getCompanies = async (routeData: string): Promise<Company> => {
const client = getApiClient();
const response = await client.get<Company>(`api/companies/${routeData}`);
if (response) {
return response.data;
}
return {} as Company;
In the [companydata].tsx, I tried to do a check if the object was empty to then redirect to companies/404 but doing so makes it always display the 404 page.
if (Object.keys(company).length === 0) {
return <Redirect to="/company/404"/>;
}
If I console.log the company, it is rendering multiple times. The first 6 times, it is an empty array so that would explain why the 404 page is always showing. The data doesn't come through until after the 6th render. I am not sure why that is.
I am calling getCompanies inside another function,
export const getData = async (companyName: string): Promise<[Company, Sales]> => {
if (companyName) {
return (await Promise.all([getCompanies(companyName), getSales()])) as [
Company,
Sales
];
}
return [{} as Company, {} as Sales];
};
I am calling getData inside a useEffect within [companydata].tsx.
const Company: NextPage = (): JSX.Element => {
const [selectedCompany, setSelectedCompany] = useState<Company>({} as Company);
const [salesAvailable, setSalesAvailable] = useState<boolean>(false);
const [sales, setSales] = useState<Sales>({} as Sales);
const router = useRouter();
const {companydata} = router.query;
useEffect(() => {
const init = async (companyName: string) => {
const [companyData, salesData] = await getData(companyName);
if (companyData) {
setSelectedCompany(companyData);
}
if (salesData) {
setSalesAvailable(true);
setSales(salesData);
} else {
setSalesAvailable(false);
}
}
};
init(companydata as string);
};
}, [companydata]);
// returning company page here
You currently do not have a method to check the status of the API call. There are four possible outcomes of most API calls - data, no data, error, and loading. You should add the status checks in your API calls
Below are two examples of how this can be achieved.
get companies hook
export const useGetCompanies = async (path: string) => {
const [data, setData] = useState<Company>();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
try {
setError(false);
setLoading(true);
const client = getApiClient();
const response = await client.get(`api/companies/${path}`);
setData(response.data);
} catch (error) {
setError(true);
} finally {
setLoading(false);
}
return {data, error, loading};
};
Since your data isn't related you also do a generic API fetch call something like
export async function useFetchData<T>(path:string){
const [data, setData] = useState<T>();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
try {
setError(false);
setLoading(true);
const client = getAPIClient();
const response = await client.get<{ data: T }>(path);
if(response) setData(response.data);
} catch (error) {
setError(true);
} finally {
setLoading(false);
}
return { data, error, loading };
};
Example use.
const Company = async () =>{
const { query } = useRouter();
const company = await useFetchData<Company>(`api/companies/${query.companydata}`);
const sales = await useFetchData<Sales>(`api/companies/${query.companydata}/sales`);
if (company.loading || sales.loading) return <p>Loading...</p>;
if (company.error || sales.error) return <p>Error or could show a not found</p>;
if (!company.data || !sales.data) return <Redirect to="/company/404"/>;
return "your page";
}
It would be best to render the data independently of each other on the page and do the if checks there. This is beneficial because you don't have to wait for both calls to complete before showing the page.
I'd create two separate components (company and sales) and place the corresponding API call in each.
Typically assigning empty objects ({} as Company or {} as Sales) to defined types is bad practice because it makes TS think the object's values are defined when they are not - defeating the purpose of using TS.
They should be left undefined, and there should be a check to see if they are defined.
Lastly, I can't test the code because I don't have access to the original code base so there might be bugs, but you should get a pretty good idea of what's happening.

React custom fetch hook is one step behind

I am creating my custom fetch hook for both get and post data. I was following official React docs for creating custom fetch hooks, but it looks like my hook-returned state variables are one step behind behind due to useState asynchronous behaviour. Here is my custom useMutation hook
export const useMutationAwait = (url, options) => {
const [body, setBody] = React.useState({});
const [data, setData] = React.useState(null);
const [error, setError] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
const fetchData = async () => {
setError(null);
setIsLoading(true);
console.log("...fetching");
try {
const response = await axiosInstance.post(url, body, options);
setData(response.status);
} catch (error) {
console.error(error.response.data);
setError(error.response.data);
}
setIsLoading(false);
};
fetchData();
}, [body]);
return [{ data, isLoading, error }, setBody];
};
And I am using it in my component like this (simplified) - when user presses register button I want to be able immediately decide if my post was successful or not and according to that either navigate user to another screen or display fetch error.
const [email, setEmail] = React.useState('');
const [password, setPassword] React.useState('');
const [{ data: mutData, error: mutError }, sendPost] =
useMutationAwait("/auth/pre-signup");
const registerUser = async () => {
sendPost({
email,
password,
}); ---> here I want to evaluate the result but when I log data and error, the results come after second log (at first they are both null as initialised in hook)
Is this even correct approach that I am trying to achieve? Basically I want to create some generic function for data fetching and for data mutating and I thought hooks could be the way.
Your approach isn't wrong, but the code you're sharing seams to be incomplete or maybe outdated? Calling sendPost just update some state inside your custom hook but assuming calling it will return a promise (your POST request) you should simply use async-await and wrap it with a try-catch statement.
export const useMutationAwait = (url, options) => {
const sendPost = async (body) => {
// submit logic here & return request promise
}
}
const registerUser = async () => {
try {
const result = await sendPost({ login, password });
// do something on success
} catch (err) {
// error handling
}
}
Some recommendations, since you're implementing your custom hook, you could implement one that only fetch fetch data and another that only submit requests (POST). Doing this you have more liberty since some pages will only have GET while others will have POST or PUT. Basically when implementing a hook try making it very specific to one solution.
You're absolutely correct for mentioning the asynchronous nature of state updates, as that is the root of the problem you're facing.
What is happening is as follows:
You are updating the state by using sendPost inside of a function.
React state updates are function scoped. This means that React runs all setState() calls it finds in a particular function only after the function is finished running.
A quote from this question:
React batches state updates that occur in event handlers and lifecycle methods. Thus, if you update state multiple times in a handler, React will wait for event handling to finish before re-rendering.
So setBody() in your example is running after you try to handle the response, which is why it is one step behind.
Solution
In the hook, I would create handlers which have access to the data and error variables. They take a callback (like useEffect does) and calls it with the variable only if it is fresh. Once it is done handling, it sets it back to null.
export const useMutationAwait = (url, options) => {
const [body, setBody] = React.useState({});
const [data, setData] = React.useState(null);
const [error, setError] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
const handleData = (callback) => {
if (data){
callback(data);
setData(null);
}
}
const handleError = (callback) => {
if (error){
callback(error);
setError(null);
}
}
React.useEffect(() => {
const fetchData = async () => {
setError(null);
setIsLoading(true);
console.log("...fetching");
try {
const response = await axiosInstance.post(url, body, options);
setData(response.status);
} catch (error) {
console.error(error.response.data);
setError(error.response.data);
}
setIsLoading(false);
};
fetchData();
}, [body]);
return [{ data, handleData, isLoading, error, handleError }, setBody];
};
We now register the handlers when the component is rendered, and everytime data or error changes:
const [
{
data: mutData,
handleData: handleMutData,
error: mutError,
handleError: handleMutError
}, sendPost] = useMutationAwait("/auth/pre-signup");
handleMutData((data) => {
// If you get here, data is fresh.
});
handleMutError((error) => {
// If you get here, error is fresh.
});
const registerUser = async () => {
sendPost({
email,
password,
});
Just as before, every time the data changes the component which called the hook will also update. But now, every time it updates it calls the handleData or handleError function which in turn runs our custom handler with the new fresh data.
I hope this helped, let me know if you're still having issues.

setState never gets set in useEffect after API call responds with data

I'm trying to update state immediately after data comes in from the API. The data is coming in, I can see it using the console.log right below my API request. All of the data is right but for some reason, setState never sets in my hook. It just returns and empty array even after the console displays data.
const [experienceData, setExperienceData] = useState([]);
const { match = {} } = props;
useEffect(() => {
async function fetchData() {
if (Object.keys(match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData(response)
}
}
fetchData();
}, []);
I must be doing something wrong but I can't figure out what that is. Hoping someone on here has run into the same issue.
UPDATE: I just changed everything over the a class and duplicated the exact code on another file and ran into the exact same issue. The console updates with the data, but the setState on the line immediately after the data does not setState.
async componentDidMount() {
if (Object.keys(this.props.match.params).length > 0) {
const response = await ApiService.getExperiences(this.props.match.params.experieneId);
console.log(response[0])
this.setState({ experienceData: response[0], occurrenceData: response[0].occurrences });
}
}
You have to useSetState in a proper way, the issue is in the setExperienceData
const [experienceData, setExperienceData] = useState({response:""});
const { match = {} } = props;
useEffect(() => {
async function fetchData() {
if (Object.keys(props.match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData(experienceData => ({ ...experienceData, response: response }));
}
}
fetchData();
}, []);
return(<div>check {experienceData.response}</div>)
I see you left the dependency array empty. This tells React to run this effect only once: when the component first renders. If you want your useEffect to respect your state hook, put setExperienceData inside the dependency array
const [experienceData, setExperienceData] = useState([]);
const { match = {} } = props;
useEffect(() => {
fetchData();
}, [props.match.params]);
const async fetchData = () => {
if (Object.keys(match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData([...response])
}
}
Could you please try passing [match.params] as the second argument to your useEffect.

Resources