All Projects β†’ limscoder β†’ react-wrangler

limscoder / react-wrangler

Licence: MIT license
A react component for simple declarative state management with "one way data flow" and side effects

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to react-wrangler

Cyclow
A reactive frontend framework for JavaScript
Stars: ✭ 105 (+556.25%)
Mutual labels:  data-flow
Redux First History
πŸŽ‰ Redux First History - Redux history binding support react-router - @reach/router - wouter
Stars: ✭ 163 (+918.75%)
Mutual labels:  data-flow
realar
5 kB Advanced state manager for React
Stars: ✭ 41 (+156.25%)
Mutual labels:  data-flow
Pipedream
Connect APIs, remarkably fast. Free for developers.
Stars: ✭ 2,068 (+12825%)
Mutual labels:  data-flow
Flow View
is a visual editor for Dataflow programming
Stars: ✭ 148 (+825%)
Mutual labels:  data-flow
Cenit
πŸš€ Cenit IO - 100% open source integration Platform (iPaaS)
Stars: ✭ 186 (+1062.5%)
Mutual labels:  data-flow
Connective
agent-based reactive programming library for typescript
Stars: ✭ 98 (+512.5%)
Mutual labels:  data-flow
hellhound
A set of libraries to create asynchronous, high performance, scalable and simple application.
Stars: ✭ 33 (+106.25%)
Mutual labels:  data-flow
Refraction
A guard that represents a central point of control in your application
Stars: ✭ 151 (+843.75%)
Mutual labels:  data-flow
roda
RΓΆda: A stream-oriented scripting language
Stars: ✭ 43 (+168.75%)
Mutual labels:  data-flow
Dffml
The easiest way to use Machine Learning. Mix and match underlying ML libraries and data set sources. Generate new datasets or modify existing ones with ease.
Stars: ✭ 123 (+668.75%)
Mutual labels:  data-flow
Ngx Model
Angular Model. Simple state management with minimalistic API, one way data flow, multiple model support and immutable data exposed as RxJS Observable.
Stars: ✭ 137 (+756.25%)
Mutual labels:  data-flow
Reactpatterns
React patterns & techniques to use in development for React Developer βš› .
Stars: ✭ 201 (+1156.25%)
Mutual labels:  data-flow
Asakusafw
Asakusa Framework
Stars: ✭ 114 (+612.5%)
Mutual labels:  data-flow
eventkit
Event-driven data pipelines
Stars: ✭ 94 (+487.5%)
Mutual labels:  data-flow
Programl
Graph-based Program Representation & Models for Deep Learning
Stars: ✭ 102 (+537.5%)
Mutual labels:  data-flow
N8n
Free and open fair-code licensed node based Workflow Automation Tool. Easily automate tasks across different services.
Stars: ✭ 19,252 (+120225%)
Mutual labels:  data-flow
rule-engine
εŸΊδΊŽζ΅η¨‹,δΊ‹δ»Άι©±εŠ¨,可拓展,响应式,θ½»ι‡ηΊ§ηš„θ§„εˆ™εΌ•ζ“Žγ€‚
Stars: ✭ 165 (+931.25%)
Mutual labels:  data-flow
mutable
State containers with dirty checking and more
Stars: ✭ 32 (+100%)
Mutual labels:  data-flow
graflow
A graph stream library for Javascript
Stars: ✭ 53 (+231.25%)
Mutual labels:  data-flow

react-wrangler

react-wrangler simplifies state management by using a declarative component to implement "one way data flow" and side effects.

It's composable components all the way down: no actions, reducers, selectors, generators, middleware, thunks, sagas, query fragments, or observeables required.

Why?

One of the first things I learned when beginning react is that props serve as the public API for components. It's a great concept because eliminating imperative function calls from the API makes components declarative and easily composable. An entire react application is composed by defining a tree of react-elements and using event handlers to trigger state changes in response to user actions. The render->user action->update state->render cycle is commonly referred to as "one way data flow".

This should be a very elegant solution to application development, but many state management frameworks force react developers to struggle with imperative API calls invoked outside of the react-element render tree and allow side effects to be triggered from anywhere in the application, making it difficult to reason about when and why side effects are triggered.

Does "one way data flow" need to be this complicated? react-wrangler is an attempt to get back to basics and provide "one way data flow" with a simple, component based API.

Example

Take a look at the example counter application and the example application code for an idea of how react-wrangler works.

Development

Install

react-wrangler requires peer dependencies immutable and react.

> npm i immutable
> npm i react
> npm i react-wrangler

Configure

The Wrangle component provides a single place to store your state, represented as an immutable Map. Pass the initialState prop to the Wrangle component to define the application's starting state. It uses context to expose data to descendant components, so it should only be rendered once at the root of an application.

import { fromJs } from 'immutable';

const initialState = fromJs({
  user: {
    id: '137af8eb-6baf-42a8-a38a-3df6fc36d562',
    display: 'username999',
    email: '[email protected]',
    preferences: {
      showFirstTimeHelp: true
    }
  },
});

ReactDOM.render(
  <Wrangle initialState={ initialState }>
    <RootComponent />
  </Wrangle>
);

Paths

