Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks (dynamic import)

Viewed 5980
const usePage = ({ page }) => {
  const prevPage = usePrevious(page)

  const [p, setPage] = useState()

  const loadData = async param => {
    const data = await import(`${param}`)
    setPage(data.default)
  }

  useEffect(() => {
    if (prevPage === page) return

    loadData(page)
  }, [page, prevPage])

  return {
    p
  }
}

const PageRoute = memo(({page}) => {
    const { p } = usePage({ page })

    const Page = p

    return (
      <Page/>
    )
  }
)

I don't really understand how do this issue related to my code . I don't call any hook inside useEffect. How can I fix it ? I want to call dynamic import in use effect in case when page parameter are not equal previous one.

1 Answers

The problem is that since you're probably exporting a React component as default from these dynamically imported modules, data.default is a function which gets passed to the setter returned by useState.

However, since the useState setter can also take in a function which does the state update, what's happening is that the setter calls the passed function which is a React component (data.default) which fires the hooks used in that component. So your call is actually equivalent to setPage(prev => data.default(prev)).

This can be fixed by explicitly passing your own state updater which just returns the data.default function:

const loadData = async param => {
  const data = await import(`${param}`)
  setPage(() => data.default) // <-------
}
Related