Preloading React components

userMarko Marinović

In previous post I wrote about code-splitting and how it improves application performance. This works great, but what about user experience? A loader is displayed each time the app needs to load additional code to run. This can get annoying, especially on slower connections. What we can do to improve this is to assume the user's next action. Is user scrolling thought the blog list and hovering over a specific post? If yes, then a user is likely to click on that post to get more info. Making this assumption allows us to preload content for the post, rendering preloaded content on the actual click.

Preloading implementation

I created a simple function lazyWithPreload to help in this case. It's a wrapper around React.lazy with additional preloading logic that returns special PreloadableComponent.

Code for lazyWithPreload and PreloadableComponent is available down here:

1import { ComponentType } from 'react';
2
3export type PreloadableComponent<T extends ComponentType<any>> = T & {
4  preload: () => Promise<void>;
5};
1import { lazy, ComponentType, createElement } from 'react';
2import { PreloadableComponent } from 'shared/types/component';
3
4const lazyWithPreload = <T extends ComponentType<any>>(
5  factory: () => Promise<{ default: T }>
6) => {
7  let LoadedComponent: T | undefined;
8  let factoryPromise: Promise<void> | undefined;
9
10  const LazyComponent = lazy(factory);
11
12  const loadComponent = () =>
13    factory().then(module => {
14      LoadedComponent = module.default;
15    });
16
17  const Component = (props =>
18    createElement(
19      LoadedComponent || LazyComponent,
20      props
21    )) as PreloadableComponent<T>;
22
23  Component.preload = () => factoryPromise || loadComponent();
24
25  return Component;
26};
27
28export default lazyWithPreload;

lazyWithPreload take a single argument, factory and returns a special component that acts in two different ways. When preload is initiated, factory gets called, loading the component. Loaded component is stored and rendered when the app renders PreloadableComponent. Another case is when component is not preloaded via preload, then PreloadableComponent acts like a regular React.lazy component.

Using it with blog list

The idea is to preload content for a post on post title hover. IBlogPost has a property PreloadableContent which utilizes lazyWithPreload.

1import { IBlogPost } from 'shared/types/models/blog';
2import lazyWithPreload from 'shared/components/lazy-with-preload';
3
4const post: IBlogPost = {
5  id: 2,
6  title: 'Whole year of reading (2019)',
7  description: 'Complete list of my 2019 reads.',
8  date: '2020-01-10',
9  slug: 'whole-year-of-reading-2019',
10  PreloadableContent: lazyWithPreload(() => import('./content.mdx')),
11};
12
13export default post;

BlogListItem displays preview for single post in the list. Hovering on post title link initializes the content preload. Now the content is loaded and loader will not appear when navigating to the post details.

1import React from 'react';
2import { Link } from '@reach/router';
3import { IBlogPost } from 'shared/types/models/blog';
4import { StyledContent } from './BlogListItemStyles';
5
6interface IProps {
7  post: IBlogPost;
8}
9
10const BlogListItem = ({ post }: IProps) => {
11  const { title, description, date, slug, PreloadableContent } = post;
12  const preloadPost = () => PreloadableContent.preload();
13
14  return (
15    <StyledContent>
16      <Link to={**/${slug}**} onMouseEnter={preloadPost}>
17        {title}
18      </Link>
19      <span>{date}</span>
20      <p>{description}</p>
21    </StyledContent>
22  );
23};
24
25export default BlogListItem;

Happy coding 🙌

We use cookies

We use cookies to optimize user experience. By continuing to use our website, you agree to our privacy policy.

Accept