Avoid components hell in React
Elias Júnior
Posted on October 9, 2021
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:
- You can’t use the
useLocation()
hook in the App component, because you’re including theBrowserRouter
on the same component, so you can only use the hook in a child component. - 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. - 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:
-
Line 1: we are declaring the interface
ReactHoc
; -
Line 2:
<P>
declares that we will receive some param of typeP
(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 paramWrappedComponent
that has the typeReact.ComponentType<P>
, a React Component with theP
params. -
Line 2:
React.FC<P>
we are returning a new React functional component with the same params as ourWrappedComponent
.
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;
-
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);
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.
Posted on October 9, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.