DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Danilo GačeviΔ‡
Danilo GačeviΔ‡

Posted on

Avoiding large ternary operator statements in React

Introducing the problem

Often when writing React applications, you are conditionally rendering some piece of UI based on some state.
This condition can be as simple as a binary boolean condition, or it can be a more complex ternary operator with multiple nested statements.

Let's imagine that we are calling an API using react-query and that we are displaying different UI depending on the API
call status.

const Example = () => {
  const { data, status } = useQuery('users', fetchUsers)

  return (
    <Page>
      {status === 'loading' ? (
        <LoadingView />
      ) : status === 'error' ? (
        <ErrorView />
      ) : (
        <SuccessView data={data} />
      )}
    </Page>
  )
}
Enter fullscreen mode Exit fullscreen mode

These kinds of conditions can get hard to read sometimes.

Leveraging compound components and React.Children.map function, we can write a cleaner solution by moving condition logic into a wrapper component.

const Example = () => {
  const { data, status } = useQuery('users', fetchUsers)

  return (
    <Page.Wrapper status={status}>
      <Page.Loading>
        <LoadingView />
      </Page.Loading>
      <Page.Error>
        <ErrorView />
      </Page.Error>
      <Page.Success>
        <SuccessView data={data} />
      </Page.Success>
    </Page.Wrapper>
  )
}
Enter fullscreen mode Exit fullscreen mode

Page component implementation

const PageWrapper = ({ children, status }) => {
  const renderChildren = useCallback(() => {
    return React.Children.map(children, (child) => {
      if (child?.type === Page.Loading && status === 'loading') {
        return child
      }
      if (child?.type === Page.Success && status === 'success') {
        return child
      }
      if (child?.type === Page.Error && status === 'error') {
        return child
      }
      return null
    })
  }, [children, status])

  return <>{renderChildren()}</>
}

export const Page = {
  Wrapper: PageWrapper,
  Loading: ({ children }) => children,
  Success: ({ children }) => children,
  Error: ({ children }) => children,
}
Enter fullscreen mode Exit fullscreen mode

Originally published at: https://www.danilothedev.com/blog/avoiding-large-ternary-statements-in-react

Top comments (5)

Collapse
 
pengeszikra profile image
Peter Vivo • Edited on

I do not prefered wrappers, so:

const Example = () => {
  const { data, status } = useQuery('users', fetchUsers)

  return (
    <Page>
      {status === Status.LOADING && <LoadingView />}
      {status === Status.SUCCESS && <SuccessView data={data} />}
      {status === Status.ERROR   && <ErrorView />}
    </Page>
  )
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
nandokferrari profile image
Fernando Ferrari

That is my choice too!

Collapse
 
joolsmcfly profile image
Julien Dephix

+1 it’s more readable and succinct

Collapse
 
gax97 profile image
Danilo GačeviΔ‡ Author

I agree that this is a highly opinionated approach :)

Collapse
 
lukeshiru profile image
Luke Shiru

To be honest, this is not that hard to read, for me:

const Example = () => {
    const { data, status } = useQuery("users", fetchUsers);

    return (
        <Page>
            {status === "loading" ? (
                <LoadingView />
            ) : status === "error" ? (
                <ErrorView />
            ) : (
                <SuccessView data={data} />
            )}
        </Page>
    );
};
Enter fullscreen mode Exit fullscreen mode

But if you really want to change it, you have a few options that don't require creating new components for that. The first one could be to use the ternary to select the component instead of using it in the render portion directly:

const Example = () => {
    const { data, status } = useQuery("users", fetchUsers);

    const Component =
        status === "loading"
            ? LoadingView
            : status === "error"
            ? ErrorView
            : SuccessView;

    return (
        <Page>
            <Component data={data} />
        </Page>
    );
};
Enter fullscreen mode Exit fullscreen mode

Other similar approach is to use the ternary to select the component at render time:

import { createElement } from "react";

const Example = () => {
    const { data, status } = useQuery("users", fetchUsers);

    return (
        <Page>
            {createElement(
                status === "loading"
                    ? LoadingView
                    : status === "error"
                    ? ErrorView
                    : SuccessView,
                { data },
            )}
        </Page>
    );
};
Enter fullscreen mode Exit fullscreen mode

But if you really don't want to use ternaries and you're thinking about all different states this could have, you can just use an object:

const Example = () => {
    const { data, status } = useQuery("users", fetchUsers);

    const Component =
        {
            loading: LoadingView,
            error: ErrorView,
        }[status] ?? SuccessView;

    return (
        <Page>
            <Component data={data} />
        </Page>
    );
};
Enter fullscreen mode Exit fullscreen mode

One thing worth mentioning is that if that useQuery hook is from react-query, you can actually use error and isLoading instead of status:

const Example = () => {
    const { data, error, isLoading } = useQuery("users", fetchUsers);

    return (
        <Page>
            {isLoading ? (
                <LoadingView />
            ) : error ? (
                <ErrorView />
            ) : (
                <SuccessView data={data} />
            )}
        </Page>
    );
};
Enter fullscreen mode Exit fullscreen mode

Cheers!

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.