Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FP alternative to polymorphism in JavaScript/ReactJS

I'm currently working on a ReactJS project where I need to create "re-usable" components in which some of the methods would need to be "overridden". In OOP I would use polymorphism. I've done some reading and it seems the consensus is to use HoC/composition but I can't quite figure out how to achieve this. I figure if I could just get a ES6 sample using composition it would maybe be easier to adapt the idea to ReactJS afterwards.

Below is a ES6 OOP example (ignore the event handling it's just for testing) of pretty much what I would like to achieve in ReactJS. Does anyone have some guidance on how to break up a ReactJS component into a HoC or even just demonstrate how I would go about using composition in ES6 based on the example?

class TransferComponent {
    constructor(){
        let timeout = null;

        this.render();
        this.events();
    }

    events(){
        let scope = this;

        document.getElementById('button').addEventListener('click', function(){
            scope.validate.apply(scope);
        });
    }

    validate(){
        if(this.isValid()){
            this.ajax();
        }
    }

    isValid(){
        if(document.getElementById('username').value !== ''){
            return true;
        }

        return false;
    }

    ajax(){
        clearTimeout(this.timeout);

        document.getElementById('message').textContent = 'Loading...';

        this.timeout = setTimeout(function(){
            document.getElementById('message').textContent = 'Success';
        }, 500);
    }

    render(){
        document.getElementById('content').innerHTML = '<input type="text" id="username" value="username"/>\n\
            <button id="button" type="button">Validate</button>';
    }
}


class OverrideTransferComponent extends TransferComponent{
    isValid(){
        if(document.getElementById('username').value !== '' && document.getElementById('password').value !== ''){
            return true;
        }

        return false;
    }

    render(){
        document.getElementById('content').innerHTML = '<input type="text" id="username" value="username"/>\n\
            <input type="text" id="password" value="password"/>\n\
            <button id="button" type="button">Validate</button>';
    }
}


const overrideTransferComponent = new OverrideTransferComponent();
<div id="content"></div>
<div id="message"></div>

UPDATE: Even though my original question was around FP I think render props is a really good solution to my issue and avoids the HoC issues.

like image 491
Wancieho Avatar asked Nov 11 '17 00:11

Wancieho


3 Answers

EDIT:

This is post is out of date. Hooks are better for most use cases.

Original Answer:

The answer regarding your example code is in the middle/bottom of this post.

A good way to go about React composition is the render-callback pattern, aka function-as-child. The main advantage it has over HOC's is that it allows you to compose components dynamically at runtime (e.g. in render) instead of statically at author time.

Regardless of whether you use render-callback or HOC's, the goal in component composition is to delegate reusable behavior to other components, and then pass those components in as props to the component that needs them.

Abstract example:

The following Delegator component uses the render-callback pattern to delegate implementation logic to an ImplementationComponent that is passed in as a prop:

const App = () => <Delegator ImplementationComponent={ImplementationB} />;

class Delegator extends React.Component {
  render() {
    const { ImplementationComponent } = this.props;

    return (
      <div>
        <ImplementationComponent>
          { ({ doLogic }) => {
            /* ... do/render things based on doLogic ... */
          } }
        </ImplementationComponent>
      </div>
    );
  }
}

The various implementation components would look like:

class ImplementationA extends React.Component {

  doSomeLogic() { /* ... variation A ... */ }

  render() {
    this.props.children({ doLogic: this.doSomeLogic })
  }
}

class ImplementationB extends React.Component {

  doSomeLogic() { /* ... variation B ... */ }

  render() {
    this.props.children({ doLogic: this.doSomeLogic })
  }
} 

Later down the line, you could nest more child components in the Delegator component following the same compositional pattern:

class Delegator extends React.Component {
  render() {
    const { ImplementationComponent, AnotherImplementation, SomethingElse } = this.props;

    return (
      <div>
        <ImplementationComponent>
          { ({ doLogic }) => { /* ... */} }
        </ImplementationComponent>
        
        <AnotherImplementation>
          { ({ doThings, moreThings }) => { /* ... */} }
        </AnotherImplementation>
        
        <SomethingElse>
          { ({ foo, bar }) => { /* ... */} }
        </SomethingElse>
      </div>
    );
  }
}

Now nested child component allows for multiple concrete implementations:

const App = () => (
  <div>
    <Delegator 
      ImplementationComponent={ImplementationB}
      AnotherImplementation={AnotherImplementation1}
      SomethingElse={SomethingVariationY}
    />

    <Delegator 
      ImplementationComponent={ImplementationC}
      AnotherImplementation={AnotherImplementation2}
      SomethingElse={SomethingVariationZ}
    />
  </div>
); 

Answer (your example):

Applying the above composition pattern to your example, the solution restructures your code but assumes that it needs to do the following:

  • allow variations of inputs and their validation logic
  • when user submits valid input, then do some ajax

Firstly, to make things easier I changed the DOM to:

<div id="content-inputs"></div>
<div id="content-button"></div> 

Now, the TransferComponent knows only how to display a button and do something when the button is pressed and the data is valid. It doesn't know what inputs to display or how to validate the data. It delegates that logic to the nested VaryingComponent.

export default class TransferComponent extends React.Component {
  constructor() {
    super();
    this.displayDOMButton = this.displayDOMButton.bind(this);
    this.onButtonPress = this.onButtonPress.bind(this);
  }

  ajax(){
    console.log('doing some ajax')
  }

  onButtonPress({ isValid }) {
    if (isValid()) {
      this.ajax();
    }
  }

  displayDOMButton({ isValid }) {
    document.getElementById('content-button').innerHTML = (
      '<button id="button" type="button">Validate</button>'
    );

    document.getElementById('button')
      .addEventListener('click', () => this.onButtonPress({ isValid }));
  }

  render() {
    const { VaryingComponent } = this.props;
    const { displayDOMButton } = this;

    return (
      <div>
        <VaryingComponent>
          {({ isValid, displayDOMInputs }) => {
            displayDOMInputs();
            displayDOMButton({ isValid });
            return null;
          }}
        </VaryingComponent>
      </div>
    )
  }
};

Now we create concrete implementations of the VaryingComponent to flesh out various input display and validation logics.

The username-only implementation:

export default class UsernameComponent extends React.Component {
  isValid(){
    return document.getElementById('username').value !== '';
  }

  displayDOMInputs() {
    document.getElementById('content-inputs').innerHTML = (
      '<input type="text" id="username" value="username"/>'
    );
  }

  render() {
    const { isValid, displayDOMInputs } = this;

    return this.props.children({ isValid, displayDOMInputs });
  }
}

The username-and-password implementation:

export default class UsernamePasswordComponent extends React.Component {
  isValid(){
    return (
      document.getElementById('username').value !== '' &&
      document.getElementById('password').value !== ''
    );
  }

  displayDOMInputs() {
    document.getElementById('content-inputs').innerHTML = (
      '<input type="text" id="username" value="username"/>\n\
      <input type="text" id="password" value="password"/>\n'
    );
  }

  render() {
    const { isValid, displayDOMInputs } = this;

    return this.props.children({ isValid, displayDOMInputs });
  }
}

Finally, composing instances of the TansferComponent would look like:

<TransferComponent VaryingComponent={UsernameComponent} />
<TransferComponent VaryingComponent={UsernamePasswordComponent} />
like image 159
brietsparks Avatar answered Oct 23 '22 10:10

brietsparks


The non-React FP example

For starters, in Functional Programming, the function is a first class citizen. This means you can treat functions as you would data in OOP (i.e. pass as parameters, assign to variables, etc.).

Your example comingles data with behavior in the objects. In order to write a purely functional solution, we'll want to separate these.

Functional Programming is fundamentally about separating data from behavior.

So, let's start with isValid.

Functional isValid

There are a few ways to order the logic here, but we'll go with this:

  1. Given a list of ids
  2. All ids are valid if there does not exist an invalid id

Which, in JS, translates to:

const areAllElementsValid = (...ids) => !ids.some(isElementInvalid)

We need a couple helper functions to make this work:

const isElementInvalid = (id) => getValueByElementId(id) === ''
const getValueByElementId = (id) => document.getElementById(id).value

We could write all of that on one line, but breaking it up makes it a bit more readable. With that, we now have a generic function that we can use to determine isValid for our components!

areAllElementsValid('username') // TransferComponent.isValid
areAllElementsValid('username', 'password') // TransferOverrideComponent.isValid

Functional render

I cheated a little on isValid by using document. In true functional programming, functions should be pure. Or, in other words, the result of a function call must only be determined from its inputs (a.k.a. it is idempotent) and it cannot have side effects.

So, how do we render to the DOM without side effects? Well, React uses a virtual DOM (a fancy data structure that lives in memory and is passed into and returned from functions to maintain functional purity) for the core library. React's side effects live in the react-dom library.

For our case, we'll use a super simple virtual DOM (of type string).

const USERNAME_INPUT = '<input type="text" id="username" value="username"/>'
const PASSWORD_INPUT = '<input type="text" id="password" value="password"/>'
const VALIDATE_BUTTON = '<button id="button" type="button">Validate</button>'

These are our components--to use the React terminology--which we can compose into UIs:

USERNAME_INPUT + VALIDATE_BUTTON // TransferComponent.render
USERNAME_INPUT + PASSWORD_INPUT + VALIDATE_BUTTON // TransferOverrideComponent.render

This probably seems like an oversimplification and not functional at all. But the + operator is in fact functional! Think about it:

  • it takes two inputs (the left operand and the right operand)
  • it returns a result (for strings, the concatenation of the operands)
  • it has no side effects
  • it doesn't mutate its inputs (the result is a new string--the operands are unchanged)

So, with that, render is now functional!

What about ajax?

Unfortunately, we can't perform an ajax call, mutate the DOM, set up event listeners, or set timeouts without side effects. We could go the complex route of creating monads for these actions, but for our purposes, suffice it to say that we'll just keep using the non-functional methods.

Applying it in React

Here's a rewrite of your example using common React patterns. I'm using controlled components for the form inputs. The majority of the functional concepts we've talked about really live under the hood in React, so this is a pretty simple implementation that doesn't use anything fancy.

class Form extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            loading: false,
            success: false
        };
    }

    handleSubmit() {
        if (this.props.isValid()) {
            this.setState({
                loading: true
            });

            setTimeout(
                () => this.setState({
                    loading: false,
                    success: true
                }),
                500
            );
        }
    }

    render() {
        return (
            <div>
                <form onSubmit={this.handleSubmit}>
                    {this.props.children}
                    <input type="submit" value="Submit" />
                </form>

                { this.state.loading && 'Loading...' }
                { this.state.success && 'Success' }
            </div>
        );
    }
}

