Technology Blog

Look deep into latest news and innovations happening in the Tech industry with our highly informational blog.

React Tutorial: Hooks and Performance

hkis
Hooks are quite possibly the most eagerly awaited addition to React since the rewrite. Does the product live up to the hype? From my perspective, yes, as they really are a great feature. They are essentially functions that open up new opportunities, such as:

  • Allows the removal of a lot of class components that we only used because we couldn’t have, e.g., a local state or ref, so the code for a component looks easier to read.
  • Enables you to use less code for the same effect.
  • Makes functions way easier to think about and test, e.g., by using the react-testing-library.
  • Can also take parameters, and the result of one can be easily be used by another hook (e.g., setState from useState in useEffect).
  • Minifies way better than classes, which tend to be a bit more problematic for minifiers.
  • Might remove HOCs and render props patterns in your app which introduced new problems despite having been designed to solve others.
    Capable of being custom-built by any skilled React developer.

There are few React hooks that are included as default. The three basic ones are useState, useEffect, and useContext. There are also several additional ones, e.g., useRef and useMemo, but for now, we shall focus on the basics.

Let’s take a look at useState, and let’s use it to create an example of a straightforward counter. How does it work? Well, basically, the whole construct is really straightforward and looks like:

export function Counter() {
 const [counter, setCounter] = React.useState(0);
 return (
   
<div>
     {counter}
     <button onClick={() => setCounter(counter + 1)}>+</button>
   </div>

 );
};

It is invoked with initialState (value) and returns an array with two elements with it. Thanks to array destructuring assignment, we can assign the variables to these elements right away. The first one is always the last state after updates, while the other one is a function that we will use to update the value. Seems rather easy, doesn’t it?

Also, due to the fact that such components used to be called stateless functional components, such a name is not appropriate anymore, because they can have a state as it is shown above. Hence, the names class components and function components seem to be more in line with what they actually do, at least from 16.8.0.

The update function (in our case setCounter), can also be used as a function which will take the previous value as an argument in the following form:

<button onClick={() => setCounter(prevCounter => prevCounter + 1)}>+</button>
<button onClick={() => setCounter(prevCounter => prevCounter - 1)}>-</button>

However, unlike the this.setState class component which was doing a shallow merge, setting the function (setCounter in our case) is overriding the whole state instead.

In addition, initialState can also be a function, not just a plain value. This has its own benefits, as that function will be run only during the initial render of the component and after that, it won’t be invoked anymore.

const [counter, setCounter] = useState(() =&gt;  calculateComplexInitialValue());

Finally, if we are going to use setCounter with the exact same value that we had at the very same moment in the current state (counter), then component will not rerender.

On the other hand, useEffect is about adding side effects to our functional component, be it subscriptions, API calls, timers, or just about anything that we may find useful. Any function that we will pass to useEffect will be run after render, and it will be doing so after every render unless we add a limitation concerning what properties’ changes should be rerun as a second argument of the function. If we want to run it only on mount and clean up on unmount, then we just need to pass an empty array in it.

const fetchApi = async () => {
 const value = await fetch("https://jsonplaceholder.typicode.com/todos/1");
 console.log(await value.json());
};
export function Counter() {
 const [counter, setCounter] = useState(0);
 useEffect(() => {
   fetchApi();
 }, []);
 return (
   
<div>
     {counter}
     <button onClick={() => setCounter(prevCounter => prevCounter + 1)}>+</button>
     <button onClick={() => setCounter(prevCounter => prevCounter - 1)}>-</button>
   </div>

 );
};

The above code will be only run once due to an empty array as a second argument. Basically, it is something like componentDidMount in such case, but it fires a bit later. If you want to have a similar hook that is called before browser paint, use useLayoutEffect, but these updates will be applied synchronously, unlike useEffect.

useContext seems to be the easiest to understand, as one provides to which context we want to get access (an object that was returned by the createContext function) and in return, it provides us with the value for that context.

const context = useContext(Context);

Finally, to write your own hook, you can just write something like the following:

function useWindowWidth() {
 let [windowWidth, setWindowWidth] = useState(window.innerWidth);
 function handleResize() {
   setWindowWidth(window.innerWidth);
 }
 useEffect(() => {
   window.addEventListener('resize', handleResize);
   return () => window.removeEventListener('resize', handleResize);
 }, []);
 return windowWidth;
}

Basically, we are using the regular useState hook for which we are assigning as initial value window width. Then in useEffect, we are adding a listener which will trigger handleResize on each window resize. We also clear after component will be unmounted (look at the return in useEffect). Easy?

Note: The word used in all hooks is important. It’s used because it allows React to check if you are not doing something bad, e.g., call hooks from regular JS functions.

Checking Types

React had its own props checking, before Flow and TypeScript were an option.

PropTypes checks if the properties (props) that are received by a React component and checks if they’re in line with what we have. Whenever a different situation occurs (e.g., object instead of an array), we will get a warning in the console. It is important to note that PropTypes are only checked in development mode due to their impact on performance and the aforementioned console warning.

As of React 15.5, PropTypes are in a different package that needs to be installed separately. They are declared along properties in a static property called propTypes (surprise), combining them with defaultProps which are used if the properties are undefined (undefined is the only case). DefaultProps are not related to PropTypes, but they can solve some warnings that might appear due to PropTypes.

