Elias Júnior

View on Dev.to

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;

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>; }

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

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;

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

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.