The use of state probably seems like a side effect, doesn't it? In some ways it is, but digging into the React internals may reveal a more functional implementation than can be seen from our single component.

Here's the Form for your example. Note that we could handle submission in a couple different ways here. One way is to pass the username and password as props into Form (probably as a generic data prop). Another option is to pass a handleSubmit callback specific to that form (just like we're doing for validate).

class LoginForm extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            username: '',
            password: ''
        };
    }

    isValid() {
        return this.state.username !== '' && this.state.password !== '';
    }

    handleUsernameChange(event) {
        this.setState({ username: event.target.value });
    }

    handlePasswordChange(event) {
        this.setState({ password: event.target.value });
    }

    render() {
        return (
            <Form
                validate={this.isValid}
            >
                <input value={this.state.username} onChange={this.handleUsernameChange} />
                <input value={this.state.password} onChange={this.handlePasswordChange} />
            </Form>
        );
    }
}

You could also write another Form but with different inputs

class CommentForm extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            comment: ''
        };
    }

    isValid() {
        return this.state.comment !== '';
    }

    handleCommentChange(event) {
        this.setState({ comment: event.target.value });
    }

    render() {
        return (
            <Form
                validate={this.isValid}
            >
                <input value={this.state.comment} onChange={this.handleCommentChange} />
            </Form>
        );
    }
}

