React Fetching Request returns null after refresh - reactjs

So I'm working on a school project right now and I've created a backend using express and nodejs. I want to retrieve data and only get the questions that are associated with the current category. After retrieving the data it gives the data I want but then when I refresh the page it only gets null. What am I doing wrong?
Fetch Hook
import axios from 'axios';
export default function useFetch(name) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
(async function () {
try {
setLoading(true);
const response = await axios
.get('http://localhost:3001/api/getQuestions')
.then((res) => {
const dataArray = res.data;
const questionArray = dataArray.filter((question) => {
return question.questionCategory === 'installation';
});
setData(questionArray);
});
console.log(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
})();
}, [name]);
return { data, error, loading };
}
Quiz Component
import fetchQuestion from '../../../hooks/fetchQuestion';
const InstallationsQuiz = () => {
const { data, loading, error } = fetchQuestion('installation');
useEffect(() => {
data.map((item) => {
console.log(item);
});
}, [data]);

Related

useEffect dependency causes infinite loop

I created a custom hook which I use in App.js
The custom hook (relevant function is fetchTasks):
export default function useFetch() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [tasks, setTasks] = useState([]);
const fetchTasks = async (url) => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("falied!");
}
const data = await response.json();
const loadedTasks = [];
for (const taskKey in data) {
loadedTasks.push({ id: taskKey, text: data[taskKey].text });
}
setTasks(loadedTasks);
} catch (err) {
console.log(err.message);
}
setLoading(false);
};
return {
loading,
setLoading,
error,
setError,
fetchTasks,
tasks,
};
}
Then in my App.js:
function App() {
const { loading, setLoading, error, setError, fetchTasks, tasks } =
useFetch();
useEffect(() => {
console.log("fetching");
fetchTasks(
"https://.....firebaseio.com/tasks.json"
);
}, []);
My IDE suggests adding the fetchTasks function as a dependency to useEffect. But once I add it, an infinite loop is created. If I omit it from the dependencies as shown in my code, it will work as expected, but I know this is a bad practice. What should I do then?
Because that every time you call useFetch(). fetchTasks function will be re-created. That cause the reference to change at every render then useEffect() will detected that dependency fetchTasks is re-created and execute it again, and make the infinite loop.
So you can leverage useCallback() to memoize your fetchTasks() function so the reference will remains unchanged.
import { useCallback } from 'react'
export default function useFetch() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [tasks, setTasks] = useState([]);
const fetchTasks = useCallback(
async (url) => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("falied!");
}
const data = await response.json();
const loadedTasks = [];
for (const taskKey in data) {
loadedTasks.push({ id: taskKey, text: data[taskKey].text });
}
setTasks(loadedTasks);
} catch (err) {
console.log(err.message);
}
setLoading(false);
};,[])
return {
loading,
setLoading,
error,
setError,
fetchTasks,
tasks,
};
}
function App() {
const { loading, setLoading, error, setError, fetchTasks, tasks } =
useFetch();
useEffect(() => {
console.log("fetching");
fetchTasks(
"https://.....firebaseio.com/tasks.json"
);
}, [fetchTasks]);
instead of return fetchTasks function return this useCallback fetchTasksCallback function from useFetch hook which created only one instance of fetchTasksCallback.
const fetchTasksCallback = useCallback(
(url) => {
fetchTasks(url);
},
[],
);
function App() {
const { loading, setLoading, error, setError, fetchTasksCallback, tasks } =
useFetch();
useEffect(() => {
console.log("fetching");
fetchTasksCallback(
"https://.....firebaseio.com/tasks.json"
);
}, [fetchTasksCallback]);
the problem is this fetchTasks every time create a new instance that way dependency list feels that there is a change and repeats the useEffect code block which causes the infinite loop problem

How to process data received from an AJAX request in React

I have a custom hook named "useFetch" which makes an AJAX request and stores the result in the state. I simply want to format the data received from the ajax using a function in my component but not sure how to do this since the function needs to be called only after the data is received.
An example is below:
import React, { Component, useState } from "react";
import useFetch from "../../../Hooks/useFetch";
const Main = () => {
const { data, isPending, error } = useFetch(
"http://127.0.0.1:8000/api/historic/1"
);
function formatData(data){
//Do some processing of the data after it's been received
}
//This doesn't work of course because it runs before the data has been received
const formatted_data=formatData(data);
return (
//Some display using the formatted data
);
};
export default Main;
This is the custom hook, useFetch, which is used in the above component. I'd prefer to not have to do the formatting in here because the formatting is specifically related to the above component and this custom hook is designed to have more universal utility.
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isPending, setisPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch(url, { signal: abortCont.signal })
.then((res) => {
if (res.ok) {
return res.json();
} else {
throw Error("could not fetch data for that resource");
}
})
.then((data) => {
setData(data);
setisPending(false);
setError(null);
})
.catch((er) => {
if (er.name === "AbortError") {
console.log("fetch aborted");
} else {
setError(er.message);
setisPending(false);
}
});
return () => abortCont.abort();
}, [url]);
return { data, isPending, error };
};
export default useFetch;
You should wrap it with useEffect hook with data as it's deps.
const [formattedData, setFormattedData] = useState();
useEffect(() => {
if (!data) return;
const _formattedData = formatData(data);
setFormattedData(_formattedData);
}, [data]);

