Permalink
Fetching contributors…
Cannot retrieve contributors at this time
269 lines (195 sloc) 8.76 KB

ylem

State Management for React: Effortless, Expandable, Adaptable

Build Status Greenkeeper Badge

ylem provides scalable and easy to understand state management for your React application. It works by enhancing Dumb (presentational) components into Smart (container) components by "connecting" them to observable objects and arrays.

If you are familiar with Redux, ylem will feel very familiar.

When your state updates, your components automatically re-render, allowing you to separate your UI from your Business Logic in a way that is familiar, easy to test, and just makes sense.

import React, { Component } from "react";
import ReactDOM from "react-dom";
import { connect, ObserveObject } from "ylem";

class Store extends ObserveObject {
  count = 0

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

  decrement = () => {
    this.count--;
  }
}

class MyComponent extends Component {
    render() {
      const { count, increment, decrement } = this.props;

      return (
          <div>
            <button onClick={decrement}>-</button>
            <input readOnly value={count} />
            <button onClick={increment}>+</button>
          </div>
      )
    }
}

const App =  connect(Store)(MyComponent);

ReactDOM.render(<App />, document.getElementById("root"));

Learn how to architect your React app with ylem for concise and powerful state management without the struggle.

Getting Started

This small getting started guide is just to get you familiar with the concepts around ylem. For more specific information checkout out API docs and for more information and different ways to use ylem checkout out the Advanced Topics.

Step 1

Start by setting up a new app with create-react-app

If you need information for how to set up a new project with create-react-app we've provided a quick guide:

Alternatively, if you want to start a new project using StealJS here is a guide on how to configure Steal for a ylem project:

Step 2

Install ylem and modify App.js

npm install ylem

Once ylem is installed, modify src/App.js to match the example below (copy and paste). Feel free to look over what is going on here, but we'll take each part in turn.

import "./App.css";
import React from "react";
import ylem, { ObserveObject } from "ylem";

var store = new ObserveObject({
  count: 0,
  increment() {
    this.count++;
  }
});

export default ylem(() => (
  <div>
    <input readOnly value={store.count} />
    <button onClick={store.increment}>+</button>
  </div>
));

Step 3

Understanding ylems Observable Objects

ylem exports some classes ObserveObject and ObserveArray. From these classes you can construct observable instances, which will inform ylems "observers" whenever a property is changed or mutated on the instance. This is the basis for our reactive stores.

var store = new ObserveObject({
  count: 0,
  increment() {
    this.count++;
  }
});

export default ylem(() => (
  <div>
    <input readOnly value={store.count} />
    <button onClick={store.increment}>+</button>
  </div>
));

You can extend ObserveObject to create your own class of observable instances. This way you can create objects that represent the state of your component along with methods to mutate that state.

Change your App.js code to something like this:

class Store extends ObserveObject {
  count = 0

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

const store = new Store();

export default ylem(() => (
  <div>
    <input readOnly value={store.count} />
    <button onClick={store.increment}>+</button>
  </div>
));

But you don't write React components like this! You create reusable bits of user interface by creating React components that take props and call callbacks passed in as props.

In the next step we'll create a regular old React component and use ylem to "connect" it to the observable store.

Step 4

Connect your Store to your Component

Similar to Redux, ylem is all about connecting data stores to "Dumb" (presentation) components to create "Smart" (container) components.

Once connected, ylem will automatically pass an instance of the Store class to your component as props:

class Store extends ObserveObject {
  count = 0

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

const Counter = props => (
  // props is an instance of your Store class
  <div>
    <input readOnly value={props.count} />
    <button onClick={props.increment}>+</button>
  </div>
);

// connect your Store to your Component
export default ylem(Store, Counter);

Though we used a "function component" for Counter in that example, we could have used any React component at all, even components with their own state. ylem uses the Higher-Order Component pattern to create a new component that wraps the provided component in an "observer component" and passes the observable object instance as props.

ylem let's you enhance the components you already have.

The components passed to ylem are just React components, so it will work with any React component library or the components you've already developed.

This encourages generic, reusable UI Components, that are connected to app specific behavior and state provided by ylem observable objects and arrays.

Step 5

Connect your Store to other Stores

The real power of of ylems Observable Objects is in the fact that you can connect Observable Objects by deriving values from other Observable Objects. Since they are all reactive, any change in one will trigger a change in the other, which may ultimately end up in a a component re-render.

Using class accessor getters (get), Observable Objects will rely on each others properties, and using that, we can organize our Stores into "Domain Models", or concepts that make sense to the app we are building.

Let's start by creating a new file called appstate.js.

This appstate will be an observable object instance, representing our top level store for the whole app.

// appstate.js
import { ObserveObject } from "ylem";

class AppState extends ObserveObject {
  user = null

  login = () => {
    this.user = "yetti";
  }
}

export default new AppState();

Authentication in a real app is much more complicated than what we show here. This example is to demonstrate how it works without getting to deep into the weeds.

Now, back in our App.js component, let's import the appstate instance and use the user property and login method to show how you can split your stores into independent entities, but still keep the reactive nature for your auto-rendering UI components.

import "./App.css";
import React from "react";
import ylem, { ObserveObject } from "ylem";
import appstate from "./appstate";

class Store extends ObserveObject {
  count = 0

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

  get user() {
    return appstate.user;
  }

  login() {
    appstate.login();
  }
}

const Counter = ({ user, login, count, increment }) => (
  <div>
    {user ? (
      <div>Welcome {user.name}!</div>
    ) : (
      <button onClick={login}>Login</button>
    )}
    <hr />
    <input readOnly value={count} />
    <button onClick={increment}>+</button>
  </div>
);

export default ylem(Store, Counter);

Continue learning about ylem...

I hope you can see the value of what we've shown in this contrived example, but ylem offers even more:

  • connect() a Redux like syntax
  • createComponent() for turning observable objects into React component that take a render prop
  • decorators for enhancing your observable classes with async and realtime behaviors and other goodies
  • and more...

Check out the Advanced Topics and Examples to see more of what ylem can do.

Contributing

Read the contributing guides

License

MIT License