paths are strings used to reference nodes within react-wrangler state.

Using initialState from above:

// path === 'user.id'
> '137af8eb-6baf-42a8-a38a-3df6fc36d562'

// path === 'user.preferences'
> Map({
  showFirstTimeHelp: true
})

// path === 'user.preferences.showFirstTimeHelp'
> true

// paths that are not present in state are undefined
// path === 'user.missing'
> undefined

Path component

Use the Path component to retrieve path values from state and map them to props:

import { Path } from 'react-wrangler';

function Welcome(props) {
  const { displayName, showFirstTimeHelp, onDismiss } = props;

  const firstTimeHelp = showFirstTimeHelp ?
    <FirstTimeHelp onDismiss={ onDismiss } /> : null;

  return (
    <div>
      <span>Welcome { displayName }</span>
      { firstTimeHelp }
    </div>);
}

function WrangledWelcome(props) {
  // map path values to props
  //
  // key === prop to map path value to
  // value === path
  const mapPathsToProps = {
    displayName: 'user.displayName',
    showFirstTimeHelp: 'user.preferences.showFirstTimeHelp'
  }

  // the `component` prop contains the component to
  // be rendered with paths mapped to props.
  return <Path component={ Welcome } mapPathsToProps={ mapPathsToProps } />;
}

Mutation

An application isn't very useful if it can't mutate it's state. Here is the example from above with state mutation in response to a user action:

function onDismiss(store, ...args) {
  store.setPath('user.perferences.showFirstTimeHelp', false);
}

function WrangledWelcome(props) {
  // map callbacks to props
  //
  // key === prop to map callback to
  // value === callback function
  const mapCallbacksToProps = { onDismiss };
  const mapPathsToProps = { ... };

  return <Path component={ Welcome }
               mapPathsToProps={ mapPathsToProps }
               mapCallbacksToProps={ mapCallbacksToProps } />;
}

Side effects

react-wrangler simplifies application logic by isolating all side effects into two callbacks. Wrangle.onMissingPaths and Wrangle.onStoreChange are the only 2 functions that should invoke side effects within a react-wrangler application.

Fetching data

Use Wrangle.onMissingPaths to fetch data. It's up to the implementer to determine how the missing path values are fetched: Falcor, Graphql, Rest, LocalStorage, AsyncStorage, etc.

onMissingPaths is called once-per-frame with an array of de-duplicated paths that have been requested, but are not present in state.

function onMissingPaths(store, missingPaths) {
  fetchPathsFromServer(missingPaths).then((pathValues) => {
    // call setPaths to update state and
    // re-render the view after pathValues are fetched.
    store.setPaths(pathValues);
  });
}

ReactDOM.render(
  <Wrangle onMissingPaths={ onMissingPaths }>
    <RootComponent />
  </Wrangle>
);

Persisting data

Use Wrangle.onStoreChange to persist data. onStoreChange is called whenever path values are changed. It's up to the implementer to determine how the path values are persisted, such as LocalStorage or an API call that makes a network request.

function onStoreChange(store, changedPaths) {
  updatePathsOnServer(changedPaths)
    .then(() => {
      store.setPath('showNotification': 'saved successfully');
    })
    .catch(() => {
      store.setPath('showNotification': 'failed to save');
    });
}

ReactDOM.render(
  <Wrangle onStoreChange={ onStoreChange }>
    <RootComponent />
  </Wrangle>
);

Debugging

Set the debug prop to enable debugging messages in the console.

<Wrangle debug={true} />

Debugging messages include information about each state update.

> store changed (5): counter.current
 > elapsed time: 0.08500000000094587ms
 > changed paths: {counter.current: 92}
 > new state: {counter: {current: 92}}
 > type 'resetState(5)' in console to return to this state

Use the resetState function in the console to retreive any previous state for "time travel" debugging. Use the setPath function in the console to manually update state (object and array values are automatically converted to Immutable data structures).

Optimization

Use immutable data structures

path values should always be immutable so Path.shouldComponentUpdate can avoid unnessecary vdom re-renders.

// don't set plain JS objects or arrays into state
setPath('user.preferences', { showFirstTimeHelp: true });

// use Immutable data structures instead
setPath('user.preferences', Immutable.fromJS({ showFirstTimeHelp: true }));

Batch calls to setPath

Invoking setPath triggers all <Path /> components to check their state and possibly re-render, so it should be called as infrequently as possible. If multiple paths need to be set at the same time, it's more efficient to invoke setPaths once instead of calling setPath multiple times:

// set multiple paths in one call
setPaths({
  'user.preferences.showFirstTimeHelp': false,
  'user.displayName': 'Kathy'
});

Avoid inline closures for callbacks

Dynamically defined functions in mapCallbacksToProps will cause unnessecary vdom re-renders. Use statically defined functions whenever possible.

Batch requests in onMissingPaths

It's recommended to batch network requests triggered from within onMissingPaths into the fewest number of requests possible.

It's also important to note that onMissingPaths can be called multiple times with the same paths. Everytime the component re-renders, onMissingPaths will be called with all missing paths. Implementers must be careful to avoid duplicate network requests by keeping track of in-flight requests.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].