How to test component that uses custom hook with React-testing-library?

I have a custom hook to make async calls with setting errors, loadings etc.
import { useEffect, useState } from 'react';
const useMakeAsyncCall = ({ asyncFunctionToRun = null, runOnMount = false }) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true);
try {
const res = await asyncFunctionToRun();
const json = await res.json();
setResponse(json);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
};
useEffect(() => {
if (runOnMount && asyncFunctionToRun !== null) fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [runOnMount]);
return { response, error, loading, fetchData };
};
export default useMakeAsyncCall;
In component I am using it like this
const { error, isLoading, fetchData } = useMakeAsyncCall({
asyncFunctionToRun: () => signUpUser(),
runOnMount: false,
});
const signUpUser = () => {
...some requests to firebase
};
const handleSumbit = (e) => {
e.preventDefault();
fetchData();
};
Now I am trying to test this logic.
it('does things', async () => {
const { container, getByTestId } = render(<Component/>);
const form = getByTestId('form');
fireEvent.submit(form);
expect(container.firstChild).toMatchSnapshot();
});
And I'm getting this error Warning: An update to Component inside a test was not wrapped in act(...) and it is pointing to setError and setLoading inside my hook. How to go about fixing it and testing this functionality?

Dynamic SVG import not showing image using Typescript

I have created an Icon Component where it can load dynamic, seems like the SVG is not showing.
This is the sample
function useDynamicSVGImport(name: string) {
const ImportedIconRef = useRef<FunctionComponent<SVGProps<SVGSVGElement>>>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error>();
useEffect(() => {
setLoading(true);
const importIcon = async (): Promise<void> => {
try {
ImportedIconRef.current = (await import(`../assets/${name}.svg`)).ReactComponent;
console.log(`../assets/${name}.svg`);
} catch (err) {
console.log(err);
setError(err);
} finally {
setLoading(false);
}
};
importIcon();
}, [name]);
return { error, loading, SvgIcon: ImportedIconRef.current };
}
As per this Answer tsconfig.json still it's not working

React hooks - fetching data from api and passing to a component

So basically, I'm trying to fetch data from api and pass it to Component.
I create usePosition hook to get my positon from browser, and then get response from api. I really don't know how to wait with useEffect for my position, when i'm executing this code now I'm getting always log 'no position'.
const usePosition = () => {
const [error, setError] = useState(null);
const [position, setPosition] = useState();
useEffect(() => {
const geo = navigator.geolocation;
if(!geo) {
setError('Geolocation is not supported.');
return;
}
const handleSuccess = position => {
const { latitude, longitude } = position.coords;
setPosition({
latitude,
longitude
});
};
const handleError = error => {
setError(error.message);
};
geo.getCurrentPosition(handleSuccess, handleError);
}, []);
return { position, error };
}
function App() {
const {position, error} = usePositon();
const [weather, setWeather] = useState([]);
useEffect(() => {
if(position) {
const URL = `https://api.openweathermap.org/data/2.5/onecall?lat=${position.latitude}&lon=${position.longitude}&exclude=current,minutely,daily&units=metric&lang=pl&appid=${API_KEY}`;
const fetchData = async () => {
const result = await fetch(URL)
.then(res => res.json())
.then(data => data);
setWeather(result.hourly);
}
fetchData();
} else {
console.log('no position');
}
}, []);
return (
<div className="App">
<div>
<Swiper weather={weather}/>
</div>
</div>
)
}
It's all because of [] empty dependencies list down in App's useEffect. It runs exactly once on mount, when usePosition has not requested anything yet. And once it successes later and returns different { error, position } App does not react.
How to solve? Provide things as dependencies:
useEffect(() => {
if(position) {
const URL = `https://api.openweathermap.org/data/2.5/onecall?lat=${position.latitude}&lon=${position.longitude}&exclude=current,minutely,daily&units=metric&lang=pl&appid=${API_KEY}`;
const fetchData = async () => {
const result = await fetch(URL)
.then(res => res.json())
.then(data => data);
setWeather(result.hourly);
}
fetchData();
} else {
console.log('no position');
}
}, [position, error]);

Resources