DEV Community

Abhirup Datta
Abhirup Datta

Posted on

Understanding HOCs in React (in Typescript)

HOC stands for Higher-Order Components.
It means a component which takes in another component and returns an enhanced component.

const HoC = Component => EnhancedComponent

The primary use case of HOC is to have a single place to sharing functionalities between components.
(Think of HOC more like a function, than component, this will be more clear as we go.)

Suppose, you have a requirement to calculate the innerWidth of browser viewport as it is resized and then do some computations/ui change.

For our example, we just want to display the window.innerWidth in "realtime".
Demo
To achieve this we can write a functional component and use the useEffect hook and attach a listener function which will update a state variable.
See code: https://github.com/abhidatta0/react-hoc-in-typescript/blob/master/src/ResizeComponent.tsx

Now, suppose we have to use this logic in another component.In that case, we have to duplicate our logic 😞 (which is obviously bad).

What could be a better(or best) way?
We can just abstract away this useEffect and listener code into another component and expose the innerWidth variable as a prop.
This "special" component is our Higher-Order Component.

withResize.tsx

const withResize = (Component: FC<any> )=> (props: any)=> {
    const  [innerWidth, setInnerWidth] = useState(0);

    const handleResize = ()=>{
     setInnerWidth(window.innerWidth);
    }

    useEffect(()=>{
     window.addEventListener('resize', handleResize);

     return ()=>{
       window.removeEventListener('resize', handleResize);
     }
    },[]); 

    return <Component {...props} windowInnerWidth={innerWidth} />
}

Enter fullscreen mode Exit fullscreen mode

WithResizeUsage.tsx

const WithResizeUsage = ({name, windowInnerWidth}: {name: string, windowInnerWidth: number})=>{
    return (
        <div>Inner Width is {windowInnerWidth} and name is {name}</div>
    )
}

export default withResize(WithResizeUsage);
Enter fullscreen mode Exit fullscreen mode

App.tsx

  <WithResizeUsage name="Developer" />
Enter fullscreen mode Exit fullscreen mode

Let's analyze what we have written

  • In App.tsx, we call WithResizeUsage with a name prop.
  • withResize is an hoc, which takes in a Component which itself accepts some props.
  • The return of withResize is a component with all its original props and the extra windowInnerWidth prop.
  • In WithResizeUsage: we accept the name prop that we get from App.tsx and the windowInnerWidth that we get from withResize hoc.
  • In WithResizeUsage, we wrap it with withResize in the last line withResize(WithResizeUsage) to bind it with withResize.

See code:
https://github.com/abhidatta0/react-hoc-in-typescript/blob/master/src/withResize.tsx

https://github.com/abhidatta0/react-hoc-in-typescript/blob/master/src/WithResizeUsage.tsx

Notice how clean our WithResizeUsage component becomes.

Let's look at how we can enhance the HOC a bit more.
Suppose, we need that that the displayed window.innerWidth is x amount more and x value will be configurable.If inner width is 90 and x is 3, we will display 93.

We will modify the HOC by adding another level to accept additional params.(This concept is called function currying)

withResizeAdvanced.tsx

type Params =  {
  bumped: number;
}
const withResizeAdvanced = (params: Params)=> (Component: FC<any> )=> (props: any)=> {
    console.log(params);
    const  [innerWidth, setInnerWidth] = useState(0);

    const handleResize = ()=>{
     setInnerWidth(window.innerWidth+params.bumped);
    }

    useEffect(()=>{
     window.addEventListener('resize', handleResize);

     return ()=>{
       window.removeEventListener('resize', handleResize);
     }
    },[]); 

    return <Component {...props} windowInnerWidth={innerWidth} />
}
Enter fullscreen mode Exit fullscreen mode

withResizeAdvancedUsage.tsx

import withResizeAdvanced from "./withResizeAdvanced";

const WithResizeUsage = ({name, windowInnerWidth}: {name: string, windowInnerWidth: number})=>{
    return (
        <div>Inner Width is {windowInnerWidth} and name is {name}</div>
    )
}

export default withResizeAdvanced({bumped: 5})(WithResizeUsage);
Enter fullscreen mode Exit fullscreen mode

App.tsx

      <WithResizeAdvancedUsage name="Developer"/>
Enter fullscreen mode Exit fullscreen mode

Everything is similar to earlier HOC, except we can pass a object which has a property bumped can we use to modify the innerWidth.

If we put earlier and the new component in App.tsx, we can visualize that for the later one, innerWidth is incremented by 5.
HOC bumped

Github repo: https://github.com/abhidatta0/react-hoc-in-typescript

Note:

  • Make sure HOC have the general logic (like getting window.innerWidth) will be reused across other components.
  • An alternative to HOC pattern for function component is to use custom hooks.

Top comments (0)