Skip to content

Instantly share code, notes, and snippets.

@jquense
Last active September 24, 2022 05:10
Show Gist options
  • Save jquense/47bbd2613e0b03d7e51c to your computer and use it in GitHub Desktop.
Save jquense/47bbd2613e0b03d7e51c to your computer and use it in GitHub Desktop.
Alternative ways to define react Components

The 0.13.0 improvements to React Components are often framed as "es6 classes" but being able to use the new class syntax isn't really the big change. The main thing of note in 0.13 is that React Components are no longer special objects that need to be created using a specific method (createClass()). One of the benefits of this change is that you can use the es6 class syntax, but also tons of other patterns work as well!

Below are a few examples creating React components that all work as expected using a bunch of JS object creation patterns (https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/ch4.md#mixins). All of the examples are of stateful components, and so need to delegate to React.Component for setState(), but if you have stateless components each patterns tends to get even simpler. The one major caveat with react components is that you need to assign props and context to the component instance otherwise the component will be static. The reason is that when reconciling updates to props, new instances aren't created props is jsut set to the next props.

Why might you use these patterns? Some are just nicely terse, or maybe you just like the library you are already using for object creation, but generally these patterns offer a lot more in terms of flexibility and functionality. For instance, since most examples are created in a closure you get private variables for free!

function PlainOldObjectComponent(props, context){
var instance = Object.create(React.Component.prototype)
instance.props = props
instance.context = context
instance.state = { message: 'Object literals ftw' }
instance.render = function() {
return <li>
<button onClick={ e => this.setState({ message: 'stateful!' })}>
{this.state.message}
</button>
</li>
}
return instance
}
function MixinComponent(props, context) {
return {
...React.Component.prototype,
props,
context,
state: {
message: 'Instances through Extension'
},
render() {
return <li>
<button onClick={ e => this.setState({ message: 'stateful!' })}>
{this.state.message}
</button>
</li>
}
}
}
function ParasiticComponent(props, context){
var instance = new React.Component(props, context)
instance.state = { message: 'Let me just add a method to this one..' }
instance.render = function() {
return <li>
<button href='#' onClick={()=> this.setState({ message: 'stateful!' })}>
{this.state.message}
</button>
</li>
}
return instance
}
// Stampit is an excellent library by Eric Elliott for object creation.
// https://github.com/ericelliott/stampit
var ReactStamp = stampit.convertConstructor(React.Component)
// ^^^ convenience method for:
// stampit().methods(React.Component.prototype).enclose(React.Component)
var StampitComponent = stampit()
.enclose(function () {
this.state = { message: 'I can even use stampit!' }
})
.methods({
render() {
return <li>
<button href='#' onClick={()=> this.setState({ message: 'stateful!' })}>
{this.state.message}
</button>
</li>
}
});
StampitComponent = stampit.compose(ReactStamp, StampitComponent);
@Cmdv
Copy link

Cmdv commented May 24, 2015

how would you go about converting this use of class? I see it in so many places but not grasping how replace class with your implementation say mixinComponents

class Root extends React.Component {
  constructor() {
    super();
    this.handleIncrement = function () { console.log('clicked'); }
  }
  render() { 
    return (
      <div>
        <h1>Hello</h1>
        <button onClick={this.handleIncrement}>click me</button>
      </div>
    );
  }
}

module.exports = Root;

@svagi
Copy link

svagi commented May 29, 2015

@Cmdv @ericelliott I suppose you can do it like this

export default function Root(initialProps) {

  function handleIncrement() {
    console.log('clicked');
  }

  return {
    __proto__: React.Component.prototype,
    props: initialProps,
    render() { 
      return (
        <div>
          <h1>Hello</h1>
          <button onClick={handleIncrement}>click me</button>
        </div>
      );
  }
}

You need to inherit from React.Component.prototype.

Question is, what is the best approach for inheritance in object literal.

@ericelliott
Copy link

@svagi I just add propTypes to the function, e.g.:

const helloFactory = function ({ React }) {
  const {
    string,
    func
  } = React.PropTypes;

  return function Hello (props) {

    // React wants propTypes
    // to be static.
    Hello.propTypes = {
      word: string,
      mode: string,

      actions: React.PropTypes.shape({
        setWord: func.isRequired,
        setMode: func.isRequired
      })
    };

    return {

      props, // set props


      componentDidUpdate () {
        this.refs.wordInput.getDOMNode().focus();
      },

      render () {
        const {
          word,
          mode
        } = this.props;

        const {
          setMode,
          setWord
        } = this.props.actions;

        const styles = {
          displayMode: {
            display: (mode === 'display') ? 'inline' : 'none'
          },

          editMode: {
            display: (mode === 'edit') ? 'inline' : 'none'
          }
        };

        const onKeyUp = function (e) {
          if (e.key !== 'Enter') return;

          setWord(e.target.value);
          setMode('display');
        };

        return (
          <p>Hello,&nbsp;
            <span
              style = { styles.displayMode }
              onClick = { () => setMode('edit') }
              >{ word }!</span>
            <input
              ref = "wordInput"
              style = { styles.editMode }
              placeholder = { word }
              onKeyUp = { onKeyUp } />
          </p>
        );
      }
    };

  };

};

export default helloFactory;

@ericelliott
Copy link

@svagi

You need to inherit from React.Component.prototype

AFAIK, this is only needed for .setState() and .forceUpdate(), both of which should be avoided if possible.

So far I have had no trouble avoiding the component prototype, but as other examples here have shown, it's trivial to use it.

@Cmdv - I didn't lint or test this, so beware of typos:

const createRoot ({ React }) { // I usually do this so I can steal PropTypes from it.
  return function Root () { // This component doesn't use props. No need to set them.
    return {
      increment () {
        console.log('clicked');
      },
      render() { 
        return (
          <div>
            <h1>Hello</h1>
            <button onClick={ this.increment }>click me</button>
          </div>
        );
      }
    };
  };
};

module.exports = createRoot;

@ericelliott
Copy link

For more details on class-free components, check out "Baby's First Reaction: A 'Hello, World' Example for React".

@troutowicz
Copy link

@Cmdv @svagi

You may be insterested in react-stampit. It's a specialized stampit factory for React, kind of similar to React.creatClass.

@voronianski
Copy link

I've noticed a strange behavior when using these patterns, for example with parasitic component (see a render method):

import React from 'react';

function createComponent (component) {
    return function (props, context) {
        let instance = new React.Component(props, context);

        instance.state = component.state;
        instance.render = component.render;

        return instance;
    };
}

let App = createComponent({
    state: {
        count: 0
    },

    render() {
        let { state, setState } = this;
        return <div onClick={() => {
            // setState({count: ++state.count});
            // this will throw exception: 
            // "Uncaught TypeError: Cannot read property '_reactInternalInstance' of undefined at ReactInstanceMap.js:34" 

            this.setState({count: ++state.count}); // this line works fine
        }}>Counter: {state.count}!</div>;
    }
});

React.render(
    <App />,
    document.getElementById('app')
);

As you see onClick calling setState reference throws an exception in React, though using this.setState works fine..

Ideally what I'm looking for is to patch render with arguments: render({ props, state }, setState) inside my createComponent function in order to avoid annoying this in methods.

@ericelliott
Copy link

You need react-stampit

@emekaokoli
Copy link

emekaokoli commented Dec 21, 2016

@ericelliott why cant i do this:
PS: im new to JS/React

`import React from 'react';

function AwesomeComponent(props){
AwesomeComponent = Object.assign(Object.create(React.Component.prototype))
AwesomeComponent.props = props
AwesomeComponent.state = {likesCount : 0};
this.onLike = this.onLike.bind(this); // LINE 7

onLike ()
let newLikesCount = this.state.likesCount + 1;
this.setState({likesCount: newLikesCount});

AwesomeComponent.render = function() {
return (


Likes : {this.state.likesCount}
Like Me


);
}

}

export default AwesomeComponent;`

this error probably starts at line 7.

Uncaught TypeError: Cannot read property 'onLike' of undefined
at AwesomeComponent (AwesomeComponent.jsx:7)
at ReactCompositeComponent.js:306
at measureLifeCyclePerf (ReactCompositeComponent.js:75)
at ReactCompositeComponentWrapper._constructComponentWithoutOwner (ReactCompositeComponent.js:305)
at ReactCompositeComponentWrapper._constructComponent (ReactCompositeComponent.js:280)
at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:188)
at Object.mountComponent (ReactReconciler.js:46)
at ReactDOMComponent.mountChildren (ReactMultiChild.js:238)
at ReactDOMComponent._createInitialChildren (ReactDOMComponent.js:691)
at ReactDOMComponent.mountComponent (ReactDOMComponent.js:516)

@xgqfrms-GitHub
Copy link

good job!

Alternative ways to define react Components

Copy link

ghost commented Sep 24, 2022

great idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment