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:
import { ComponentType } from 'react';
export type PreloadableComponent<T extends ComponentType<any>> = T & {
preload: () => Promise<void>;
};
import { lazy, ComponentType, createElement } from 'react';
import { PreloadableComponent } from 'shared/types/component';
const lazyWithPreload = <T extends ComponentType<any>>(
factory: () => Promise<{ default: T }>
) => {
let LoadedComponent: T | undefined;
let factoryPromise: Promise<void> | undefined;
const LazyComponent = lazy(factory);
const loadComponent = () =>
factory().then(module => {
LoadedComponent = module.default;
});
const Component = (props =>
createElement(
LoadedComponent || LazyComponent,
props
)) as PreloadableComponent<T>;
Component.preload = () => factoryPromise || loadComponent();
return Component;
};
export 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
.
import { IBlogPost } from 'shared/types/models/blog';
import lazyWithPreload from 'shared/components/lazy-with-preload';
const post: IBlogPost = {
id: 2,
title: 'Whole year of reading (2019)',
description: 'Complete list of my 2019 reads.',
date: '2020-01-10',
slug: 'whole-year-of-reading-2019',
PreloadableContent: lazyWithPreload(() => import('./content.mdx')),
};
export 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.
import React from 'react';
import { Link } from '@reach/router';
import { IBlogPost } from 'shared/types/models/blog';
import { StyledContent } from './BlogListItemStyles';
interface IProps {
post: IBlogPost;
}
const BlogListItem = ({ post }: IProps) => {
const { title, description, date, slug, PreloadableContent } = post;
const preloadPost = () => PreloadableContent.preload();
return (
<StyledContent>
<Link to={`/${slug}`} onMouseEnter={preloadPost}>
{title}
</Link>
<span>{date}</span>
<p>{description}</p>
</StyledContent>
);
};
export default BlogListItem;
Happy coding 🙌
Top comments (0)