React behavior when rendering a component

rwehresmann

Let's suppose I have the following LoadingIndicator component:

export default function LoadingIndicator({ isLoading, children }) {
  return isLoading ? (
    <div>
      <LoadingContainer>
        <Spinner as="span" animation="border" role="status" />
        <br />
        <span>Carregando...</span>
      </LoadingContainer>
    </div>
  ) : (
    { children }
  );
}

Now I have the following example of a page that uses this component:

export default function DocumentsPage() {
  const dispatch = useDispatch();

  const currentUser = useSelector((state) => state.users.current);

  React.useEffect(() => {
    if (!currentUser) dispatch(UsersAction.requestCurrentUser());
  }, [dispatch, currentUser]);

  return (
    <LoadingIndicator isLoading>
      <MyAccountBaseLayout>
        <Wrapper>
          {currentUser.documentsStatus.map((document) => (
            <DocumentCard
              key={document.label}
              label={document.label}
              status={document.status}
            />
          ))}
        </Wrapper>
      </MyAccountBaseLayout>
    </LoadingIndicator>
  );
}

The idea is simple: render the loading spinner stuff when isLoading, otherwise render what we received. However, currentUser will only be present after the requestCurrentUser call, which happens after the first render. Because I iterate over the currentUser.documentsStatus, I'm receiving the error TypeError: Cannot read property 'documentsStatus' of null.

What I imagined that should happen is that the piece of code that uses the currentUser variable should only be rendered when the prop isLoading is false, but that's wrong. My question is: with the internal logic of the LoadingIndicator, why the error still happen? What is the best approach here?

devserkan

In your case, it will always fall into this error since there isn't any initial value for currentUser.documentsStatus in your state (probably). You can set an initial state but I guess you don't want to do this. In this case instead of doing the map directly there, maybe you can pass this data to another component and do it there.

return (
  <LoadingIndicator isLoading={isLoading}>
    <MyAccountBaseLayout>
      <Wrapper>
        <DocumentCards currentUser={currentUser} />
      </Wrapper>
    </MyAccountBaseLayout>
  </LoadingIndicator>
);

function DocumentCards({ currentUser }) {
  return currentUser.documentsStatus.map((document) => (
    <DocumentCard
      key={document.label}
      label={document.label}
      status={document.status}
    />
  ));
}

I don't know if this is the strategy you want but without setting an initial state or checking the data before map (you don't want to do this probably and use the loading part I guess) you don't have so many options.

By the way, I don't know where do you get isLoading but <LoadingIndicator isLoading> means isLoading will always be true in this component. This is why I use it like <LoadingIndicator isLoading={isLoading}> in my example, assuming there is an isLoading defined somewhere.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related