{"id":392,"hash":"31b067a2a7bb0f36a7496f9985ac0163a20e1ca2202b2255a759820d84323c75","pattern":"React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing","full_message":"I was trying the useEffect example something like below:\n\nuseEffect(async () => {\n    try {\n        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);\n        const json = await response.json();\n        setPosts(json.data.children.map(it => it.data));\n    } catch (e) {\n        console.error(e);\n    }\n}, []);\n\nand I get this warning in my console. But the cleanup is optional for async calls I think. I am not sure why I get this warning.  Linking sandbox for examples. https://codesandbox.io/s/24rj871r0p","ecosystem":"npm","package_name":"reactjs","package_version":null,"solution":"Note: If you want to support SSR/SSG for SEO, use framework specific api from React-router/Remix, Next.js or Gatsby.\n\nFor React version >=19\nWe now have nice loader and form action api:\n\nLoading data:\n\nfunction Comments({ dataPromise }) {\n  const data = use(dataPromise);\n  return <div>{JSON.stringify(data)}</div>;\n}\n\nasync function loadData() {\n  return { data: \"some data\" };\n}\n\nexport function Index() {\n  return (\n    <Suspense fallback={<div>Loading...</div>}>\n      <Comments dataPromise={loadData()} />\n    </Suspense>\n  );\n}\n\nForm actions\n\nasync function updateDataAndLoadNew() {\n  // update data\n  // load new data\n\n  // return new data\n  return { data: \"some data\" };\n}\n\nexport default function Index() {\n  const [state, action, isPending] = useActionState(\n    async (prev, formData) => {\n      return updateUserAndLoadNewData();\n    },\n    { data: \"initial state, no data\" } // can be null\n  );\n\n  if (state.error) {\n    return `Error ${state.error.message}`;\n  }\n\n  return (\n    <form action={action}>\n      <input type=\"text\" name=\"name\" />\n      <button type=\"submit\" disabled={isPending}>\n        Do it\n      </button>\n      {state.data && <p>Data {state.data}</p>}\n    </form>\n  );\n}\n\nFor React version >=18\nStarting with React 18 you can also use Suspense, but it's not yet recommended if you are not using frameworks that correctly implement it:\n\nIn React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy.\n\nIf not part of the framework, you can try some libs that implement it like swr.\n\nOversimplified example of how suspense works. You need to throw a promise for Suspense to catch it, show fallback component first and render Main component when promise it's resolved.\n\nlet fullfilled = false;\nlet promise;\n\nconst fetchData = () => {\n  if (!fullfilled) {\n    if (!promise) {\n      promise = new Promise(async (resolve) => {\n        const res = await fetch('api/data')\n        const data = await res.json()\n\n        fullfilled = true\n        resolve(data)\n      });\n    }\n\n    throw promise\n  }\n};\n\nconst Main = () => {\n  fetchData();\n  return <div>Loaded</div>;\n};\n\nconst App = () => (\n  <Suspense fallback={\"Loading...\"}>\n    <Main />\n  </Suspense>\n);\n\nFor React version <=17\nI suggest to look at Dan Abramov (one of the React core maintainers) answer here:\n\nI think you're making it more complicated than it needs to be.\n\nfunction Example() {\n  const [data, dataSet] = useState<any>(null)\n\n  useEffect(() => {\n    async function fetchMyAPI() {\n      let response = await fetch('api/data')\n      response = await response.json()\n      dataSet(response)\n    }\n\n    fetchMyAPI()\n  }, [])\n\n  return <div>{JSON.stringify(data)}</div>\n}\n\nLonger term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like\n\nconst response = MyAPIResource.read();\n\nand no effects. But in the meantime you can move the async stuff to a separate function and call it.\n\nYou can read more about experimental suspense here.\n\nIf you want to use functions outside with eslint.\n\n function OutsideUsageExample({ userId }) {\n  const [data, dataSet] = useState<any>(null)\n\n  const fetchMyAPI = useCallback(async () => {\n    let response = await fetch('api/data/' + userId)\n    response = await response.json()\n    dataSet(response)\n  }, [userId]) // if userId changes, useEffect will run again\n\n  useEffect(() => {\n    fetchMyAPI()\n  }, [fetchMyAPI])\n\n  return (\n    <div>\n      <div>data: {JSON.stringify(data)}</div>\n      <div>\n        <button onClick={fetchMyAPI}>manual fetch</button>\n      </div>\n    </div>\n  )\n}","confidence":0.95,"source":"stackoverflow","source_url":"https://stackoverflow.com/questions/53332321/react-hook-warnings-for-async-function-in-useeffect-useeffect-function-must-ret","votes":593,"created_at":"2026-04-19T04:51:05.199181+00:00","updated_at":"2026-04-19T04:51:05.199181+00:00"}