Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind to and use a higher-order component in ReasonReact

Let's say I have a higher-order component, something like the following trivial definition, exported from the JavaScript module ./hoc.js:

export const withStrong =
  Component => props =>
    <strong> <Component ...props/> </strong>

Assuming I have some component called HelloMessage, what is the equivalent of this piece of JavaScript:

import { withStrong } from './hoc.js';

const HelloMessage = ...

const StrongMessage = withStrong(HelloMessage);

ReactDOM.render(
  <StrongMessage name="Joe" />,
  document.getElementById('react-app')
);
like image 951
glennsl Avatar asked Aug 24 '19 20:08

glennsl


People also ask

Can we use higher-order component in functional component?

Higher-Order Components enable us to apply functional programming principles on components by embracing composition. React Hooks, in contrast, transformed pure (in the sense of functional programming) function components to stateful/side-effect burdened beasts. Anyway, both have their right to exist.

What are higher-order components hoc and how you can use them in practice?

A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React's compositional nature. Concretely, a higher-order component is a function that takes a component and returns a new component.

In which of the following cases should you use higher-order components?

We use higher order components to primarily reuse logic in React apps. However, they have to render some UI. Hence, HOCs are inconvenient when you want to share some non-visual logic. In such a case, React hooks seem to be a perfect mechanism for code reuse.

What is the correct syntax for higher-order components?

Higher-order components or HOC is the advanced method of reusing the component functionality logic. It simply takes the original component and returns the enhanced component. Syntax: const EnhancedComponent = higherOrderComponent(OriginalComponent);


1 Answers

TL;DR:

This should be the exact equivalent of the requested JavaScript snippet:

[@bs.module ./hoc.js]
external withStrong
  : React.component('props) => React.component('props)
  = "withStrong";

module HelloMessage = ...

module StrongMessage = {
  include HelloMessage;
  let make = withStrong(make);
};

ReactDOMRe.renderToElementWithId(
  <StrongMessage name="Joe" />,
  "react-app"
);

There'a also a runnable example on the Reason playground with a few adaptations made to work around not having a separate JavaScript file.

Explanation follows:

Binding

withStrong is just a function. It happens to be a function that accepts and returns a react component, which is a bit mysterious, but they're really just values like any other. We can just bind it like an ordinary function.

Even something as simple as this would work

[@bs.module ./hoc.js]
external withStrong : 'a => 'a = "withStrong";

assuming you always make sure to pass in a component. But it wouldn't be particularly safe as you can pass it anything else too, so let's try to use the type system as it should be used, restricting it to only accept react components.

The ReasonReact source code says components have the type component('props), so that's what we'll use.

[@bs.module ./hoc.js]
external withStrong
  : React.component('props) => React.component('props)
  = "withStrong";

Using the 'props type variable in both the argument and return type means we constrain them to be the same. That is, the returned component will have exactly the same props as the one passed in, which is exactly what we want in this case.

And that's really all there is to the binding itself. we can now use it like this:

let strongMessage = withStrong(HelloMessage.make);

Unfortunately this doesn't support JSX. To render strongMessage as is we'd have to write something like

React.createElementVariadic(strongMessage, { "name": "Joe" }, [||]);

Not great. So let's fix that.

JSX

<StrongMessage name="Joe" />

transforms to

React.createElementVariadic(
  StrongMessage.make,
  StrongMessage.makeProps(~name="Joe", ()),
  [||]
);

So we need a StrongMessage module with two functions, make and makeProps that conform to what's expected by React.createElementVariadic. make is just the component itself, so that's simple enough. makeProps is a function that acccepts the props as labeled arguments terminated by unit (since the props may be optional) and returns a js object. This also happens to be exactly what [@bs.obj] does, which isn't in any way coincidental.

Putting this together then, we get:

module StrongMessage = {
  let make = withStrong(HelloMessage.make);

  [@bs.obj]
  external makeProps
    : (~name: string, unit) => {. "name" string }
    = "";
}

And that's it! Yay!

Addendum: Shortcuts

Ok, so the makeProps function is a bit of an annoying mouthful. Fortunately in our case, where the props of the wrapped component is the same as the original, it's also unnecessary since StrongMessage.makeProps will be identical to HelloMessage.makeProps. Let's just steal that then! And now we have

module StrongMessage = {
  let make = withStrong(HelloMessage.make);
  let makeProps = HelloMessage.makeProps;
}

But we can do even better! By using include HelloMessage we can drop makeProps entirely (thanks to @bloodyowl, via @idkjs, for this one).

module StrongMessage = {
  include HelloMessage;
  let make = withStrong(make);
}

That's pretty nice, isn't it? This works because include HelloMessage will include all the exported definitions from HelloMessage such as makeProps, but also make and anything else. This is probably what you want when you wrap a component in this way, but beware that it imports and re-exports everything from the included module, in case that's not what you want.

Usage

Finally, once we have both the binding and JSX in order we can use it like this

ReactDOMRe.renderToElementWithId(
  <StrongMessage name="Joe" />,
  "react-app"
);
like image 156
glennsl Avatar answered Sep 24 '22 20:09

glennsl