React is pretty easy to learn if you know JavaScript, however, it's pretty easy to lose track of your project or just mess things up as it scales or gets ready for a refactor or re-write. I'll share some tips which have literally saved my life...and a whole lot of time😇. Let's get into it!
Tip 1: (Using Containers)
It's very easy to bloat your components with a lot of code: API calls, form logic and a whole lot more logic. To add to all this, the UI code is shoved into these already bloated components. How do we solve this? Containerizing! Containers allow us to isolate our logic and UI code into different components which helps us avoid bloating that particular component just like MVC does. Let's look at an example:
This component fetches news items and displays a UI for the fetched new items
const Dashboard = () => {
const [news, newsError] = useCustomFetch("/news");
const [user, userError] = useCustomFetch("/user");
const [trends, trendsError] = useCustomFetch("/trends");
const [notifications] = useCustomFetch("/notifications");
if (news) {
// sort news for tags
// sort news for "sort options"
// perform some custom operations on news
// do something else like caching?
}
if (trends) {
// sort trends for tags
// sort trends for "sort options"
// perform some custom operations on trends
// do something else like caching?
}
if (notifications) {
// sort notifications for tags
// sort notifications for "sort options"
// perform some custom operations on notifications
// do something else like caching?
}
return (
<div>
<h2>user</h2>
loading handler
map cards
display available tags
display sort options
<h2>notifications</h2>
loading handler
map cards
display available tags
display sort options
<h2>Latest News</h2>
loading handler
map cards
display available tags
display sort options
<h2>Trends</h2>
loading handler
map cards
display available tags
display sort options
</div>
);
};
We're skipping a whole lot of logic and UI code here, but you can pretty much see how huge our component can get if left to grow on its own terms. Now let's look at this same example containerized.
Instead of having our entire code as just Dashboard, we can split it into DashboardContainer
and Dashboard
. It's NOT compulsory to name your containers with Container, however, it's a good naming convention as done with Controllers in MVC eg: UsersController
.
DashboardContainer.jsx
const DashboardContainer = () => {
const [news, newsError] = useCustomFetch("/news");
const [user, userError] = useCustomFetch("/user");
const [trends, trendsError] = useCustomFetch("/trends");
const [notifications] = useCustomFetch("/notifications");
if (news) {
// sort news for tags
// sort news for "sort options"
// perform some custom operations on news
// do something else like caching?
}
if (trends) {
// sort trends for tags
// sort trends for "sort options"
// perform some custom operations on trends
// do something else like caching?
}
if (notifications) {
// sort notifications for tags
// sort notifications for "sort options"
// perform some custom operations on notifications
// do something else like caching?
}
return (
<Dashboard
notifications={notifications}
trends={trends}
news={news}
user={user}
{/* all your other props */}
/>
);
};
Now, your dashboard component will look like this:
const Dashboard = ({ user, notifications, ... }) => {
return (
<div>
<h2>user</h2>
loading handler
map cards
display available tags
display sort options
<h2>notifications</h2>
loading handler
map cards
display available tags
display sort options
<h2>Latest News</h2>
loading handler
map cards
display available tags
display sort options
<h2>Trends</h2>
loading handler
map cards
display available tags
display sort options
</div>
);
};
This way, you can have all your logic in one component and pass all data needed in the UI through props.
Tip 2: (Tidy man's props😂)
I gave this tip a such a ridiculous name because I actually discovered this while I was trying to beautify my code and cut down a bunch of lines. What does this whole thing involve? Let's take a look. In the above tip, we passed our props like this:
<Dashboard
notifications={notifications}
trends={trends}
news={news}
user={user}
/>
This is fine, but sometimes, you just need something a bit straightforward and easier to grasp. We can replace the above code with this:
const props = { notifications, trends, news, user };
<Dashboard {...props} />
Clean, simple and very readable😊
Tip 3: (Error Boundaries)
According to the react docs, Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
Basically, a part of your app crashing won't drag the whole app down with it, and on top of that, you get to display a custom fallback UI and log/report the errors associated with your app crash. All you need to do is to create your error boundary and pass your components as props. I usually wrap my whole app with the error boundary.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
And wrap the component you want to "protect"
<ErrorBoundary>
<App />
</ErrorBoundary>
That's all. You can check out the docs demo here.
Tip 4: (Picking your libraries)
Like it or not, libraries determine how you write and organize your code. You might have a way of doing something, but a library will ultimately determine what input it takes in and how it works.
One problem I've always had with react is how other libraries usually don't fit into your react app, require a lot of boilerplate code, or how they just have these weird operations😓 Redux meets all these criteria btw😓
There's some good news though, there's usually always an easier/smaller option if you look hard enough. For example, most projects don't need all of redux's features, just a global state, maybe reducers, a setter and a getter😅 You can try libraries like Zustand, Reactn and the multipurpose React Query.
If you want a simpler routing experience, you can also try out Glass Router which takes a friendlier approach to the whole routing business.
Just remember, the community always has simpler, smaller and usually faster alternatives.
Tip 5: (Relative imports)
This applies to CRA users
We usually have different directories for assets, views and all those in our app. This usually leads to uncomfortable imports with ../../..
. There are a bunch of solutions for this, however, the most used, which I also prefer is to reconfigure webpack to use relative paths: Instead of ../../assets
, we can have @/assets
setup
We basically want to edit our CRA setup without having to eject
first. There are some nice libraries for this, which we'll install in our project:
yarn add react-app-rewired customize-cra
From there, we create a config-overrides.js
file and dump this code in:
const { override, addWebpackAlias } = require("customize-cra");
const path = require("path");
module.exports = override(
addWebpackAlias({
["@"]: path.resolve(__dirname, "src"),
})
);
From there, we head over to our package.json
scripts section and replace react-scripts
with react-app-rewired
like so:
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
That's it for the CRA + JS users!
If you're using TypeScript with CRA, you need to add the following so the compiler doesn't shout at you for using @ in your imports.
Create a new file like tsconfig.base.json
in your project root (at the same level as your package.json) and add the following:
{
"compilerOptions": {
"paths": {
"@/*": [
"src/*"
]
}
},
}
We're not adding this in the main tsconfig.json
because TypeScript will rewrite the tsconfig.json
and throw this error:
The following changes are being made to your tsconfig.json file:
- compilerOptions.paths must not be set (aliased imports are not supported)
Now to get this to work, you simply need to extend this in your main tsconfig.json
file:
{
"extends": "./tsconfig.base.json",
You may need to restart your editor for this to take effect (TypeScript users only). From there, you can start replacing all your uncomfortable imports😇
Thanks for reading
These are a few tips and tricks which have helped me speed up my workflow, keep my code neat and basically help on my quest for laziness😇
If you have anything you'll like to share, a new tip, a faster way to do something I mentioned, something you don't agree with, just reach out to me. Thanks!
Top comments (12)
I feel like you could go further and refactor by passing an array of components that you'd want to render into a dashboard container. The container itself could use Flexbox or Grid to stylize the render and not carrying which component is coming in.
Given this, I would create template containers that are specific to its context vs. passing 4 different contexts to a dashboard and having the dashboard responsible for the UI. I feel it would be much more modular as a result.
If I get you right, you mean a different container for every segment, so you don't mix up different contexts, correct? I do that for really big components or when I need to fetch stuff separately or just make a reusable block of code for multiple pages. Otherwise, I just stick to something similar to what I did above.
Cool....Yup. Separation of concerns and isolating functionality to its own container that you can just pass around and use wherever.
Great Tips!.
IMO, Tip #1 can be improved futher by using custom hooks. If we have stateful logic that are to be shared, it is better to use a custom hook.
Came here to say this! Dan Abramov added an update to a blog he wrote recommending this "container" style here, essentially saying that he thinks hooks are now a better solution.
Thanks for the info😊
These are awesome!
was great , thank you so much🙏😍
Loved the post man, just a simple add-on
if someone is using airbnb eslint spreading the props will launch a problem, hopefully someone reads this while looking it up haha
Oh wow, didn't know that. Is there a workaround?
These are awesome!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.