React Custom Hooks #2: Optimize Your Font Size with useFontSize

Improve your React app's user experience with the useFontSize custom hook. Discover how to optimize your font sizes and improve accessibility with ease.

#react
#javascript
#hooks
React Custom Hooks #2: Optimize Your Font Size with useFontSize
Picture by Marco

Following the first article about React Custom Hooks, today we’ll see how to simplify and reuse across our apps another piece of logic, enhancing our skills as React developers little by little. I’m talking about the useFontSize custom hook, let’s see together why is it so useful!

Why the need for a useFontSize hook?

Using the Medium app on my smartphone, I was navigating through the app’s options while reading, and I crossed a pretty nice feature: Medium gives you the ability to resize the font size of the app to help you read better posts’ content.

This is a pretty common feature in many platforms, but I never thought of implementing it, not even for learning purposes.

So, to implement the functionality on this website, I came up with this custom hook that allows me to control a CSS variable. This way I can then use this variable in my text components and obtain a full font resize feature without rendering all the text components (more on this in the future, maybe with a new article 😁).

How does it work?

This custom hook comes with some features:

  • Set a CSS variable into the document element. The variable name can be customized.
  • Store the saved value in the localStorage, so that our users are not required to change the font size on every page reload.
  • The hook is isomorphic, which means it works also doing Server Side Rendering.

The hook accepts some optional parameters to customize its functioning, such as the localStorage key to store the font size, the CSS variable name and the initialSize in case of SSR.

The implementation

Here you go with the code implementation! I know it’s a long piece of code, but most of it is comments I left to clarify what it does, so let’s read the code and let’s check together what’s going on behind the scene! 🚀

jsx
import { useReducer } from 'react';
import { useLocalStorage } from '@mountain-ui/react-hooks';
import { hasWindow } from '@mountain-ui/utils';

export interface UseFontSizeProps {
storageKey?: string;
initialSize?: string;
variableName?: string;
}

/**
* Set the CSS variable in the <html> node
*/
const applyPropToDocument = (variableName: string, storedFontSize: string) => {
document.documentElement.style.setProperty(variableName, storedFontSize);
};

/**
* Retrieve the CSS variable value from the <html> node
*/
const getDocumentProp = (variableName: string) => {
document.documentElement.style.getPropertyValue(variableName);
};

/**
*
* @param {UseFontSizeProps} options
*/
export function useFontSize({
storageKey = 'f_s',
initialSize = '16px',
variableName = '--font-size'
}: UseFontSizeProps = {}) {
/**
* Create a state connected to the localStorage.
* This is necessary to consistently store
* the key/value pair with the font size
*/
const [storedFontSize, storeFontSize] = useLocalStorage(storageKey, initialSize);

/**
* The init function for the useReducer hook.
* This function runs on the first render when used and takes care of:
* - Check if the hook runs in the client, otherwise return the default value
* - If a font size is already stored in the localStorage, set it as a CSS variable
* - Return the stored value as the state value.
*/
function init(initialValue: string) {
if (!hasWindow()) return initialValue;
if (storedFontSize) applyPropToDocument(variableName, storedFontSize);
return storedFontSize || getDocumentProp(variableName);
}

/**
* The reducer function, that we'll use in our components
* to update the font size with user interactions.
* It set the new value for the CSS variable and saves it as the state.
*/
function reducer(_state: string, newFont: string | number) {
const newSize = `${newFont}px`;
applyPropToDocument(variableName, newSize);
storeFontSize(newSize);
return newFont;
}

/**
* Here is where the hook directly returns
* the useReducer return value, in the form of
* [fontSize, setFontSize]
*/
return useReducer(reducer, initialSize, init);
}

export default useFontSize;

function App() {
const [fontSize, setFontSize] = useFontSize({ variableName: '--font-size' });

return (
<div>
<p style={{ fontSize: 'var(--font-size)' }}>Hello world</p>
<button onClick={() => setFontSize(12)}>Set font size to 12px</p>
<button onClick={() => setFontSize(20)}>Set font size to 20px</p>
</div>
);
}

Now let’s see what’s happening step by step:

  • First of all, it imports utilities and hooks necessary for the implementation, you can find all of them in the mountain-ui repository.

  • The next step is to define an interface to describe the 3 accepted options: storageKey, initialSize and variableName. If you are not using typescript, you can skip this step.

  • It’s time to declare some utilities to set and retrieve the font-size variable from the <html> node inside the DOM. The two functions applyPropToDocument and getDocumentProp respectively do those operations.

  • We are finally ready to start declaring the custom hook! We’ll destructure from the options object the previously defined parameters, assigning default values in case their not passed when using the hook.

  • At the beginning of the implementation, the hook invokes another custom hook, the useLocalStorage utility, you can deeply read about it in my previous post about this hook.
    As you can read in the code comment, it creates a React state connected to the localStorage, so it can fulfill the requirements we’ve seen before.

  • Since we are going to use the useReducer hook for this implementation, it was necessary to define the init function that will take care of various detail once the hook is invoked the first time:

    • Check if the hook runs in the client, otherwise return the default value.
    • If a font size is already stored in the localStorage, set it as a CSS variable.
    • Return the stored value as the state value.
  • The last step before returning the useReducer result is to define the necessary logic to update the font-size value and store it in the localStorage and notify the component about the change. All this logic goes inside the reducer function.

How to use the useFontSize hook?

Attached at the end of the gist you’ll find a basic example of how to use the custom hook, but this should not limit your imagination on how to make the user interact with your page! On my website, I made it interactive through a slider!

Conclusion

We are at the end of this second React Hooks story, I hope it could be helpful and I’d like to hear from you for any doubt!

You can also find more about the resources I’m using on the following pages:

Thanks for taking the time to read it, see you with the next post! 🚀

Last updated: