Although React is one of the most popular and most used front-end frameworks in the world, many developers still struggle when it comes to refactoring code for improved reusability. If you’ve ever found yourself repeating the same snippet of code all throughout your React app, you’ve come to the right article.
In this tutorial, you’ll be introduced to the three most common indicators that it’s time to build a reusable React component. Then we’ll go on to look at some practical demos by building a reusable layout and two exciting React hooks.
By the time you’ve finished reading, you’ll be able to figure out by yourself when it’s appropriate to create reusable React components, and how to do so.
This article assumes a basic knowledge of React and React hooks. If you want to brush up on these topics, I recommend you to check out “Getting Started with React” guide and “Intorduction to React Hooks”.
Top Three Indicators of a Reusable React Component
First let’s look at some indications of when you might want to do this.
Repetitive creation of wrappers with the same CSS style
My favorite sign of knowing when to create a reusable component is the repetitive use of the same CSS style. Now, you may think, “Wait a minute: why don’t I simply assign the same class name to elements that share the same CSS style?” You’re absolutely right. It’s not a good idea to create reusable components every time some elements in different components share the same style. In fact, it may introduce unnecessary complexity. So you have to ask yourself one more thing: are these commonly styled elements wrappers?
For example, consider the following login and signup pages:
import './common.css';
function Login()
);
}
import './common.css';
function Signup()
);
}
The same styles are being applied to the container (the <div>
element) and the footer of each component. So in this case, you can create two reusable components — <Wrapper />
and <Footer />
— and pass them children as a prop. For example, the login component could be refactored as follows:
import Footer from "./Footer.js";
function Login() } footer= />
);
}
As a result, you no longer need to import common.css
in multiple pages or create the same <div>
elements to wrap everything.
Repetitive use of event listeners
To attach an event listener to an element, you can either handle it inside useEffect()
like this:
import from 'react';
function App()
useEffect(() =>
}, []);
return (...);
}
Or you can do it directly inside your JSX like this, as is demonstrated in the following button component:
function Button() }>
Click me!
);
};
When you want to add an event listener to document
or window
, you’d have to go with the first method. However, as you may have already realized, the first method requires more code with the use of useEffect()
, addEventListener()
and removeEventListener()
. So in such case, creating a custom hook will allow your components to be more concise.
There are four possible scenarios for using event listeners:
- same event listener, same event handler
- same event listener, different event handler
- different event listener, same event handler
- different event listener, different event handler
In the first scenario, you can create a hook where both the event listener and the event handler are defined. Consider the following hook:
import from 'react';
export default function useKeydown()
useEffect(() =>
}, []);
};
You can then use this hook in any component as follows:
import useKeydown from './useKeydown.js';
function App() ;
For the other three scenarios, I recommend creating a hook that receives an event and an event handling function as props. For example, I will pass keydown
and handleKeydown
as props to my custom hook. Consider the following hook:
import from 'react';
export default function useEventListener( )
}, []);
};
You can then employ this hook in any component as follows:
import useEventListener from './useEventListener.js';
function App()
useEventListener('keydown', handleKeydown);
return (...);
};
Repetitive use of the same GraphQL script
You don’t really need to look for signs when it comes to making GraphQL code reusable. For complex applications, GraphQL scripts for a query or a mutation easily take up 30–50 lines of code because there are many attributes to request. If you’re using the same GraphQL script more than once or twice, I think it deserves its own custom hook.
Consider the following example:
import from "@apollo/react-hooks";
const GET_POSTS = gql`
query getPosts
emojis
...
}
`;
const = useQuery(GET_POSTS, );
Instead of repeating this code in every page that requests posts from the back end, you should create a React hook for this particular API:
import from "@apollo/react-hooks";
function useGetPosts() `;
const = useQuery(GET_POSTS, );
return [data];
}
const Test = () => )}
);
};
Building Out Three Reusable React Components
Now that we’ve seen some common signs of when to create a new component that you can share throughout your react application, let’s put that knowledge into practice and build out three practical demos.
1. Layout component
React is normally used for building complex web apps. This means that a large number of pages need to be developed in React, and I doubt that every page of an app will have a different layout. For instance, a web app consisting of 30 pages usually uses less than five different layouts. Therefore, building a flexible, reusable layout that can be utilized in many different pages is essential. This will save you very many lines of code and consequently a tremendous amount of time.
Consider the following React functional component:
import React from "react";
import style from "./Feed.module.css";
export default function Feed() >
Header
>
className=>
))}
}
Footer
);
}
const itemData = [1, 2, 3, 4, 5];
This is a typical web page that has a <header>
, a <main>
and a <footer>
. If there are 30 more web pages like this, you would easily get tired of repeatedly writing HTML tags and applying the same style over and over.
Instead, you can create a layout component that receives <header>
, <main>
and <footer>
as props, as in the following code:
import React from "react";
import style from "./Layout.module.css";
import PropTypes from "prop-types";
export default function Layout() >
);
}
Layout.propTypes = ;
This component doesn’t require <header>
and <footer>
. So, you can use this same layout for pages regardless of whether they contain a header or a footer.
Using this layout component, you can turn your feed page into a much more sophisticated block of code:
import React from "react";
import Layout from "./Layout";
import style from "./Feed.module.css";
export default function Feed() >Header}
main=>
className=>
))}
}
footer=>Footer}
/>
);
}
const itemData = [1, 2, 3, 4, 5];
Pro tip for creating layouts with sticky elements
Many developers tend to use position: fixed
or position: absolute
when they want to stick a header to the top of the viewport or a footer to the bottom. However, in the case of layouts, you should try to avoid this.
Since the elements of a layout will be the parent elements of passed props, you want to keep the style of your layout elements as simple as possible — so that passed <header>
, <main>
, or <footer>
are styled as intended. So, I recommend applying position: fixed
and display: flex
to the outermost element of your layout and setting overflow-y: scroll
to the <main>
element.
Here’s an example:
.Container
.Main
Now, let’s apply some styles to your feed page and see what you’ve built:
.FeedHeader
.FeedFooter
.ItemList
.Item
.FeedHeader,
.FeedFooter,
.Item
Sticky header and footer demo
And here’s the code in action.
This is what it looks like on desktop screens.
This is what it looks like on mobile screens.
This layout works as intended on iOS devices, too! In case you don’t know, iOS is notorious for bringing unexpected position-related problems to the development of web apps.
2. Event Listener
Often, the same event listener is used more than once throughout a web app. In such a case, it’s a great idea to create a custom React hook. Let’s learn how to do this by developing a useScrollSaver
hook, which saves the scroll position of a user’s device on a page — so that the user doesn’t need to scroll all again from the top. This hook will be useful for a web page in which a large number of elements, such as posts and comments, are listed; imagine the feed pages of Facebook, Instagram and Twitter without a scroll saver.
Let’s break down the following code:
import from "react";
export default function useScrollSaver(scrollableDiv, pageUrl) -scrollPosition`,
scrollableDiv.current.scrollTop.toString()
);
};
useEffect(() => ;
}
}, [scrollableDiv, pageUrl]);
useEffect(() => -scrollPosition`)
) -scrollPosition`)
);
scrollableDiv.current.scrollTop = prevScrollPos;
}
}, [scrollableDiv, pageUrl]);
}
You can see that the useScrollSaver
hook needs to receive two items: scrollableDiv
, which must be a scrollable container just like the <main>
container in your layout above, and pageUrl
, which will be used as an identifier of a page so that you can store scroll positions of multiple pages.
Step 1: Save the scroll position
First of all, you need to bind a “scroll” event listener to your scrollable container:
const scrollableBody = scrollableDiv.current;
scrollableBody.addEventListener("scroll", handleScroll);
return function cleanup() ;
Now, every time scrollableDiv
is scrolled by a user, a function called handleScroll
will be run. In this function, you should utilize either localStorage
or sessionStorage
to save the scroll position. The difference is that data in localStorage
doesn’t expire, while data in sessionStorage
is cleared when the page session ends. You can use setItem(id: string, value: string)
to save data in either storage:
const handleScroll = () => -scrollPosition`,
scrolledDiv.current.scrollTop.toString()
);
};
Step 2: Restore the scroll position
When a user comes back to a web page, the user should be directed to his or her previous scroll position — if there is any. This position data is currently saved in sessionStorage
, and you need to take it out and use it. You can use getItem(id: string)
to obtain data from the storage. Then, you simply need to set scroll-top
of the scrollable container to this obtained value:
const prevScrollPos = Number(
sessionStorage.getItem(`$scrollPosition`)
);
scrollableDiv.current.scrollTop = prevScrollPos;
Step 3: Use useScrollSaver
hook in any web page
Now that you’ve finished creating your custom hook, you can use the hook in any web page you’d like as long as you pass the two required items to the hook: scrollableDiv
and pageUrl
. Let’s go back to Layout.js
and use your hook in there. This will allow any web page that uses this layout to enjoy your scroll saver:
import React, from "react";
import style from "./Layout.module.css";
import PropTypes from "prop-types";
import useScrollSaver from "./useScrollSaver";
export default function Layout() >
);
}
Scrollsaver Demo
And here’s the code running in a Sandbox. Try scrolling the page, then using the arrow in the bottom left and corner to reload the app.
You’ll find yourself positioned at where you left off!
3. Query/Mutation (specific to GraphQL)
If you like to use GraphQL with React, as I do, you can reduce your codebase even further by creating a React hook for GraphQL queries or mutations.
Consider the following example for running a GraphQL query getPosts()
:
import from "@apollo/react-hooks";
const GET_POSTS = gql`
query getPosts
emojis
...
}
`;
const = useQuery(GET_POSTS, );
If there are more and more attributes to request from the back end, your GraphQL script will take up more and more space. So, instead of repeating the GraphQL script and useQuery
every time you need to run query getPosts()
, you can create the following React hook:
import from "@apollo/react-hooks";
export default function useGetPosts()
emojis
...
}
`;
const = useQuery(GET_POSTS, );
return [data, loading, error];
}
Then, you can use your useGetPosts()
hook as follows:
import React from "react";
import Layout from "./Layout";
import style from "./Feed.module.css";
import useGetPosts from "./useGetPosts.js";
export default function Feed() >Header}
main=>
className=>
))}
}
footer=>Footer}
/>
);
}
Conclusion
In this article, you’ve learned the three most common indicators of a reusable React component and the three most popular uses cases. Now you have the knowledge of when to create a reusable React component and how to do so easily and professionally. You’ll soon find yourself enjoying refactoring lines of code into a sophisticated reusable React component or hook. Using these refactoring techniques, our development team at Clay was able to reduce our codebase to a manageable size. I hope you can, too!