Stalfos
Stalfos

Reputation: 1488

Change HOC wrapper component with its own state to react hooks

I currently have an HOC component that I'd like to port over to use react hooks and basically just start thinking in that idea.

This HOC component basically provides a functionality for a wrapped component to display an alert dialog box. The HOC component manages its own state making this very easy for the wrapped component to display an alert dialog. The wrapped component simply has to call the function passed down to props to display it.

Here's what the HOC looks like now:

function withAlertDialog(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super(props);

      this.state = {
        alertDialogOpen: false,
        alertMessage: ""
      };
    }

    displayAlert = message => {
      this.setState({alertDialogOpen: true, alertMessage: message});
    }

    closeAlertDialog = () => {
      this.setState({alertDialogOpen: false});
    }

    render() {
      return (
        <React.Fragment>
          <WrappedComponent 
            onDisplayAlert={this.displayAlert}
            onCloseAlert={this.closeAlertDialog} />
          <MyAlertDialogComponent
            open={this.state.alertDialogOpen}
            onClose={this.closeAlertDialog} />
        </React.Fragment>
      );
    }
  }
}

This is a more simple case, the actual HOC used is a lot more complex, but the idea still follows. The wrapped component can now basically call this.props.onDisplayAlert('some message here'); to display the alert. The wrapped component also doesn't have to render the MyAlertDialogComponent in its own render function. Basically, the wrapped component does not have to worry about how MyAlertDialogComponent is handled, all it knows is that calling this.props.onDisplayAlert will display an alert dialog box somehow. Reusing this HOC saves a lot of lines of code.

How would one go about changing this to a react hooks implementation? I've tried looking around but most articles and the documentation itself use an HOC with a single wrapped component and isn't really managing another component in addition to that. I'd like to understand how to change to the "react hooks" ideology but keep that same level of convenience about not having to render MyAlertDialogComponent in each component that wants to use it.

Upvotes: 1

Views: 1968

Answers (1)

Khauri
Khauri

Reputation: 3863

The only difference between your old HOC and a new HOC utilizing hooks is that you simply have to change the anonymous class you return from your HOC to an anonymous function that uses hooks.

Conversion between a class and a hooked-in function follows normal conversion rules that you might find in numerous tutorials online. In the case of your example, convert your state to useState and convert your class methods to regular functions.

You just pass the state and these regular functions around to whatever component needs them. Calling the setter for your state will re-render the component.

If you review the example below you'll see MyWrappedComponent is wrapped using withAlertDialog which passes the two function props to MyWrappedComponent. Those functions are used inside MyWrappedComponent to set the state that renders MyAlertDialogComponent

const { useState } = React
function withAlertDialog(WrappedComponent) {
  return function(props){
    const [alertState, setAlertState] = useState({
      alertDialogOpen: false,
      alertMessage: ""
    })

    const displayAlert = message => {
      setAlertState({
        alertDialogOpen: true, 
        alertMessage: message
      });
    }

    const closeAlertDialog = () => {
      setAlertState({alertDialogOpen: false});
    }
    
    return (
      <React.Fragment>
        <WrappedComponent 
          onDisplayAlert={displayAlert}
          onCloseAlert={closeAlertDialog} />
        <MyAlertDialogComponent
          open={alertState.alertDialogOpen}
          onClose={closeAlertDialog} />
      </React.Fragment>
    );
  }
}

const MyWrappedComponent = withAlertDialog(function (props){
  return (
    <div>
      <a onClick={props.onDisplayAlert}>Open Alert</a>
      <a onClick={props.onCloseAlert}>Close Alert</a>
    </div>
  )
})

function MyAlertDialogComponent(props){
  if(!props.open){
    return null
  }
  return (
    <div>Dialogue Open</div>
  )
}

function App(){
  return (
    <MyWrappedComponent />
  )
}

ReactDOM.render(<App />, document.querySelector('#app'))
div > a {
 display : block;
 padding : 10px 0;
}
<div id="app" />

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

Upvotes: 1

Related Questions