The other two options are Flow and TypeScript, and they are way more popular nowadays (especially TypeScript).

  • TypeScript is a typed superset of JavaScript, developed by Microsoft, that can check errors before an app is even running and provides superior autocomplete functionality for development. It also greatly improves refactoring. Due to Microsoft support, which has extensive experience with typed languages, it is a rather safe choice as well.
  • Flow is not a language, unlike TypeScript. It is a static type checker for JavaScript, so it is more akin to a tool that you include in JavaScript than a language. The whole idea behind Flow is quite similar to what TypeScript offers. It allows you to add types so it is less likely to have any bugs before you run the code. Just like TypeScript, Flow is now supported in CRA (Create React App) from the start.

Personally, I find TypeScript faster (practically instantaneous), especially in autocomplete, which seems a bit slower with Flow. It’s worth noting that IDEs such as WebStorm, which I personally use, employ a CLI for integration with Flow. However, it seems even easier to integrate optional use in files, where you simply add // @flow at the beginning of the file to start type checking. Also, from what I can tell, it seems that TypeScript won the battle versus Flow in the end—it is way more popular now, and some of the most popular libraries are getting refactored from Flow to TypeScript.

There are a few more options, mentioned also in official documentation, such as Reason (developed by Facebook and gaining popularity in the React community), Kotlin (a language developed by JetBrains) and more.

Obviously, for front-end developers, the easiest approach would be to hop in and start using Flow and TypeScript, rather than switch to Kotlin or F#. However, for back-end developers that are transitioning to front-end, these might actually be easier to get started with.

Production and React Performance

The most basic and obvious change that you need to do for production mode is to switch to “production” for DefinePlugin and add UglifyJsPlugin in the case of Webpack. In the case of CRA, it is as simple as using npm run build (which will be running react-scripts build). Be aware that Webpack and CRA are not the only options, as you can use other build tools like Brunch. This is usually covered in official documentation, be it official React documentation or documentation for a specific tool. To make sure that the mode is set correctly, you can use React Developer Tools, which will give you an indication of what kind of build you are using (production vs. development). The aforementioned steps will make your application run without checks and warnings that come from React and the bundle itself will be minimized, too.

There are a few more things you can do for your React app. What do you do with the JS file that is built? You can start with just “bundle.js” if the size is relatively small, or maybe do something like “vendor + bundle” or maybe “vendor + smallest required part + import things when they are needed.” This is useful when you are dealing with a very big app and you do not need to import everything at the very beginning. Be aware that bundling some JavaScript code in the main bundle that is not even being used will simply increase the bundle size and make the app load slower at the very beginning.

Vendor bundles can be useful if you are planning on freezing libraries’ versions upon realizing they might not change for a long time (if ever). Also, bigger files are better at gzipping so the benefit that you get from separating might sometimes not be worth it. It depends on the file size and sometimes you will simply need to try it yourself.

Code Splitting

Code splitting can appear in more ways than suggested here, but let’s focus on what we have available in CRA and React itself. Basically, in order to split code into different chunks, we can use import() which works thanks to Webpack (import itself is a proposal in Stage 3 as of now, so it is not part of the language standard just yet). Whenever Webpack sees import, it will know that it needs to start code splitting at this stage and cannot include it in the main bundle (the code that it is inside the import).

Now we can connect it with React.lazy() which requires import() with a file path containing the component that needs to be rendered in that place. Next, we can use React.suspense() which will display a different component in that place until the imported component is loaded. One might wonder; if we are importing a single component, then why we would need it?

That’s not quite the case, as React.lazy() will be showing the component we import(), but import() might fetch a bigger chunk than that single component. For example, that particular component might have other libraries in tow, more code, etc., so one file isn’t necessary—it might be many more files bundled together. Finally, we can wrap all of that in ErrorBoundary (you can find the code in our section on error boundaries) which will serve as a fallback if something fails with the component we wanted to import (e.g., if there’s a network error).

import ErrorBoundary from './ErrorBoundary';
const ComponentOne = React.lazy(() => import('./ComponentOne'));
function MyComponent() {
   return (
       <ErrorBoundary>
           <React.Suspense fallback={
<div>Loading...</div>

}>
               <ComponentOne/>
           </React.Suspense>
       </ErrorBoundary>
   );
}

This is a basic example, but you can obviously do more. You can use import and React.lazy for dynamic route splitting (e.g., admin vs. regular user, or just really big paths that bring a lot). Be aware that React. lazy only supports default exports as of now and doesn’t support server-side rendering.

React Code Performance

Regarding performance, if your React application is acting laggy, there are two tools that can help you figure out the issue.

The first one is Chrome Performance Tab, which will tell you what happens with each component (e.g., mount, update). Thanks to that, you should be able to identify which component is exhibiting performance issues trouble and then optimize it.

The other option is to use DevTools Profiler which became available in React 16.5+, and with cooperation from shouldComponentUpdate (or PureComponent, which was explained in part one of this tutorial), we can improve performance for some critical components.

Obviously, employing basic best practices for the web is optimal, such as debouncing some events (e.g., scroll), being cautious with animations (using transform instead of changing the height and animating it) and so on. Using best practices can be overlooked very easily, especially if you are just getting to grips with React.

For more Information and to build a website using React JS, Hire React Developer from us as we give you a high-quality product by utilizing all the latest tools and advanced technology. E-mail us any clock at – hello@hkinfosoft.com or Skype us: “hkinfosoft“.

To develop your custom website using React JS, please visit our technology page.

Content Source:

  1. toptal.com