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;








3















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()>
...









share|improve this question



















  • 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 mimic componentWillUnmount like useEffect(() => cancel, [])

    – Tom Finney
    Mar 27 at 12:00


















3















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()>
...









share|improve this question



















  • 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 mimic componentWillUnmount like useEffect(() => cancel, [])

    – Tom Finney
    Mar 27 at 12:00














3












3








3








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()>
...









share|improve this question














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






share|improve this question













share|improve this question











share|improve this question




share|improve this question










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 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 mimic componentWillUnmount like useEffect(() => cancel, [])

    – Tom Finney
    Mar 27 at 12:00













  • 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 mimic componentWillUnmount like useEffect(() => 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













3 Answers
3






active

oldest

votes


















2














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]);





share|improve this answer
































    0














    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;






    share|improve this answer

























    • 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


















    0














    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;





    share|improve this answer



























      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
      );



      );













      draft saved

      draft discarded


















      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









      2














      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]);





      share|improve this answer





























        2














        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]);





        share|improve this answer



























          2












          2








          2







          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]);





          share|improve this answer













          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]);






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered May 13 at 21:06









          Colin DColin D

          1,14218 silver badges24 bronze badges




          1,14218 silver badges24 bronze badges


























              0














              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;






              share|improve this answer

























              • 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















              0














              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;






              share|improve this answer

























              • 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













              0












              0








              0







              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;






              share|improve this answer













              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;







              share|improve this answer












              share|improve this answer



              share|improve this answer










              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 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

















              • 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
















              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











              0














              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;





              share|improve this answer





























                0














                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;





                share|improve this answer



























                  0












                  0








                  0







                  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;





                  share|improve this answer













                  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;






                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  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






























                      draft saved

                      draft discarded
















































                      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.




                      draft saved


                      draft discarded














                      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





















































                      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







                      Popular posts from this blog

                      Kamusi Yaliyomo Aina za kamusi | Muundo wa kamusi | Faida za kamusi | Dhima ya picha katika kamusi | Marejeo | Tazama pia | Viungo vya nje | UrambazajiKuhusu kamusiGo-SwahiliWiki-KamusiKamusi ya Kiswahili na Kiingerezakuihariri na kuongeza habari

                      Swift 4 - func physicsWorld not invoked on collision? The Next CEO of Stack OverflowHow to call Objective-C code from Swift#ifdef replacement in the Swift language@selector() in Swift?#pragma mark in Swift?Swift for loop: for index, element in array?dispatch_after - GCD in Swift?Swift Beta performance: sorting arraysSplit a String into an array in Swift?The use of Swift 3 @objc inference in Swift 4 mode is deprecated?How to optimize UITableViewCell, because my UITableView lags

                      Access current req object everywhere in Node.js ExpressWhy are global variables considered bad practice? (node.js)Using req & res across functionsHow do I get the path to the current script with Node.js?What is Node.js' Connect, Express and “middleware”?Node.js w/ express error handling in callbackHow to access the GET parameters after “?” in Express?Modify Node.js req object parametersAccess “app” variable inside of ExpressJS/ConnectJS middleware?Node.js Express app - request objectAngular Http Module considered middleware?Session variables in ExpressJSAdd properties to the req object in expressjs with Typescript