4

So, I built a custom hook to fetch data from an api. Here is the code:

export const useLambdaApi = () => {
  const [data, setData] = useState()
  const [isLoading, setIsLoading] = useState(false)

  useEffect(() => {
    const fetchData = async () => { ... }
    fetchData();
  },[isLoading]);

  return [data, setIsLoading];
}

And in the component I need the data I do:

export default function Comp (props) {
  const [data, setIsLoading] = useLambdaApi()

  useEffect(() => {
    const interval = setInterval(() => {
      setIsLoading(true)
      console.log(Date())
    }, 10000);
    return () => {
      window.clearInterval(interval); // clear the interval in the cleanup function
    };
  },[data]);
  return( ... )
}

But I get a TypeError: TypeError: setIsLoading is not a function

I know this must be something silly, but I am relatively new to React, so any feedback would be of much help.

Thanks.


EDIT:

To provide more context I added more code to my snipped of the component. I try to update the isLoading state from a setInterval. But I also did try from useEffect without the interval, and outside of useEffect...

This is the Stack trace:

PatientBoard.js:26 Uncaught TypeError: setIsLoading is not a function
    at PatientBoard.js:26
(anonymous) @ PatientBoard.js:26
setInterval (async)
(anonymous) @ PatientBoard.js:25
commitHookEffectList @ react-dom.development.js:21100
commitPassiveHookEffects @ react-dom.development.js:21133
callCallback @ react-dom.development.js:363
invokeGuardedCallbackDev @ react-dom.development.js:412
invokeGuardedCallback @ react-dom.development.js:466
flushPassiveEffectsImpl @ react-dom.development.js:24223
unstable_runWithPriority @ scheduler.development.js:676
runWithPriority$2 @ react-dom.development.js:11855
flushPassiveEffects @ react-dom.development.js:24194
(anonymous) @ react-dom.development.js:23755
scheduler_flushTaskAtPriority_Normal @ scheduler.development.js:451
flushTask @ scheduler.development.js:504
flushWork @ scheduler.development.js:637
performWorkUntilDeadline @ scheduler.development.js:238
Danf
  • 1,409
  • 2
  • 21
  • 39
  • Your snippets, even though they are lacking some details, seem plausible to me. Unless you call `setIsLoading` directly and have it in a handler it should actually work. Can you share the stack trace or further details? – Christian Ivicevic May 08 '20 at 01:04
  • Thanks for your comment. I edited the question with the stack trace and more of the code where I call the function from... The timer works, and the state `data` comes with the fetch data, although if Iog to console from that component `data` will be undefined. I asume because the fetching takes time, but if I log data every 10s in the timer it's always undefined, which strikes me as odd too. – Danf May 08 '20 at 01:24
  • With your snippets I still cannot reproduce the error: https://codesandbox.io/s/wizardly-sutherland-93msc - Could be that you're conditionally using hooks or having some other side-effect that causes problems. – Christian Ivicevic May 08 '20 at 01:30
  • Thanks for taking time with the codesandbox... What could be something that can cause this problem though? I am just starting the project, so it's just a couple of components and this is the only one importing that custom hook. – Danf May 08 '20 at 02:22

1 Answers1

1

Use it like so:

Updated: Trigger re-fetching based on URL changes:

import React, { useEffect, useState } from "react";

// Passing URL as a parameter
export const useLambdaApi = (url) => {
  const [data, setData] = useState();
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(url);
      const data = await response.json();

      // Set stats
      setIsLoading(false);
      setData(data);
    };
    fetchData();

  // Passing URL as a dependency
  }, [url]);

  // Return 'isLoading' not the 'setIsLoading' function
  return [data, isLoading];
};

// Using Hook into your component
export default function App() {
    // State will be changed if URL changes
    const [data, isLoading] = useLambdaApi('Your URL');

  // Loading indicator
  if (isLoading) return <div>Loading..</div>;

  // Return data when isLoading = false
  return (
    <div className="App">
      // Use data..
    </div>
  );
}

Here is a codesandbox example.

awran5
  • 4,333
  • 2
  • 15
  • 32
  • Yes, but in addition to using the `isLoading` state I also want to be able to change it from the component, triggering another data fetch. That's why I pass the setIsLoading function. However, if you can recommend a better way to trigger that fetching it would also be helpful... I know mine is not a very elegant solution... – Danf May 08 '20 at 02:05
  • If that is the case, I would recommend to pass the *url* as a parameter and keep *useEffect* watching when it changes. I'll update the answer. – awran5 May 08 '20 at 02:18
  • Thanks, I can try that and see how it works. I'll have to use another parameter though, since my API URL down't change... but a flag will do for now – Danf May 08 '20 at 02:24
  • @awran5 Why do you suggest to avoid returning everything but primitive values? It is perfectly viable to return whatever you want as hook are just functions. – Christian Ivicevic May 08 '20 at 11:02
  • @ChristianIvicevic You're right, Sorry that was not exactly what I wanted to say and probably will lead to misunderstanding. – awran5 May 08 '20 at 17:37