For sake of example, your app can render both Form implementations:

class App extends React.Component {
    render() {
        return (
            <div>
                <LoginForm />
                <CommentForm />
            </div>
        );
    }
}

Finally, we use ReactDOM instead of innerHTML

ReactDOM.render(
    <App />,
    document.getElementById('content')
);

The functional nature of React is often hidden by using JSX. I encourage you to read up on how what we're doing is really just a bunch of functions composed together. The official docs cover this pretty well.

For further reading, James K. Nelson has put together some stellar resources on React that should help with your functional understanding: https://reactarmory.com/guides/learn-react-by-itself/react-basics

like image 27
Luke Willis Avatar answered Oct 23 '22 09:10

Luke Willis


Reading your question, it's not clear if you are referring to composition or inheritance, but they are different OOP concepts. I recommend you to take a look at this article if you don't know the difference between them.

About your specific problem with React. I'd recommend you to try to user composition since it gives you a lot of flexibility to build your UI and pass props around.

If you are working with React you are probably already using composition when you populate Dialogs dynamically, for example. As the React docs shows:

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

Folks at Facebook have been developing very challenging UIs and building thousand of components with React and didn't find a good use case for inheritance over composition. As the docs says:

React has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.

If you really want to use inheritance, their recommendation is that you extract the functionality that you want to reuse over components into a separate JavaScript module. The components may import it and use that function, object, or a class, without extending it.

In the example you provided, two functions in a utils.js would do just fine. See:

isUsernameValid = (username) => username !== '';

isPasswordValid = (password) => password !== '';

You can import them and use in your components just fine.

like image 1
Tiago Alves Avatar answered Oct 23 '22 10:10

Tiago Alves