npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-unify

v1.0.9

Published

Unify state and props, decouple render() and update state synchronously

Downloads

15

Readme

react-unify 💍

Unify state and props, decouple render() and handle state synchronously

Simple Examples

With decorators and class field initialization

import * as React from "react";
import { Render, prop, state } from 'react-unify';

// decoupled stateless component render()
@Render(counter => (
  <div>
    <p>Count: {counter.count}</p>
    <button onClick={() => counter.increment()}>
      Increment by {counter.amount}
    </button>
  </div>
))
export class Counter extends React.Component {
  @prop amount = 1; // gets this.props.amount, sets defaultProps.amount
  @state count = 0; // access to this.state.count in a synchronous way

  increment() {
    this.count += this.amount;
    // this.count is updated synchronously
    // calls this.setState({count: this.state.count + this.props.amount})
    // (this.state.count will be updated asynchronously)
  }
}

Optionally extract stateless render

// CounterRender.jsx
export const CounterRender = counter => (
  <div>
    <p>Count: {counter.count}</p>
    <button onClick={() => counter.increment()}>
      Increment by {counter.amount}
    </button>
  </div>
);

Without decorators

import * as React from "react";
import { Render, prop, state } from 'react-unify';
import { CounterRender } from './CounterRender';

export class Counter extends React.Component {
  amount = 1;
  count = 0;

  increment() {
    this.count += this.amount;
  }
}

// manually applying decorators
Render(CounterRender)(Counter);
prop(Counter.prototype, 'amount');
state(Counter.prototype, 'count');

Testing

test('Counter render', () => {
  expect(CounterRender({ count: 0, amount: 1 })).toMatchSnapshot();
  // or use Counter.Render set by @Render
});

test('Counter instance', () => {
  const counter = new Counter({});
  counter.increment();
  expect(counter.count).toBe(1);
});

Live Demos

Installation

npm install react-unify

TypeScript

Enable decorators in tsconfig.json, class field initialization is enabled by default.

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

Babel

Enable decorators and class field initialization with plugins.

Unfortunately transform-decorators-legacy does not support replacing class properties with a getter/setter. You need to use my version of it or go without decorators for now.

npm install osi-oswald/babel-plugin-transform-decorators-legacy.git#v1.4.2
npm install babel-plugin-transform-class-properties

Add plugins to .babelrc file, NOTE: Order of Plugins Matters!

{
  "presets": ["env", "react"],
  "plugins": ["transform-decorators-legacy", "transform-class-properties"]
}

create-react-app

Unfortunately decorators are not supported at the moment. You would need to eject and follow the Babel instructions, or go without decorators for now.

Class field initialization is enabled by default.

API

@Render

Assigns this.render (and MyComponent.Render) to a stateless render function with the component instance as input.

@Render(MyComponentRender)
class MyComponent extends React.Component {
  /* ... */

  // set by @Render
  render() {
    return MyComponentRender(this);
  }

  // set by @Render
  static Render = MyComponentRender;
}

@state

Elevate this.state.someState to this.someState and access it synchronously. Will call this.setState() for you to update this.state.someState and trigger a rerender. Changes to this.state.someState from other sources (manual this.setState() / getDerivedStateFromProps() / mutating this.state) will be synchronized back to this.someState before shouldComponentUpdate() or on forceUpdate() respectively.

class MyComponent extends React.Component {
  @state myPrimitive = 'myInitialState';
  @state myObject = {name: 'Alice', age: 30};
  @state myArray = [0, 1, 2];

  updateMyPrimitive() {
    this.myPrimitive = 'newValue';
  }

  updateMyObject() {
    // update objects in an immutable way
    this.myObject = {...this.myObject, name: 'Bob'};

    // avoid mutating objects directly 
    // -> will not trigger setState() and therefore not rerender
    this.myObject.name = 'Bob';
  }

  updateMyArray() {
    // update arrays in an immutable way
    // see https://vincent.billey.me/pure-javascript-immutable-array/
    this.myArray = [...this.myArray, 3];

    // avoid mutating arrays directly 
    // -> will not trigger setState() and therefore not rerender
    this.myArray.push(3);
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    // return new state based on nextProps and prevState
    return { myState: nextProps.myProp };
  }

  // recommended: use React.PureComponent instead
  shouldComponentUpdate(nextProps, nextState) {
    // use this.myState (or nextState.myState) to get next state
    // use this.state.myState to get current state
    return this.myState !== this.state.myState;
  }

  // note: legacy lifecyle
  componentWillUpdate(nextProps, nextState) {
    // same as in shouldComponentUpdate()
  }

  componentDidUpdate(prevProps, prevState) {
    // use this.myState (or this.state.myState) to get current state
  }
}

@prop

Elevate this.props.someProp to this.someProp and optionally define its default value whenever someProp is undefined.

class MyComponent extends React.Component {
  @prop myProp;
  @prop myPropWithDefault = 'myDefaultValue';
  
  // note: legacy lifecyle
  componentWillReceiveProps(nextProps) {
    // use this.myProp (or this.props.myProp) to get current prop
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    // return new state based on nextProps and prevState
    return { myState: nextProps.myProp };
  }
  
  // recommended: use React.PureComponent instead
  shouldComponentUpdate(nextProps, nextState) {
    // use this.myProp (or this.props.myProp) to get current prop
    return this.myProp !== nextProps.myProp;
  }
  
  // note: legacy lifecyle
  componentWillUpdate(nextProps, nextState) {
    // use this.myProp (or this.props.myProp) to get current prop
  }
  
  componentDidUpdate(prevProps, prevState) {
    // use this.myProp (or this.props.myProp) to get current prop
  }
}

Caveat: When using @prop to set a default value, always use this.myProp. this.props.myProp will not receive the default value set by @prop until after the first render. (Set MyComponent.defaultProps.myProp directly if this matters for you.)

@child / @children

Specialized alternative to @prop children. Extract and name child(ren) from this.props.children, the result will be cached until next shouldComponentUpdate() with different nextProps.

class MyComponent extends React.Component {
  // gets React.Children.toArray(this.props.children)[0]
  @child mySingleChild;

  // gets React.Children.toArray(this.props.children)
  @children allMyChildren; 
  
  // gets React.Children.toArray(this.props.children).find(findChild)
  @child(findChild) mySpecialChild;

  // gets React.Children.toArray(this.props.children).filter(filterChildren)
  @children(filterChildren) mySpecialChildren; 
}