Quantcast
Channel: Krzysztof Żuraw blog's RSS Feed
Viewing all articles
Browse latest Browse all 205

On React Render props vs HOC

$
0
0

Recently one of my colleague at work stumbled upon strange case when using one of the popular react pattern called hoc (high order component). It turns out that sometimes your hoc can override your passed props. Let me explain it a little bit more.

Imagine that you have a Button component:

import*as React from'react';interfaceOwnProps{onClick:()=>void;
  amount:number;
  name:string;}const Button: React.FunctionComponent<OwnProps> = props => (
  <buttononClick={props.onClick}>{props.name}{props.amount}</button>
);

It’s is a small component that takes 3 props. One onClick to handle button clicks and the rest two are just for displaying data on the button.

How you can pass props to this button? Let’s agree that you need a higher level of abstraction 😂 and you pass them via HOC:

import*as React from'react';exportinterfaceHOCProps{onClick:()=>void;}exportconst withHOC =<WrappedComponentPropsextendsobject>(
  WrappedComponent: React.ComponentType<WrappedComponentProps>
) =>{// tslint:disable-next-line:no-consoleconstonClick=()=>console.log('Clicked! from HOC');constHOC: React.FunctionComponent<WrappedComponentProps & HOCProps>=props=>{return<WrappedComponent{...props}onClick={onClick}/>;};HOC.displayName =`withHOC(${WrappedComponent})`;returnHOC;};

so this can be used like this:

import*as React from'react';import{ HOCProps, withHOC }from'../hoc';import{ Button }from'./button';interfaceOwnProps{onClick:()=>void;
  amount:number;
  name:string;}const ButtonHOC =withHOC(Button);// usage<ButtonHOConClick={()=>setAmount(amount +1)}amount={amount}name="HOC button"/>;

You clap yourself on the back for such a good work - 👏🏻. You abstract away onClick.

The code looks fine but it turns out that clicking on button results in console.log! But you wanted it to increment amount by one. What is happening?

Your HOC is overriding your component props. To avoid that you will need to change the prop name - so the clashing won’t occur anymore.

Let’s look for another pattern that is common in react world - render props:

import*as React from'react';exportinterfaceOwnProps{render:({ onClick }:{onClick:()=>void})=>JSX.Element;}exportconst RenderPropsButton: React.FunctionComponent<OwnProps> = props =>{// tslint:disable-next-line:no-consoleconstonClick=()=>console.log('Clicked from renderProps');return props.render({ onClick });};

it can be used like this:

<RenderPropsButtonrender={renderProps=>(<ButtononClick={renderProps.onClick}amount={amount}name="RenderProps button"/>)}/>

Will it help? Yes - as we are passing onClick via renderProp instead of being directly injected into the component. This allows the developer to see from where this prop is coming and fix it.

Summary

When render props first came to react world I did not see a clear benefit over hoc. After seeing a similar case like one above - I find them better in terms of readability than hoc.

PS. I know that those examples are overcomplicated. There are here to prove the point.

TLDR

With render props you can avoid accidental prop override.


Viewing all articles
Browse latest Browse all 205

Trending Articles