I am writing a custom react hook for fetching data from an endpoint. This is what the function looks like
import { useState } from "react";
const useHttp = async (endpoint, method, data) => {
const [loading, setLoading] = useState(false)
const [fetchedData, setfetchedData] = useState(null)
setfetchedData(await fetch.method(endpoint));
return [isLoading, fetchedData]
}
export default useHttp;
As you can see, I want to do a fetch request to whatever method is passed on to the useHttp hook. Please someone point me how to do it?
You cannot pass async functions to React Hooks. You have to useEffect
import { useState, useEffect } from "react";
const useHttp = (endpoint, method, options) => {
const [isLoading, setLoading] = useState(false);
const [fetchedData, setFetchedData] = useState(null);
useEffect(() => {
setLoading(true);
fetch(endpoint, { method, ...options })
.then(data => data.json())
.then((json) => {
// do something with JSON data
setFetchedData(json);
})
.catch((err) => {
// do something with err
})
.finally(() => {
setLoading(false);
});
}, []);
return [isLoading, fetchedData];
};
export default useHttp;
Use useEffect hook to make the HTTP request.
fetch function takes an optional second argument which is an object specifying various options for the HTTP request and one of the options is a method option. Use this method option to specify the request method.
import { useState, useEffect } from "react";
const useHttp = async (endpoint, method, data) => {
const [loading, setLoading] = useState(false);
const [fetchedData, setfetchedData] = useState(null);
useEffect(() => {
setLoading(true);
fetch(endpoint, { method })
.then(res => res.json())
.then(data => {
setLoading(false);
setfetchedData(data);
})
.catch(err => {
setLoading(false);
console.log(err.message);
});
}, []);
return [isLoading, fetchedData];
}
For details on how to specify options for fetch function and different options that can be specified, see using fetch
If you want to use async-await syntax, you can write useEffect hook as:
useEffect(() => {
async function makeRequest() {
setLoading(true);
try {
const response = await fetch(endpoint, { method });
const data = await res.json();
setLoading(false);
setfetchedData(data);
} catch (error) {
setLoading(false);
console.log(err.message);
}
}
makeRequest();
}, []);
hi maybe this help you:
1- call function:
const useHttp = async (url,method,data)=>{
var options = {
method:method,
headers: {
'Content-Type': 'application/json; charset=utf-8;'
}
};
if(method==='POST' && data)
options.body = JSON.stringify(data);
const response = await fetch(url, options);
const rep = await response.json();
console.log(rep);
return rep;
};
in above code first create your request options and then send it by fetch to end point.
2- use it in compoent like below:
setLoading(true);
var rep = await useHttp(...)
setLoading(false);
Related
i need to call my custom hook called useAxios() inside function. As i know its not available in react, but i hope there's some method that do the trick
useAxios hook:
import { useState, useEffect } from "react";
import Axios from "axios";
const useAxios = (method: string, param: string, data: any) => {
const [response, setResponse] = useState([]);
const [error, setError] = useState([]);
const fetchData = () => {
Axios({
method: method,
url: `https://gernagroup-server.herokuapp.com/${param}`,
data: data,
})
.then((response) => {
setResponse(response.data);
})
.catch((err) => {
setError(err);
});
};
useEffect(() => {
fetchData();
}, []);
return { response, error };
};
export default useAxios;
I have to call this function, but using my hook
const handleNewSale = () => {
Axios.post("https://gernagroup-server.herokuapp.com/new-sale", {
data: selectedValues,
})
.then((response) => {
console.log(response);
showModal(ResultType.success, "New sale added successfully!");
})
.catch((err) => {
console.log(err);
showModal(ResultType.error, "Something went wrong");
});
};
I have to call this function on click
<Button text="New sale" onClick={handleNewSale} />
fetchData is async, you’re missing async and await keywords in code. Same for handleNewSale.
Avoid any types.
Give your custom hook a strict return type. Also you may need an export keyword in front of the ‘const’
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]);
I'm trying to stop axios request.
I use useInterval(custom hooks)(I referenced a website) to request api.
So I stop it with useState and it's totally stopped when i set interval like 1000ms.
however, when i set interval like 100ms then i can't stop api request. it's stopped after 3seconds or something.
So i tried to use if statement. but it's not working as i expected.
and I also checked Network from development tool on chrome
and the request Status was getting changed from pending to 200
and when all the request's Status change to 200, then it stopped.
I really want to know how i can stop API request properly.
my code is like this
useInterval
import { useEffect } from "react";
import { useRef } from "react";
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => {
savedCallback.current();
};
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
export default useInterval;
API calling
const [API_DATA, setAPI_DATA] = useState(null);
const [apiStart, setApiStart] = useState(false);
const [spinner, setSpinner] = useState(false);
//Request API
const getAPI = useCallback(async () => {
if (apiStart) {
await axios
.get(API_URL, {
headers: Header,
})
.then(response => {
setAPI_DATA(response.data);
setSpinner(false);
})
.catch(error => {
init();
console.log("error");
});
}
}, [API_DATA, spinner]);
// start API
const start_API = () => {
setSpinner(true);
setApiStart(true);
};
//stop API
const stop_API = () => {
setSpinner(false);
alert("API STOP");
setApiStart(false);
};
//using useInterval
useInterval(
() => {
if (apiStart) return getAPI();
},
apiStart ? 100 : null
);
Go take a look at the axios documentation at https://axios-http.com/docs/cancellation. I would remove the if(apiStart) as this does not do much. I would possibly rewrite your this method as follows:
const [data, setData] = useState(null);
const [spinnerActive, setSpinnerActive] = useState(false);
const controller = new AbortController();
const getAPI = useCallback(async () => {
setSpinnerActive(true);
await axios
.get(API_URL, {
headers: Header,
signal: controller.signal
})
.then(response => {
setData(response.data);
setSpinnerActive(false);
})
.catch(error => {
setSpinnerActive(false);
console.log("error");
});
}, [data, spinnerActive]);
useInterval(
() => {
getApi()
},
apiStart ? 100 : null
);
Then when you want to abort the request, call controller.abort()
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]);
I have a lot of react experience but I'm new to hooks.
I have the following useFetch hook that I modified after this useAsync hook:
import { useState, useEffect, useCallback } from 'react'
export default function useFetch(url, options, { immediate }) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [isPending, setIsPending] = useState(false)
const executeFetch = useCallback(async () => {
setIsPending(true)
setData(null)
setError(null)
await fetch(url, options)
.then((response) => response.json())
.then((response) => setData(response))
.catch((err) => setError(err))
.finally(() => setIsPending(false))
return { data, error, isPending }
}, [url, options, data, error, isPending])
useEffect(() => {
if (immediate) {
executeFetch()
}
}, [executeFetch, immediate])
return { data, error, isPending, executeFetch }
}
My problem is I want to use it inside a submit function, and hooks don't work inside other functions, like so (reduced version of the code for brevity):
export default function SignupModal({ closeModal }) {
const { executeFetch } = useFetch(url, {options},
{ immediate: false }
)
async function handleSubmit(evt) {
evt.preventDefault()
const { data, error, isPending } = await executeFetch()
}
...
}
currently I'm intentionaly throwing an error in the call, but the error variable remains null.
What am I missing here?
Is this even possible with hooks?
Thanks in advance!
React hook can only be used in the body of your component not inside another function. executeFetch itself is returning { data, error, isPending } and this makes it a nested hook so you can't use it inside your handleSubmit.
useFetch is already returning { data, error, isPending, executeFetch } so executeFetch doesn't need to return again. You can access all these data from the useFetch hook. When you call executeFetch data in your component, data, error and isPending will be updated by setState which will cause your hook to return a new set of values for any of these values that get updated.
export default function useFetch(url, options, { immediate }) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [isPending, setIsPending] = useState(false)
const executeFetch = useCallback(async () => {
setIsPending(true)
setData(null)
setError(null)
await fetch(url, options)
.then((response) => response.json())
.then((response) => setData(response))
.catch((err) => setError(err))
.finally(() => setIsPending(false))
}, [url, options, data, error, isPending])
useEffect(() => {
if (immediate) {
executeFetch()
}
}, [executeFetch, immediate])
return { data, error, isPending, executeFetch }
}
export default function SignupModal({ closeModal }) {
const { executeFetch, data, error, isPending } = useFetch(url, {options},
{ immediate: false }
)
async function handleSubmit(evt) {
evt.preventDefault()
await executeFetch()
}
...
// Example in your return function
{error != null && <Error />}
<Button state={isPending ? 'processing' : 'normal'}
}
Updated based on the comment
If you need to have an access to data or error inside your handleSubmit function, you will need to return the promise's response/error in your hook so then you should be able to access data/error inside your handleSubmit as well.
Also I recommend to pass options or any other variable data that are subject to change before user triggers handleSubmit to the executeFetch as an argument so executeFetch can always get the latest data.
CodeSandBox Example 1
CodeSandBox Example 2
const useFetch = url => {
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const [data, setData] = useState(null);
const executeFetch = useCallback(
// Here you will access to the latest updated options.
async ({ options }) => {
setIsPending(true);
setError(null);
return await fetch(url, options)
.then(response => response.json())
.then(response => {
setData(response);
return response;
})
.catch(err => {
setError(err.message)
return err;
})
.finally(() => setIsPending(false));
},
[url, setIsPending, setError]
);
return { data, error, isPending, executeFetch }
};
const { data, executeFetch, error, isPending } = useFetch("URL");
const handleSubmit = useCallback(async (event) => {
event.preventDefault();
// I am passing hardcoded { id: 1 } as an argument. This can
// be a value from the state ~ user's input depending on your
// application's logic.
await executeFetch({ id: 1 }).then(response => {
// Here you will access to
// data or error from promise.
console.log('RESPONSE: ', response);
})
}, [executeFetch]);
Another recommendations is to not pass a boolean to trigger executeFetch immediately inside your hook, it's up to the caller to decide whether to run the executeFetch immediately or not.
const { executeFetch, ... } = useFetch(....);
// you can call it immediately after setting the hook if you ever needed
await executeFetch()