Avoid components hell in React
Tags: react, javascript, programming, typescript
Published: 2021-10-09 03:21:49

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.

```tsx
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;
```

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):

```tsx
export interface ReactHoc {
  <P>(WrappedComponent: React.ComponentType<P>): React.FC<P>;
}
```

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:

```tsx
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;
```

- **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](https://www.npmjs.com/package/recompose) library. It has other powerful uses, but for now we will use only the `compose`.

```tsx
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);
```

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.