4

I'm using swr in a react project and I'm trying to generify the loading/error messages in a parent component wrapping the components loading data.

The wrapping component is a very simple component returning different messages depending on the loadingstate.

const LoadingView = ({ loading, error, children }) => {
  if (error) {
    return <span>Error</span>
  }

  if (loading) {
    return <span>Loading...</span>
  }

  return <Container>{children}</Container>
}

And the child component:

const WipePeriodTeams = ({ wipePeriodId }) => {
  const params = useParams()
  const { data, error } = useSWR(
    `/some-endpoint`
  )

  return <LoadingView loading={!data}>{console.log(data.length)}</LoadingView> <--- ReferenceError
}

The issue being that the child component's render method is always evaluated, doesn't matter if loading is true/false which could end up in a ReferenceError due to data not loaded.

Is the return value always evaluated no matter what the parent returns? Is there a way around this?

Thanks! :)

JustinCredible
  • 165
  • 3
  • 14
  • Maybe it's just me but I don't get it... "The issue being that the child component's render method is always evaluated, doesn't matter if loading is true/false": by child component do you mean `WipePeriodTeams`? "Is the return value always evaluated no matter what the parent returns?": if `WipePeriodTeams` is the child component where's the parent? – lbsn Mar 08 '22 at 16:45
  • I'm not sure I understand what you're asking. Your parent is always rendering `LoadingView`, and `LoadingView` renders different things depending on the props it receives. What's the problem exactly? – Galupuf Mar 08 '22 at 16:46
  • Sorry for being unclear, let me try to rephrase the issue. So `WipePeriodTeams` is rendering `LoadingView` which is wrapping the content of `WipePeriodTeams`, however if `LoadingView` were to return `Loading...` instead of its children while the data is still loading I still get a ReferenceError because the data has not yet been fetched. So the question is why the wrapped content of `WipePeriodTeams` is still evaluated even though the `LoadingView` component is returning a simple span with text? – JustinCredible Mar 08 '22 at 16:57
  • @JustinCredible have you found a solution to this yet? I am trying to solve the same issue. Thanks! – threepoint1416 Feb 05 '23 at 02:10
  • Afraid not, still using the same overhead for each component I create. Sorry. :) – JustinCredible Feb 20 '23 at 11:13

1 Answers1

1

That is the correct behaviour - the evaluation of children occurs in the parent component. You are seeing an error because data is undefined, so data.length is trying to point to a property of something that doesn't exist.

One way to avoid the error is to use && separator to check if data exists before referring to its length:

<LoadingView loading={!data}>{data && console.log(data.length)}</LoadingView>

Another approach is to replace your JSX expression with a component. I understand your example has a contrived child console.log(), but in the real world you're likely to pass in another component(s). Components are functions, so not evaluated at parent level:

const ChildComponent = ({data}) => {
  return (<>{console.log(data.length)}</>)
}

const Parent = () => {
  const { data, error } = useSWR(
    `/some-endpoint`
  );
  return (
    <LoadingView loading={!data}>
      <ChildComponent data={data} />
    </LoadingView>
  );
}

Live example

There'a a few other approaches to delaying evaluation of children if you dig around online, but be cautious as some of them feel like messy workarounds.

Ro Milton
  • 2,281
  • 14
  • 9
  • Thanks for the answer! I'm trying to avoid the null/undefined check and optional chaining in the render method, otherwise I could just remove the `LoadingView` and use `data?.length`. The 2nd solution makes the `LoadingView` work as intended. However that means I'll have to create an extra interim component each time I'm fetching with SWR. – JustinCredible Mar 09 '22 at 13:31