8 Miraculous Ways to Bolster Your React Apps
August 25th, 2019
Sometimes when we build our apps in React we can easily miss opportunities to improve our app, and that's probably because when our app just works and feels fast we tolerate it for being perfect. As developers we might assume that if the outcome of our projects look normal to us, then it would look normal to the users. When our minds think that way, this may cause us to overlook areas in our code that can optimized for a better result.
This article will go over 8 Miraculous Ways to Bolster Your React Apps.
The first way to bolster your react apps is to love your identities.
It's important to remember that you can wrap variables and functions with React.useMemo
as you can grant them the ability to memoize themselves so that react knows they remain identical for future renders.
Otherwise if you don't memoize them, their references will disappear from future renders. This might hurt their feelings, so you can show them that you love them and would like to keep them by memoizing them. If you love them, they'll love you back by making sure they take care of you and your app by helping to avoid wasteful operations for the situations they're in.
For example, let's pretend that we are making a custom hook that takes in a list of urls
as arguments so that it can accumulate them into an array of promises to be resolved with Promise.all
. The results will be inserted to the state and passed to the App
component as soon as it's finished. Our promises list will map over the urls
array which contains 4 different urls to fetch:
Our task was to fetch data from these 4 links, so ideally only 4 requests should be sent out. But if we take a look at the network tab inside Chrome, the truth is that it sent out 8 requests. This is because the urls
argument does not retain the same identity as previously because when App
re-renders, it's instantiating a new array every time--so React treats it as a changed value.
Computer programs sometimes think they can outsmart us and get away with this lousy behavior. In order to fix this we can use React.useMemo
so that the array of promises doesn't recompute itself on every render as long as the array containing the urls does not change.
Let's refactor our code to apply this concept:
If we run this now, it will still be sending 8 requests. That's because although we memoized the urls
array, we also need to memoize the promises
variables inside our hook because that is also instantiating itself whenever the hook runs:
Our code should now only send 4 requests when we run it. Hurray!
Sometimes we can get in a situation where we want to sneak in a prop to be merged with children before proceeding to render. React lets you view the props of any react element as well as others, such as exposing its key
.
We can just wrap the children element with a new component and inject the new props from there or we can just merge the new props by using this method.
For example, lets say we have an App
component that is using a useModal
hook which provides some handy utilities to manage modals by providing controls like open
, close
, and opened
. We want to pass these props to a VisibilityControl
component because it's going to provide additional functionality before passing the modal data down to the children:
VisibilityControl
makes sure that activated
is true
before allowing opened
to be used normally by its children. If this was used in a secret route the VisibilityControl
provides the functionality of preventing unactivated users from seeing secret content.
There might come a time when you need to combine two or more reducers in the app to make a larger one. This approach is similar to how combineReducers
worked in react-redux.
Let's pretend we were planning to make a giant microservice app where we originally planned to designate each part in the app to be in charge of their own context/state but then we just thought of a million dollar idea of an app that requires the states to be united into a single large state instead so we can manage them all in the same environment.
We have an authReducer.js
, ownersReducer.js
, and frogsReducer.js
that we'd like to combine:
authReducer.js
ownersReducer.js
frogsReducer.js
We'll import them into our main file and define the state structure there:
App.js
You would then just work with hooks as you normally would with calling dispatch
, passing in the matching type
and arguments to the designated reducer.
The most important part to look at is the rootReducer
:
Projects benefit immensely from Sentry when integrated with React. Having detailed reports of errors all sent to a central location to be analyzed at once is a very important tool to have!
Once you npm install @sentry/browser
and have it set up for your react app, you can log into sentry.io after you create your account and analyze your error reports in your project's dashboard.
These reports are really detailed, so you'll benefit from feeling like an FBI agent by getting fed tons of information to help you solve those errors like knowing the user's device, browser, the URL the error occurred, the user's IP address, the stack trace of the error, was the error handled or not, the function name, the source code, a useful list of breadcrumbs exposing a trace of network actions that led to the error, headers, and more.
Here's a screenshot of what this may look like:
You can also have several team members comment on different things so it can also be a collaborative environment.
window.fetch
Unless you don't care about Internet Explorer users, you should not use window.fetch
for your react apps because none of the IE browsers support window.fetch
unless you provide a polyfill. Axios is great to support IE but also good for the additional functionality it brings to the table like canceling requests mid-flight. This window.fetch
actually applies to any web app and not specific to React. The reason it's in this list is because it's not uncommon that window.fetch
is used in React apps today. And since react apps goes through transpiling/compiling stages depending on the tools configured, it can be quite tempting to accidentally assume that it transpiles window.fetch
.
Even though React.useRef
is the new kid on the block for attaching and controlling references to a DOM node, it's not always the best option.
Sometimes you might need more control to a DOM node so that you can provide additional functionality.
For example, the react docs show a situation where you'll need to use a callback ref to ensure that even when there are changes to the current ref value, a component outside can still be notified of updates. This is the advantage of callback refs over useRef
.
Material-ui makes use of this powerful concept to attach additional functionality throughout their component modules. The great part about this is that cleanup naturally emerges from this behavior. Wonderful!
useWhyDidYouUpdate
This is a custom hook to expose changes that make our components re-render. Sometimes when a memoizer like the higher order component React.memo
isn't enough you can use this handy hook to find which props you need to consider memoizing instead: (Credits to Bruno Lemos)
You would then use it like this:
This is going to be quoted from my previous article from awhile ago because it's a little long and it fits greatly in this post. Here is the content:
Let me give a real life example as I'd like to put a little more emphasize on this one.
One of the greatest benefits of higher order functions is that when used correctly, it will save a lot of time for you and for those around you.
At my job, we used react-toastify to display notifications. We used it every where. In addition, they also make great escape hatches for quick last minute UX decisions: "How should we handle this error? Just display a toast notification!" Done.
However, we started noticing that when the app became larger and the level of complexity was creeping up on us, our toast notifications were becoming too frequent. This is fine--however, we didn't have a way of preventing duplicates. This meant that some toast notifications were showing up multiple times on the screen even when they were exactly the same as the toast above it.
So we ended up leveraging the api that the library provides to help remove active toast notifications by id using toast.dismiss().
In order to explain the parts ahead, it's probably a good idea to show the file we were importing toasts from before proceeding:
Now bear with me, I know this might not look appealing. But I promise it will get better in two minutes.
This is what we had in a separate component to check if a previous toast was already on the screen. And if there was, it will attempt to remove that toast and re-display the new toast.
This was working fine--however, we had other toasts throughout the app that needed to be modified the same way. We had to go through every file that displays a toast nofication to remove duplicates.
When we think of going through every file in 2019, we immediately knew that it wasn't the solution. So we looked at the util/toast.js
file and refactored that to solve our problem instead. Here's what it looked like afterwards:
src/util/toast.js
Instead of having to go through every file, the simplest solution was to create a higher order function. Doing this allowed us to "reverse" the roles so that instead of us searching through files, the toasts were instead directed to our higher order function.
This way the codes in the files were not modified or touched. They still function as normal, and we gained the ability to remove duplicate toasts without going anywhere to write unnecessary code in the end. This saved time.
And that concludes the end of this article! I hope you found it useful and look out for more in the future!