DEV Community

Cover image for Avoid components hell in React
Elias Júnior
Elias Júnior

Posted on • Edited on

Avoid components hell in React

Hello everyone. In this article I will focus on what the best way is, in my opinion, to handle “components cascade” in React. Using this approach, your application will be well organized and you’ll make it more readable and easier to maintain.

import AppRoutes from 'src/components/AppRoutes';
import store from 'src/store/store';
import theme from 'src/styles/theme';

import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      cacheTime: 0,
      retry: false,
      refetchInterval: false,
      refetchOnMount: false,
      refetchOnReconnect: false,
      refetchOnWindowFocus: false,
    },
  },
});

const App = () => {
  return (
    <ChakraProvider theme={theme}>
      <QueryClientProvider client={queryClient}>
        <Provider store={store}>
          <BrowserRouter>
            <AppRoutes />
          </BrowserRouter>
        </Provider>
      </QueryClientProvider>
    </ChakraProvider>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Looks like hell, doesn't it? But imagine that you have even more providers, or these providers have many properties that you need to include.

But, what is the problem? I have some points here:

  1. You can’t use the useLocation() hook in the App component, because you’re including the BrowserRouter on the same component, so you can only use the hook in a child component.
  2. You may face some conflicts when importing multiple providers from many libraries (or even your own code). So you will need to rename import { Provider as ReduxProvider } from 'react-redux’ for example.
  3. When you want to remove a provider, your commit will have many changed lines in your code, because your editor will reindent all child components at least 1 column to the left.

I could point out other problems here, but I think that’s enough.

The solution

We have a technique in React for reusing component logic. It’s called high-order component (the HOC). It’s basically a function what will wrap your component with any other component that you want.

Create a generic type for HOCs

So if we are looking to make reusable components, we need to create a type definition for our HOCs (only if you’re using Typescript):

export interface ReactHoc {
  <P>(WrappedComponent: React.ComponentType<P>): React.FC<P>;
}
Enter fullscreen mode Exit fullscreen mode

Don’t panic! Let me explain what is happening here:

  • Line 1: we are declaring the interface ReactHoc;
  • Line 2: <P> declares that we will receive some param of type P (any type) - this is because we don’t know what property the React component will have;
  • Line 2: (WrappedComponent: React.ComponentType<P>) we are receiving a param WrappedComponent that has the type React.ComponentType<P>, a React Component with the P params.
  • Line 2: React.FC<P> we are returning a new React functional component with the same params as our WrappedComponent.

Yes, it’s a little difficult, but you will get used to working with Typescript typing. If you don't understand that now, you will later, don’t worry.

Create your first HOC

Now for the easy part! Let’s create our React Redux HOC:

import store from 'src/store/store';
import { ReactHoc } from 'src/types/hocs';

import { Provider } from 'react-redux';

const withRedux: ReactHoc = (Component) => (props) =>
  (
    <Provider store={store}>
      <Component {...props} />
    </Provider>
  );

export default withRedux;
Enter fullscreen mode Exit fullscreen mode
  • Line 6: we are declaring the function name. It will have the type of the ReactHoc, a function that will receive a component and will return another React component.
  • Line 8: we add the Redux provider, as we did before;
  • Line 9: now we need to render the component we want to wrap, passing all parameters to it.

You will need to create other HOCs for the other providers: withChakraUi, withReactQuery, withReactRouter ...

And in the end, you will need to compose your app with all that HOCs. For that, I like to use the Recompose library. It has other powerful uses, but for now we will use only the compose.

import AppRoutes from 'src/components/AppRoutes';
import withChakraUI from 'src/hocs/with-chakra-ui';
import withReactQuery from 'src/hocs/with-react-query';
import withReactRouter from 'src/hocs/with-react-router';
import withReactSuspense from 'src/hocs/with-react-suspense';
import withRedux from 'src/hocs/with-redux';

import { compose } from 'recompose';

const App = () => {
  return <AppRoutes />;
};

export default compose(
  withChakraUI,
  withReactSuspense,
  withReactRouter,
  withReactQuery,
  withRedux,
)(App);
Enter fullscreen mode Exit fullscreen mode

Your App component is now clean and beautiful! If you need to remove the redux, you just need to remove the withRedux and it’s done! One line in your commit (actually two, as you will need to remove the import line 😁)

Make good use of what you have just learned and leave your comment or question below. And if you liked, please like and share.

Top comments (0)