React useEffect cleanup function unexpectedly calledIs there an “exists” function for jQuery?var functionName = function() vs function functionName() Set a default parameter value for a JavaScript functionWhat is the difference between call and apply?How do I return the response from an asynchronous call?Loop inside React JSXProgrammatically navigate using react routerWhy do we need middleware for async flow in Redux?Canceling an Axios REST call in React Hooks useEffects cleanup failingReact new HOOKS api, useReducer with dynamic initialState
How to prevent criminal gangs from making/buying guns?
What if a restaurant suddenly cannot accept credit cards, and the customer has no cash?
Is this bar slide trick shown on Cheers real or a visual effect?
How would armour (and combat) change if the fighter didn't need to actually wear it?
How to get locks that are keyed alike?
Why does Japan use the same type of AC power outlet as the US?
Is the Microsoft recommendation to use C# properties applicable to game development?
Is there a name for the technique in songs/poems, where the rhyming pattern primes the listener for a certain line, which never comes?
Why aren't rockets built with truss structures inside their fuel & oxidizer tanks to increase structural strength?
Setting up a Mathematical Institute of Refereeing?
Why do my bicycle brakes get worse and feel more 'squishy" over time?
Doesn't the speed of light limit imply the same electron can be annihilated twice?
What should I do if actually I found a serious flaw in someone's PhD thesis and an article derived from that PhD thesis?
Locked room poison mystery!
Telephone number in spoken words
What is the farthest a camera can see?
Would the USA be eligible to join the European Union?
Did Pope Urban II issue the papal bull "terra nullius" in 1095?
What would it take to get a message to another star?
What allows us to use imaginary numbers?
Solving pricing problem heuristically in column generation algorithm for VRP
What should I do with the stock I own if I anticipate there will be a recession?
How do I call a 6-digit Australian phone number with a US-based mobile phone?
How can I communicate my issues with a potential date's pushy behavior?
React useEffect cleanup function unexpectedly called
Is there an “exists” function for jQuery?var functionName = function() vs function functionName() Set a default parameter value for a JavaScript functionWhat is the difference between call and apply?How do I return the response from an asynchronous call?Loop inside React JSXProgrammatically navigate using react routerWhy do we need middleware for async flow in Redux?Canceling an Axios REST call in React Hooks useEffects cleanup failingReact new HOOKS api, useReducer with dynamic initialState
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
I am creating a custom hook to fetch api on form submit, I make the api call inside an useEffect hook and I have a reducer to handle the hook's states.
One of the states is trigger
set to false at first which controls if the useEffect do anything, the point is the hook returns a function that flips trigger
value which triggers the useEffect only when you call this function.
The problem is the useEffect's cleanup function is called during the api call even though the component is clearly still mounted.
The cleanup function seems to be fired because I set the value of trigger
based on its previous value, when I set trigger
to a fixed value the cleanup function is not called but I lose my functionality
const fetchReducer = (state, action) =>
switch (action.type)
case 'FETCH_TRIGGER':
return
...state,
trigger: !state.trigger
case 'FETCH_INIT':
return
...state,
isLoading: true,
isError: false
;
case 'FETCH_SUCCESS':
return
...state,
isLoading: false,
isError: false,
datas: action.payload,
;
case 'FETCH_FAILURE':
return
...state,
isLoading: false,
isError: true,
;
default:
throw new Error();
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled) //isCancelled is true at this point
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
return cancel;
, [state.trigger]);
return ...state, triggerFetch;
Usage:
function MyComponent () {
const datas, isLoading, isError, triggerFetch = useFetchApi(query);
return (
<form onSubmit=event => event.preventDefault(); triggerFetch()>
...
javascript reactjs react-hooks
add a comment |
I am creating a custom hook to fetch api on form submit, I make the api call inside an useEffect hook and I have a reducer to handle the hook's states.
One of the states is trigger
set to false at first which controls if the useEffect do anything, the point is the hook returns a function that flips trigger
value which triggers the useEffect only when you call this function.
The problem is the useEffect's cleanup function is called during the api call even though the component is clearly still mounted.
The cleanup function seems to be fired because I set the value of trigger
based on its previous value, when I set trigger
to a fixed value the cleanup function is not called but I lose my functionality
const fetchReducer = (state, action) =>
switch (action.type)
case 'FETCH_TRIGGER':
return
...state,
trigger: !state.trigger
case 'FETCH_INIT':
return
...state,
isLoading: true,
isError: false
;
case 'FETCH_SUCCESS':
return
...state,
isLoading: false,
isError: false,
datas: action.payload,
;
case 'FETCH_FAILURE':
return
...state,
isLoading: false,
isError: true,
;
default:
throw new Error();
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled) //isCancelled is true at this point
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
return cancel;
, [state.trigger]);
return ...state, triggerFetch;
Usage:
function MyComponent () {
const datas, isLoading, isError, triggerFetch = useFetchApi(query);
return (
<form onSubmit=event => event.preventDefault(); triggerFetch()>
...
javascript reactjs react-hooks
1
The cleanup function is invoked every time the effect is run again as well as on unmount, and in your case it will be run every timestate.trigger
changes.
– Tholle
Mar 27 at 11:53
1
The cleanup function returned from an effect is only ever explicitly called on unmount if you pass an empty array as the second argument. Otherwise, the clean up function will be called whenever something in the dependency array changes.
– Tom Finney
Mar 27 at 11:53
1
You could add another use effect that didn't do anything except for return thatcancel
function and have it with an empty array dependency that would mimiccomponentWillUnmount
likeuseEffect(() => cancel, [])
– Tom Finney
Mar 27 at 12:00
add a comment |
I am creating a custom hook to fetch api on form submit, I make the api call inside an useEffect hook and I have a reducer to handle the hook's states.
One of the states is trigger
set to false at first which controls if the useEffect do anything, the point is the hook returns a function that flips trigger
value which triggers the useEffect only when you call this function.
The problem is the useEffect's cleanup function is called during the api call even though the component is clearly still mounted.
The cleanup function seems to be fired because I set the value of trigger
based on its previous value, when I set trigger
to a fixed value the cleanup function is not called but I lose my functionality
const fetchReducer = (state, action) =>
switch (action.type)
case 'FETCH_TRIGGER':
return
...state,
trigger: !state.trigger
case 'FETCH_INIT':
return
...state,
isLoading: true,
isError: false
;
case 'FETCH_SUCCESS':
return
...state,
isLoading: false,
isError: false,
datas: action.payload,
;
case 'FETCH_FAILURE':
return
...state,
isLoading: false,
isError: true,
;
default:
throw new Error();
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled) //isCancelled is true at this point
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
return cancel;
, [state.trigger]);
return ...state, triggerFetch;
Usage:
function MyComponent () {
const datas, isLoading, isError, triggerFetch = useFetchApi(query);
return (
<form onSubmit=event => event.preventDefault(); triggerFetch()>
...
javascript reactjs react-hooks
I am creating a custom hook to fetch api on form submit, I make the api call inside an useEffect hook and I have a reducer to handle the hook's states.
One of the states is trigger
set to false at first which controls if the useEffect do anything, the point is the hook returns a function that flips trigger
value which triggers the useEffect only when you call this function.
The problem is the useEffect's cleanup function is called during the api call even though the component is clearly still mounted.
The cleanup function seems to be fired because I set the value of trigger
based on its previous value, when I set trigger
to a fixed value the cleanup function is not called but I lose my functionality
const fetchReducer = (state, action) =>
switch (action.type)
case 'FETCH_TRIGGER':
return
...state,
trigger: !state.trigger
case 'FETCH_INIT':
return
...state,
isLoading: true,
isError: false
;
case 'FETCH_SUCCESS':
return
...state,
isLoading: false,
isError: false,
datas: action.payload,
;
case 'FETCH_FAILURE':
return
...state,
isLoading: false,
isError: true,
;
default:
throw new Error();
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled) //isCancelled is true at this point
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
return cancel;
, [state.trigger]);
return ...state, triggerFetch;
Usage:
function MyComponent () {
const datas, isLoading, isError, triggerFetch = useFetchApi(query);
return (
<form onSubmit=event => event.preventDefault(); triggerFetch()>
...
javascript reactjs react-hooks
javascript reactjs react-hooks
asked Mar 27 at 11:48
GatoyuGatoyu
1391 silver badge9 bronze badges
1391 silver badge9 bronze badges
1
The cleanup function is invoked every time the effect is run again as well as on unmount, and in your case it will be run every timestate.trigger
changes.
– Tholle
Mar 27 at 11:53
1
The cleanup function returned from an effect is only ever explicitly called on unmount if you pass an empty array as the second argument. Otherwise, the clean up function will be called whenever something in the dependency array changes.
– Tom Finney
Mar 27 at 11:53
1
You could add another use effect that didn't do anything except for return thatcancel
function and have it with an empty array dependency that would mimiccomponentWillUnmount
likeuseEffect(() => cancel, [])
– Tom Finney
Mar 27 at 12:00
add a comment |
1
The cleanup function is invoked every time the effect is run again as well as on unmount, and in your case it will be run every timestate.trigger
changes.
– Tholle
Mar 27 at 11:53
1
The cleanup function returned from an effect is only ever explicitly called on unmount if you pass an empty array as the second argument. Otherwise, the clean up function will be called whenever something in the dependency array changes.
– Tom Finney
Mar 27 at 11:53
1
You could add another use effect that didn't do anything except for return thatcancel
function and have it with an empty array dependency that would mimiccomponentWillUnmount
likeuseEffect(() => cancel, [])
– Tom Finney
Mar 27 at 12:00
1
1
The cleanup function is invoked every time the effect is run again as well as on unmount, and in your case it will be run every time
state.trigger
changes.– Tholle
Mar 27 at 11:53
The cleanup function is invoked every time the effect is run again as well as on unmount, and in your case it will be run every time
state.trigger
changes.– Tholle
Mar 27 at 11:53
1
1
The cleanup function returned from an effect is only ever explicitly called on unmount if you pass an empty array as the second argument. Otherwise, the clean up function will be called whenever something in the dependency array changes.
– Tom Finney
Mar 27 at 11:53
The cleanup function returned from an effect is only ever explicitly called on unmount if you pass an empty array as the second argument. Otherwise, the clean up function will be called whenever something in the dependency array changes.
– Tom Finney
Mar 27 at 11:53
1
1
You could add another use effect that didn't do anything except for return that
cancel
function and have it with an empty array dependency that would mimic componentWillUnmount
like useEffect(() => cancel, [])
– Tom Finney
Mar 27 at 12:00
You could add another use effect that didn't do anything except for return that
cancel
function and have it with an empty array dependency that would mimic componentWillUnmount
like useEffect(() => cancel, [])
– Tom Finney
Mar 27 at 12:00
add a comment |
3 Answers
3
active
oldest
votes
A local variable can be used inside the useEffect callback. Credit to @gaearon
https://codesandbox.io/s/k0lm13kwxo
useEffect(() =>
let ignore = false;
async function fetchData()
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
fetchData();
return () => ignore = true;
, [query]);
add a comment |
Solution by Tom Finney from the comments:
You could add another use effect that didn't do anything except for return that cancel function and have it with an empty array dependency that would mimic componentWillUnmount like useEffect(() => cancel, [])
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled)
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
, [state.trigger]);
useEffect(_=> cancel, []) //remove return cancel from useEffect and replace by this
return ...state, triggerFetch;
And does this actually work? Shouldn't it beuseEffect(()=> () => cancel(), [])
?
– Powell Ye
Mar 27 at 13:21
@Gatoyu I don't think this solution will successfully cancel on unmount. Thecancel
function in the second effect will always be thecancel
from the initial render which will change theisCancelled
variable from the initial render. CallingtriggerFetch
will trigger a re-render which means the first effect (the one that callsquery
) will be using anisCancelled
from the re-render which will not be changed by the second effect. This is why a ref is needed.
– Ryan Cogswell
Mar 27 at 15:04
add a comment |
Currently, you are triggering a state change which will trigger a re-render which will trigger an effect which will then call your API. All you really want to do is call your API.
In the sample code below, I've changed triggerFetch
to actually execute the query and removed the trigger
state. I added an effect without dependencies to allow cancelling on unmount. I also changed the cancel approach to use a ref rather than a local variable so that it will persist across re-renders.
import useReducer, useEffect, useRef from "react";
const fetchReducer = (state, action) =>
switch (action.type)
case "FETCH_INIT":
return
...state,
isLoading: true,
isError: false
;
case "FETCH_SUCCESS":
return
...state,
isLoading: false,
isError: false,
datas: action.payload
;
case "FETCH_FAILURE":
return
...state,
isLoading: false,
isError: true
;
default:
throw new Error();
;
const useFetchApi = (query, initialData = []) =>
const cancelledRef = useRef(false);
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = async _ =>
dispatch( type: "FETCH_INIT" );
try
const datas = await query();
if (!cancelledRef.current)
dispatch( type: "FETCH_SUCCESS", payload: datas );
catch (err)
if (!cancelledRef.current)
dispatch( type: "FETCH_FAILURE", payload: err );
;
useEffect(_ =>
return _ =>
console.log("canceling");
cancelledRef.current = true;
;
, []);
return ...state, triggerFetch ;
;
export default useFetchApi;
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55376471%2freact-useeffect-cleanup-function-unexpectedly-called%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
A local variable can be used inside the useEffect callback. Credit to @gaearon
https://codesandbox.io/s/k0lm13kwxo
useEffect(() =>
let ignore = false;
async function fetchData()
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
fetchData();
return () => ignore = true;
, [query]);
add a comment |
A local variable can be used inside the useEffect callback. Credit to @gaearon
https://codesandbox.io/s/k0lm13kwxo
useEffect(() =>
let ignore = false;
async function fetchData()
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
fetchData();
return () => ignore = true;
, [query]);
add a comment |
A local variable can be used inside the useEffect callback. Credit to @gaearon
https://codesandbox.io/s/k0lm13kwxo
useEffect(() =>
let ignore = false;
async function fetchData()
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
fetchData();
return () => ignore = true;
, [query]);
A local variable can be used inside the useEffect callback. Credit to @gaearon
https://codesandbox.io/s/k0lm13kwxo
useEffect(() =>
let ignore = false;
async function fetchData()
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
fetchData();
return () => ignore = true;
, [query]);
answered May 13 at 21:06
Colin DColin D
1,14218 silver badges24 bronze badges
1,14218 silver badges24 bronze badges
add a comment |
add a comment |
Solution by Tom Finney from the comments:
You could add another use effect that didn't do anything except for return that cancel function and have it with an empty array dependency that would mimic componentWillUnmount like useEffect(() => cancel, [])
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled)
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
, [state.trigger]);
useEffect(_=> cancel, []) //remove return cancel from useEffect and replace by this
return ...state, triggerFetch;
And does this actually work? Shouldn't it beuseEffect(()=> () => cancel(), [])
?
– Powell Ye
Mar 27 at 13:21
@Gatoyu I don't think this solution will successfully cancel on unmount. Thecancel
function in the second effect will always be thecancel
from the initial render which will change theisCancelled
variable from the initial render. CallingtriggerFetch
will trigger a re-render which means the first effect (the one that callsquery
) will be using anisCancelled
from the re-render which will not be changed by the second effect. This is why a ref is needed.
– Ryan Cogswell
Mar 27 at 15:04
add a comment |
Solution by Tom Finney from the comments:
You could add another use effect that didn't do anything except for return that cancel function and have it with an empty array dependency that would mimic componentWillUnmount like useEffect(() => cancel, [])
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled)
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
, [state.trigger]);
useEffect(_=> cancel, []) //remove return cancel from useEffect and replace by this
return ...state, triggerFetch;
And does this actually work? Shouldn't it beuseEffect(()=> () => cancel(), [])
?
– Powell Ye
Mar 27 at 13:21
@Gatoyu I don't think this solution will successfully cancel on unmount. Thecancel
function in the second effect will always be thecancel
from the initial render which will change theisCancelled
variable from the initial render. CallingtriggerFetch
will trigger a re-render which means the first effect (the one that callsquery
) will be using anisCancelled
from the re-render which will not be changed by the second effect. This is why a ref is needed.
– Ryan Cogswell
Mar 27 at 15:04
add a comment |
Solution by Tom Finney from the comments:
You could add another use effect that didn't do anything except for return that cancel function and have it with an empty array dependency that would mimic componentWillUnmount like useEffect(() => cancel, [])
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled)
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
, [state.trigger]);
useEffect(_=> cancel, []) //remove return cancel from useEffect and replace by this
return ...state, triggerFetch;
Solution by Tom Finney from the comments:
You could add another use effect that didn't do anything except for return that cancel function and have it with an empty array dependency that would mimic componentWillUnmount like useEffect(() => cancel, [])
const useFetchApi = (query, initialData = []) =>
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = _ => dispatch( type: 'FETCH_TRIGGER' );
const cancel = _ => console.log("canceling");isCancelled = true ;
useEffect(_ =>
if (!state.trigger)
return;
triggerFetch();
(async _ =>
dispatch( type: 'FETCH_INIT' );
try
const datas = await query();
if (!isCancelled)
dispatch( type: 'FETCH_SUCCESS', payload: datas )
catch (err)
if (!isCancelled)
dispatch( type: 'FETCH_FAILURE', payload: err )
)();
, [state.trigger]);
useEffect(_=> cancel, []) //remove return cancel from useEffect and replace by this
return ...state, triggerFetch;
answered Mar 27 at 12:38
GatoyuGatoyu
1391 silver badge9 bronze badges
1391 silver badge9 bronze badges
And does this actually work? Shouldn't it beuseEffect(()=> () => cancel(), [])
?
– Powell Ye
Mar 27 at 13:21
@Gatoyu I don't think this solution will successfully cancel on unmount. Thecancel
function in the second effect will always be thecancel
from the initial render which will change theisCancelled
variable from the initial render. CallingtriggerFetch
will trigger a re-render which means the first effect (the one that callsquery
) will be using anisCancelled
from the re-render which will not be changed by the second effect. This is why a ref is needed.
– Ryan Cogswell
Mar 27 at 15:04
add a comment |
And does this actually work? Shouldn't it beuseEffect(()=> () => cancel(), [])
?
– Powell Ye
Mar 27 at 13:21
@Gatoyu I don't think this solution will successfully cancel on unmount. Thecancel
function in the second effect will always be thecancel
from the initial render which will change theisCancelled
variable from the initial render. CallingtriggerFetch
will trigger a re-render which means the first effect (the one that callsquery
) will be using anisCancelled
from the re-render which will not be changed by the second effect. This is why a ref is needed.
– Ryan Cogswell
Mar 27 at 15:04
And does this actually work? Shouldn't it be
useEffect(()=> () => cancel(), [])
?– Powell Ye
Mar 27 at 13:21
And does this actually work? Shouldn't it be
useEffect(()=> () => cancel(), [])
?– Powell Ye
Mar 27 at 13:21
@Gatoyu I don't think this solution will successfully cancel on unmount. The
cancel
function in the second effect will always be the cancel
from the initial render which will change the isCancelled
variable from the initial render. Calling triggerFetch
will trigger a re-render which means the first effect (the one that calls query
) will be using an isCancelled
from the re-render which will not be changed by the second effect. This is why a ref is needed.– Ryan Cogswell
Mar 27 at 15:04
@Gatoyu I don't think this solution will successfully cancel on unmount. The
cancel
function in the second effect will always be the cancel
from the initial render which will change the isCancelled
variable from the initial render. Calling triggerFetch
will trigger a re-render which means the first effect (the one that calls query
) will be using an isCancelled
from the re-render which will not be changed by the second effect. This is why a ref is needed.– Ryan Cogswell
Mar 27 at 15:04
add a comment |
Currently, you are triggering a state change which will trigger a re-render which will trigger an effect which will then call your API. All you really want to do is call your API.
In the sample code below, I've changed triggerFetch
to actually execute the query and removed the trigger
state. I added an effect without dependencies to allow cancelling on unmount. I also changed the cancel approach to use a ref rather than a local variable so that it will persist across re-renders.
import useReducer, useEffect, useRef from "react";
const fetchReducer = (state, action) =>
switch (action.type)
case "FETCH_INIT":
return
...state,
isLoading: true,
isError: false
;
case "FETCH_SUCCESS":
return
...state,
isLoading: false,
isError: false,
datas: action.payload
;
case "FETCH_FAILURE":
return
...state,
isLoading: false,
isError: true
;
default:
throw new Error();
;
const useFetchApi = (query, initialData = []) =>
const cancelledRef = useRef(false);
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = async _ =>
dispatch( type: "FETCH_INIT" );
try
const datas = await query();
if (!cancelledRef.current)
dispatch( type: "FETCH_SUCCESS", payload: datas );
catch (err)
if (!cancelledRef.current)
dispatch( type: "FETCH_FAILURE", payload: err );
;
useEffect(_ =>
return _ =>
console.log("canceling");
cancelledRef.current = true;
;
, []);
return ...state, triggerFetch ;
;
export default useFetchApi;
add a comment |
Currently, you are triggering a state change which will trigger a re-render which will trigger an effect which will then call your API. All you really want to do is call your API.
In the sample code below, I've changed triggerFetch
to actually execute the query and removed the trigger
state. I added an effect without dependencies to allow cancelling on unmount. I also changed the cancel approach to use a ref rather than a local variable so that it will persist across re-renders.
import useReducer, useEffect, useRef from "react";
const fetchReducer = (state, action) =>
switch (action.type)
case "FETCH_INIT":
return
...state,
isLoading: true,
isError: false
;
case "FETCH_SUCCESS":
return
...state,
isLoading: false,
isError: false,
datas: action.payload
;
case "FETCH_FAILURE":
return
...state,
isLoading: false,
isError: true
;
default:
throw new Error();
;
const useFetchApi = (query, initialData = []) =>
const cancelledRef = useRef(false);
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = async _ =>
dispatch( type: "FETCH_INIT" );
try
const datas = await query();
if (!cancelledRef.current)
dispatch( type: "FETCH_SUCCESS", payload: datas );
catch (err)
if (!cancelledRef.current)
dispatch( type: "FETCH_FAILURE", payload: err );
;
useEffect(_ =>
return _ =>
console.log("canceling");
cancelledRef.current = true;
;
, []);
return ...state, triggerFetch ;
;
export default useFetchApi;
add a comment |
Currently, you are triggering a state change which will trigger a re-render which will trigger an effect which will then call your API. All you really want to do is call your API.
In the sample code below, I've changed triggerFetch
to actually execute the query and removed the trigger
state. I added an effect without dependencies to allow cancelling on unmount. I also changed the cancel approach to use a ref rather than a local variable so that it will persist across re-renders.
import useReducer, useEffect, useRef from "react";
const fetchReducer = (state, action) =>
switch (action.type)
case "FETCH_INIT":
return
...state,
isLoading: true,
isError: false
;
case "FETCH_SUCCESS":
return
...state,
isLoading: false,
isError: false,
datas: action.payload
;
case "FETCH_FAILURE":
return
...state,
isLoading: false,
isError: true
;
default:
throw new Error();
;
const useFetchApi = (query, initialData = []) =>
const cancelledRef = useRef(false);
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = async _ =>
dispatch( type: "FETCH_INIT" );
try
const datas = await query();
if (!cancelledRef.current)
dispatch( type: "FETCH_SUCCESS", payload: datas );
catch (err)
if (!cancelledRef.current)
dispatch( type: "FETCH_FAILURE", payload: err );
;
useEffect(_ =>
return _ =>
console.log("canceling");
cancelledRef.current = true;
;
, []);
return ...state, triggerFetch ;
;
export default useFetchApi;
Currently, you are triggering a state change which will trigger a re-render which will trigger an effect which will then call your API. All you really want to do is call your API.
In the sample code below, I've changed triggerFetch
to actually execute the query and removed the trigger
state. I added an effect without dependencies to allow cancelling on unmount. I also changed the cancel approach to use a ref rather than a local variable so that it will persist across re-renders.
import useReducer, useEffect, useRef from "react";
const fetchReducer = (state, action) =>
switch (action.type)
case "FETCH_INIT":
return
...state,
isLoading: true,
isError: false
;
case "FETCH_SUCCESS":
return
...state,
isLoading: false,
isError: false,
datas: action.payload
;
case "FETCH_FAILURE":
return
...state,
isLoading: false,
isError: true
;
default:
throw new Error();
;
const useFetchApi = (query, initialData = []) =>
const cancelledRef = useRef(false);
const [state, dispatch] = useReducer(fetchReducer,
isLoading: false,
isError: false,
datas: initialData,
trigger: false
);
const triggerFetch = async _ =>
dispatch( type: "FETCH_INIT" );
try
const datas = await query();
if (!cancelledRef.current)
dispatch( type: "FETCH_SUCCESS", payload: datas );
catch (err)
if (!cancelledRef.current)
dispatch( type: "FETCH_FAILURE", payload: err );
;
useEffect(_ =>
return _ =>
console.log("canceling");
cancelledRef.current = true;
;
, []);
return ...state, triggerFetch ;
;
export default useFetchApi;
answered Mar 27 at 14:34
Ryan CogswellRyan Cogswell
10.2k1 gold badge14 silver badges34 bronze badges
10.2k1 gold badge14 silver badges34 bronze badges
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55376471%2freact-useeffect-cleanup-function-unexpectedly-called%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
The cleanup function is invoked every time the effect is run again as well as on unmount, and in your case it will be run every time
state.trigger
changes.– Tholle
Mar 27 at 11:53
1
The cleanup function returned from an effect is only ever explicitly called on unmount if you pass an empty array as the second argument. Otherwise, the clean up function will be called whenever something in the dependency array changes.
– Tom Finney
Mar 27 at 11:53
1
You could add another use effect that didn't do anything except for return that
cancel
function and have it with an empty array dependency that would mimiccomponentWillUnmount
likeuseEffect(() => cancel, [])
– Tom Finney
Mar 27 at 12:00