A most common pattern is using React for UI rendering and Redux global state management together with Flux pattern. Before you put your app into Redux rabbit hole consider whether you actually need Redux. This is a very higher-level view of a frontend component architecture. We should shape architecture to promote code readability, maintainability, debuggability, testability, and better separation of concerns.
I’ll explain each section in detail from the bottom up.
The separation of the presentational and container components was a decision inspired by Dan Abramov’s blog post.
Presentational components are…
- Only responsible for the DOM markup.
- They get data via props and communicate back only via callbacks we passed on as props.
- They might have a state, but most of the time, the state is lifted up to a container component and passed to the presentational component via props.
- Since presentational components are loosely coupled it’s really easy to reuse them.
This is where most of the business logic happens. Container components “contain” presentational components. Container components are responsible for…
- Fetching data from backend APIs and forward them as props for presentational components.
- Composing dispatchable actions as callbacks for presentational components.
- Passing data from global store to presentational components.
- Storing local state/callbacks to trigger actions lifted from presentational components.
Container components are not reusable but we can break them into reusable smaller units like custom hooks which I will discuss later.
Previously I used to fetch data from APIs via redux but I would discourage that now because,
- Lots of boilerplate code to fetch data from an API. It’s a boring repetitive task.
- Pollutes the global state.
- Having to make redux state async adds additional complexity to the code base.
- API calls are duplicated if we want the same data in several components. So it impacts application performance. Of course, you can dedupe them with prop drilling but again it’s additional complexity.
- Duplicated API calls also mean additional renderings. Again it impacts application performance.
And the answer I found for the above problem is …
Alternative Approach to Data fetching — Custom Hooks
Custom Hooks allow us to extract component logic into reusable functions. It’s not something specific for data fetching but let’s focus on our use case for now. Instead of building our own hooks from the scratch, I opt to use the SWR library which addresses the problems above plus a bit more. There are alternatives like React Query too.
SWR makes our life easier with…
- A builtin cache for request deduplication
- Builtin optimistic rendering — first return the muted data from cache (stale), then send the fetch request (revalidate), and finally, come with the up-to-date data.
- Automatic retry on error.
- Polling on interval.
- Data revalidation on focus (If the user switch browser tabs or returned after locked ).
Since we use custom hooks for data we only have few use cases to use the redux store. as I said earlier we might be better off without redux.
That’s it, this is how I think we should build a frontend React app in 2021. If you have any questions/suggestions feel free to leave a comment. In the next post, I will share how to accommodate automated testing into this architecture.