React: Abstraction for components. Composition vs Render props

Viewed 312

Long question please be ready

I've been working with react-query recently and discovered that a lot of the code has to be duplicated for every component that is using useQuery. For example:

if(query.isLoading) {
   return 'Loading..'
}

if(query.isError) {
  return 'Error'
}

if(query.isSuccess) {
   return 'YOUR ACTUAL COMPONENT'
}

I tried creating a wrapper component to which you pass in the query information and it will handle all the states for you.

A basic implementation goes as follows:

const Wrapper = ({ query, LoadingComponent, ErrorComponent, children }) => {

  if (query.isLoading) {
    const toRender = LoadingComponent || <DefaultLoader />;
    return <div className="grid place-items-center">{toRender}</div>;
  }

  if (query.isError) {
    const toRender = ErrorComponent ? (
      <ErrorComponent />
    ) : (
      <div className="dark:text-white">Failed to Load</div>
    );
    return <div className="grid place-items-center">{toRender}</div>;
  }

  if (query.isSuccess) {
    return React.Children.only(children);
  }
  return null;
};

And, using it like:

const Main = () => {
   const query = useQuery(...);
   return (
      <Wrapper query={query}>
         <div>{query.message}</div>
      </Wrapper>

   )
}

The issue with this is that query.message can be undefined and therefore throws an error. Can't read property message of undefined

But, it should be fixable by optional chaining query?.message.

This is where my confusion arises. The UI elements inside wrapper should have been rendered but they donot. And, if they don't then why does it throw an error.

This means that, the chilren of wrapper are executed on each render. But not visible. WHY??

Render Props to rescue

Wrapper

  const WrapperWithRP = ({ query, children }) => {
      const { isLoading, data, isError, error } = query;
      if (isLoading) return 'Loading...'
      if (isError) return 'Error: ' + error

      return children(data)
  }

And using it like,

const Main = () => {
   const query = useQuery(...);
   return (
      <Wrapper query={query}>
        {state => {
             return (
                <div>{state.message}</div>
             )
          }
        }
      </Wrapper>

   )
}

This works as expected. And the children are only rendered when the state is either not loading or error.

I am still confused as to why the first approach doesn't show the elements on the UI event when they are clearly executed?

Codesandbox link replicating the behaviour: https://codesandbox.io/s/react-composition-and-render-prop-tyyzh?file=/src/App.js

1 Answers

Okay, so i figured it out and it is simpler than it sounds.

In approach one: the JSX will still be executed because it's a function call React.createElement. But, Wrapper is only returning the children when the query succeeds. Therefore, the children won't be visible on the UI.

Both approaches work, but with approach 1, we might have to deal with undefined data.

I ended up using render-prop as my solution as it seemed cleaner.

Related