Reputation: 31
Here is my code:
const useMyFetch = (url, options) =>
{
const [response, setResponse] = React.useState(null);
React.useEffect(() =>
{
console.log("going to fetch ", url);
fetch(url, options).then(async function(response)
{
var json = await response.json();
setResponse(json.message);
});
}, [ url ]);
return response;
};
function Example()
{
const res = useMyFetch("https://dog.ceo/api/breeds/image/random", { method: 'GET' });
if (!res)
{
return <div>loading...</div>
}
return <img src={res} alt="an image" />;
}
It looks that everything is fine... except when I replace the second argument of useEffect from [ url ] to [ url, options ]. When 'options' is there, we're entering in the well known infinite loop... however it's logical to have it in this array. What's wrong here? Thanks
Upvotes: 1
Views: 1260
Reputation: 23705
As @Titus already mentioned it's because { method: 'GET' }
is referentially different each new render. But I believe moving that out the component is not flexible enough in real life. Need to pass token to headers or any other dynamic calculation is rather expected requirement, right?
Option 1. We may use JSON.stringify
to pass parameter object as a string:
function useMyFetch(url, options) {
useEffect(() => {
}, [url, JSON.stringify(options)])
}
Option 2. We may use useRef
to store previous props and use custom comparison:
function useMyFetch(url, options) {
const prevOptions = useRef(null);
useEffect(() => {
...
prevOptions.current = options;
}, [url, customCompare(options, prevOptions.current)])
}
Option 3. And finally me may create custom hook that would return referentially same object if all nested properties are equal:
function useObject(obj) {
const prevObj = useRef(obj);
const isEqual = _.isEqual(prevObj.current, obj);
useEffect(() => {
prevObj.current = obj;
}, [isEqual]);
return isEqual ? prevObj.current : obj;
}
and use it later as
function Example()
{
const requestOptions = useObject({ method: 'GET' });
const res = useMyFetch("https://dog.ceo/api/breeds/image/random", requestOptions);
if (!res)
{
return <div>loading...</div>
}
return <img src={res} alt="an image" />;
}
Option 4. Most straightforward way is to decompose object options
into primitive values:
function useMyFetch(url, options) {
const {method, headers: {contentType} = {} , ...rest } = options;
useEffect(() => {
}, [url, method, contentType, JSON.stringify(rest)]);
}
I believe while this method is more verboose than others above, it's slightly faster especially if rest
typically is empty(no extra headers).
Upvotes: 0
Reputation: 2786
look the URL for testing show the image it should return the setResponse what you want
const useMyFetch = (url, options) =>
{
const [response, setResponse] = React.useState(null);
React.useEffect(() =>
{
console.log("going to fetch ", url);
fetch(url, options).then(async (response)=>
{
var json = await response.json();
return setResponse(json.message);
});
}, [ url ]);
return response;
};
function Example()
{
const res = useMyFetch("https://dog.ceo/api/breeds/image/random", { method: 'GET' });
if (!res)
{
return <div>loading...</div>
}
return <img src={res} alt="an image" />;
}
export default Example;
Upvotes: 0
Reputation: 22474
Define { method: 'GET' }
as a constant object so that the options
parameter will be always the same, here is an example:
const options = { method: 'GET' };
function Example() {
const res = useMyFetch("https://dog.ceo/api/breeds/image/random", options);
...
}
Otherwise, options
will be considered as changed every time the useMyFetch
is called because { method: 'GET' } === { method: 'GET' }
is false
.
Upvotes: 1