Full Code of RoyalIcing/react-organism for AI

master 4879bc0a0066 cached
42 files
76.9 KB
20.7k tokens
47 symbols
1 requests
Download .txt
Repository: RoyalIcing/react-organism
Branch: master
Commit: 4879bc0a0066
Files: 42
Total size: 76.9 KB

Directory structure:
gitextract_z5_js_hy/

├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── demo/
│   └── src/
│       ├── components/
│       │   ├── Calculator.js
│       │   ├── Counter.js
│       │   ├── FriendsList.js
│       │   ├── Items.js
│       │   ├── Notifications.js
│       │   ├── PhotosList.js
│       │   └── Row.js
│       ├── index.js
│       ├── organisms/
│       │   ├── Calculator.js
│       │   ├── Counter.js
│       │   ├── Counter2.js
│       │   ├── Counter3.js
│       │   ├── Counter4.js
│       │   ├── Items.js
│       │   ├── ItemsChoice.js
│       │   └── Social.js
│       └── state/
│           ├── counter.js
│           ├── friends.js
│           ├── photos.js
│           ├── placeholderAPI.js
│           └── selection.js
├── nwb.config.js
├── package.json
├── packages/
│   └── create-react-organism/
│       ├── .gitignore
│       ├── README.md
│       ├── bin/
│       │   └── create-react-organism.js
│       └── package.json
├── src/
│   ├── adjustArgs/
│   │   └── extractFromDOM.js
│   ├── index.d.ts
│   ├── index.js
│   ├── multi.js
│   └── nextFrame.js
├── tests/
│   ├── .eslintrc
│   ├── extractFromDOM-test.js
│   ├── index-test.js
│   └── multi-test.js
└── umd/
    └── react-organism.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/coverage
/demo/dist
/es
/lib
/node_modules
npm-debug.log*


================================================
FILE: .travis.yml
================================================
sudo: false

language: node_js
node_js:
  - 4
  - 6
  - 7
  - 8

before_install:
  - npm install codecov.io coveralls

after_success:
  - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js
  - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

branches:
  only:
    - master


================================================
FILE: CONTRIBUTING.md
================================================
## Prerequisites

[Node.js](http://nodejs.org/) >= v4 must be installed.

## Installation

- Running `npm install` in the components's root directory will install everything you need for development.

## Demo Development Server

- `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading.

## Running Tests

- `npm test` will run the tests once.

- `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`.

- `npm run test:watch` will run the tests on every change.

## Building

- `npm run build` will build the component for publishing to npm and also bundle the demo app.

- `npm run clean` will delete built resources.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Patrick Smith

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# React Organism

[![Travis][build-badge]][build]
[![npm package][npm-badge]][npm]
[![Coveralls][coveralls-badge]][coveralls]

**Dead simple React/Preact state management to bring pure components alive**

- Supports `async`/`await` and easy loading (e.g. `fetch()`)
- Reload when particular props change
- Animate using generator functions: just `yield` the new state for each frame
- Tiny: 1.69 KB gzipped (3.49 KB uncompressed)
- Embraces the existing functional `setState` while avoiding boilerplate (no writing `this.setState()` or `.bind` again)
- Easy to unit test

#### Table of contents

- [Installation](#installation)
- [Demos](#demos)
- [Usage](#usage)
  - [Basic](#basic)
  - [Using props](#using-props)
  - [Async & promises](#async)
  - [Handling events](#handling-events)
  - [Animation](#animation)
  - [Serialization: Local storage](#serialization-local-storage)
  - [Separate and reuse state handlers](#separate-and-reuse-state-handlers)
  - [Multicelled organisms](#multicelled-organisms)
- [API](#api)
  - [`makeOrganism(PureComponent, StateFunctions, options)`](#makeorganismpurecomponent-statefunctions-options)
  - [State functions](#state-functions)
  - [Argument enhancers](#argument-enhancers)
- [Why instead of Redux?](#why-instead-of-redux)

## Installation

```
npm i react-organism --save
```

## Demos

- [Animated counter](https://codesandbox.io/s/2vx12v3qmn)
- [Dynamic loading with `import()`](https://codesandbox.io/s/X6mLEwG7W)
- [Live form error validation with Yup](https://codesandbox.io/s/4xQpKRRWx)
- [Multicelled component — using multiple states](https://codesandbox.io/s/Yv7j1xLqM)
- [Todo List](https://codesandbox.io/s/yME5Y3Yz)
- [Inputs, forms, animation, fetch](https://react-organism.now.sh) · [code](https://github.com/BurntCaramel/react-organism/tree/master/demo/src)
- [User Stories Maker](https://codesandbox.io/s/xkZ5ZONl)
- [React Cheat Sheet](https://react-cheat.now.sh/) · [code](https://github.com/BurntCaramel/react-cheat)

## Usage

### Basic

```js
// organisms/Counter.js
import makeOrganism from 'react-organism'
import Counter from './components/Counter'

export default makeOrganism(Counter, {
  initial: () => ({ count: 0 }),
  increment: () => ({ count }) => ({ count: count + 1 }),
  decrement: () => ({ count }) => ({ count: count - 1 })
})
```

```js
// components/Counter.js
import React, { Component } from 'react'

export default function Counter({
  count,
  handlers: {
    increment,
    decrement
  }
}) {
  return (
    <div>
      <button onClick={ decrement } children='−' />
      <span>{ count }</span>
      <button onClick={ increment } children='+' />
    </div>
  )
}
```

### Using props

The handlers can easily use props, which are always passed as the first argument

```js
// organisms/Counter.js
import makeOrganism from 'react-organism'
import Counter from './components/Counter'

export default makeOrganism(Counter, {
  initial: ({ initialCount = 0 }) => ({ count: initialCount }),
  increment: ({ stride = 1 }) => ({ count }) => ({ count: count + stride }),
  decrement: ({ stride = 1 }) => ({ count }) => ({ count: count - stride })
})

// Render passing prop: <CounterOrganism stride={ 20 } />
```

### Async

Asynchronous code to load from an API is easy:

```js
// components/Items.js
import React, { Component } from 'react'

export default function Items({
  items,
  collectionName,
  handlers: {
    load
  }
}) {
  return (
    <div>
      {
        !!items ? (
          `${items.length} ${collectionName}`
        ) : (
          'Loading…'
        )
      }
      <div>
        <button onClick={ load } children='Reload' />
      </div>
    </div>
  )
}
```

```js
// organisms/Items.js
import makeOrganism from 'react-organism'
import Items from '../components/Items'

const baseURL = 'https://jsonplaceholder.typicode.com'
const fetchAPI = (path) => fetch(baseURL + path).then(r => r.json())

export default makeOrganism(Items, {
  initial: () => ({ items: null }),

  load: async ({ path }, prevProps) => {
    if (!prevProps || path !== prevProps.path) {
      return { items: await fetchAPI(path) }
    }
  }
})
```

```js
<div>
  <ItemsOrganism path='/photos' collectionName='photos' />
  <ItemsOrganism path='/todos' collectionName='todo items' />
</div>
```

### Handling events

Handlers can easily accept arguments such as events.

```js
// components/Calculator.js
import React, { Component } from 'react'

export default function Calculator({
  value,
  handlers: {
    changeValue,
    double,
    add3,
    initial
  }
}) {
  return (
    <div>
      <input value={ value } onChange={ changeValue } />
      <button onClick={ double } children='Double' />
      <button onClick={ add3 } children='Add 3' />
      <button onClick={ initial } children='reset' />
    </div>
  )
}
```

```js
// organisms/Calculator.js
import makeOrganism from 'react-organism'
import Calculator from '../components/Calculator'

export default makeOrganism(Calculator, {
  initial: ({ initialValue = 0 }) => ({ value: initialValue }),
  // Destructure event to get target
  changeValue: (props, { target }) => ({ value }) => ({ value: parseInt(target.value, 10) }),
  double: () => ({ value }) => ({ value: value * 2 }),
  add3: () => ({ value }) => ({ value: value + 3 })
})
```

### Animation

```js
import makeOrganism from 'react-organism'
import Counter from '../components/Counter'

export default makeOrganism(Counter, {
  initial: ({ initialCount = 0 }) => ({ count: initialCount }),
  increment: function * ({ stride = 20 }) {
    while (stride > 0) {
      yield ({ count }) => ({ count: count + 1 })
      stride -= 1
    }
  },
  decrement: function * ({ stride = 20 }) {
    while (stride > 0) {
      yield ({ count }) => ({ count: count - 1 })
      stride -= 1
    }
  }
})
```

### Automatically extract from `data-` attributes and `<forms>`

Example coming soon

### Serialization: Local storage

```js
// organisms/Counter.js
import makeOrganism from 'react-organism'
import Counter from '../components/Counter'

const localStorageKey = 'counter'

export default makeOrganism(Counter, {
  initial: ({ initialCount = 0 }) => ({ count: initialCount }),
  load: async (props, prevProps) => {
    if (!prevProps) {
      // Try commenting out:
      /* throw (new Error('Oops!')) */

      // Load previously stored state, if present
      return await JSON.parse(localStorage.getItem(localStorageKey))
    }
  },
  increment: ({ stride = 1 }) => ({ count }) => ({ count: count + stride }),
  decrement: ({ stride = 1 }) => ({ count }) => ({ count: count - stride })
}, {
  onChange(state) {
    // When state changes, save in local storage
    localStorage.setItem(localStorageKey, JSON.stringify(state))
  }
})
```

### Separate and reuse state handlers

React Organism supports separating state handlers and the component into their own files. This means state handlers could be reused by multiple smart components.

Here’s an example of separating state:

```js
// state/counter.js
export const initial = () => ({
  count: 0
})

export const increment = () => ({ count }) => ({ count: count + 1 })
export const decrement = () => ({ count }) => ({ count: count - 1 })
```

```js
// organisms/Counter.js
import makeOrganism from 'react-organism'
import Counter from './components/Counter'
import * as counterState from './state/counter'

export default makeOrganism(Counter, counterState)
```

```js
// App.js
import React from 'react'
import CounterOrganism from './organisms/Counter'

class App extends React.Component {
  render() {
    return (
      <div>
        <CounterOrganism />
      </div>
    )
  }
}
```

### Multicelled Organisms

Example coming soon.


## API

### `makeOrganism(PureComponent, StateFunctions, options?)`
```js
import makeOrganism from 'react-organism'
```
Creates a smart component, rendering using React component `PureComponent`, and managing state using `StateFunctions`.

#### `PureComponent`
A React component, usually a pure functional component. This component is passed as its props:

- The props passed to the smart component, combined with
- The current state, combined with
- `handlers` which correspond to each function in `StateFunctions` and are ready to be passed to e.g. `onClick`, `onChange`, etc.
- `loadError?`: Error produced by the `load` handler
- `handlerError?`: Error produced by any other handler

#### `StateFunctions`
Object with functional handlers. See [state functions below](#state-functions).

Either pass a object directly with each function, or create a separate file with each handler function `export`ed out, and then bring in using `import * as StateFunctions from '...'`.

#### `options`

##### `adjustArgs?(args: array) => newArgs: array`

Used to enhance handlers. See [built-in handlers below](#argument-enhancers).

##### `onChange?(state)`

Called after the state has changed, making it ideal for saving the state somewhere (e.g. Local Storage).


### State functions

Your state is handled by a collection of functions. Each function is pure: they can only rely on the props and state passed to them. Functions return the new state, either immediately or asynchronously.

Each handler is passed the current props first, followed by the called arguments:
- `(props, event)`: most event handlers, e.g. `onClick`, `onChange`
- `(props, first, second)`: e.g. `handler(first, second)`
- `(props, ...args)`: get all arguments passed
- `(props)`: ignore any arguments
- `()`: ignore props and arguments

Handlers must return one of the following:
- An object with new state changes, a la React’s `setState(changes)`.
- A function accepting the previous state and current props, and returns the new state, a la React’s `setState((prevState, props) => changes)`.
- A promise resolving to any of the above (object / function), which will then be used to update the state. Uncaught errors are stored in state under the key `handlerError`. Alternatively, your handler can use the `async`/`await` syntax.
- An iterator, such as one made by using a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function%2A). Each object passed to `yield` may be one of the above (object / function / promise).
- An array of any of the above (object / function / promise / iterator).
- Or optionally, nothing.

There are some handlers for special tasks, specifically:

#### `initial(props) => object` (required)
Return initial state to start off with, a la React’s `initialState`. Passed props.

#### `load(props: object, prevProps: object?, { handlers: object }) => object | Promise<object> | void` (optional)
Passed the current props and the previous props. Return new state, a Promise returning new state, or nothing. You may also use a generator function (`function * load(props, prevProps)`) and `yield` state changes.

If this is the first time loaded or if being reloaded, then `prevProps` is `null`.

Usual pattern is to check for either `prevProps` being `null` or if the prop of interest has changed from its previous value:
```js
export const load = async ({ id }, prevProps) => {
  if (!prevProps || id !== prevProps.id) {
    return { item: await loadItem(id) }
  }
}
```

Your `load` handler will be called in React’s lifecycle: `componentDidMount` and `componentWillReceiveProps`.


### Argument enhancers

Handler arguments can be adjusted, to cover many common cases. Pass them to the `adjustArgs` option. The following enhancers are built-in:

#### `extractFromDOM(args: array) => newArgs: array`
```js
import extractFromDOM from 'react-organism/lib/adjustArgs/extractFromDOM'
```

Extract values from DOM, specifically:
- For events as the first argument, extracts `value`, `checked`, and `name` from `event.target`. Additionally, if target has `data-` attributes, these will also be extracted in camelCase from its [`dataset`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset). Suffixing `data-` attributes with `_number` will convert value to a number (instead of string) using `parseFloat`, and drop the suffix. Handler will receive these extracted values in an object as the first argument, followed by the original arguments.
- For `submit` events, extracts values of `<input>` fields in a `<form>`. Handler will receive the values keyed by the each input’s `name` attribute, followed by the original arguments. Pass the handler to the `onSubmit` prop of the `<form>`. Form must have `data-extract` attribute present. To clear the form after submit, add `data-reset` to the form.


## Why instead of Redux?

- Like Redux, separate your state management from rendering
- Unlike Redux, avoid loose strings for identifying actions
- Redux encourages having state in one bundle, whereas dynamic `import()` encourages breaking apps into sections
- Easier to reuse functionality, as action handlers are totally encapsulated
- No ability to reach across to the other side of your state tree
- Encourages composition of components
- Supports `async` and `await` in any action
- Supports generator functions to allow multiple state changes — great for animation
- No `switch` statements
- No boilerplate or additional helper libraries needed


[build-badge]: https://img.shields.io/travis/RoyalIcing/react-organism/master.png?style=flat-square
[build]: https://travis-ci.org/RoyalIcing/react-organism

[npm-badge]: https://img.shields.io/npm/v/react-organism.png?style=flat-square
[npm]: https://www.npmjs.org/package/react-organism

[coveralls-badge]: https://img.shields.io/coveralls/RoyalIcing/react-organism/master.png?style=flat-square
[coveralls]: https://coveralls.io/github/RoyalIcing/react-organism


================================================
FILE: demo/src/components/Calculator.js
================================================
import React, { Component } from 'react'

export default function Calculator({
  value,
  handlers: {
    changeValue,
    double,
    add3,
    initial
  }
}) {
  return (
    <div className='h-spaced'>
      <input value={ value } onChange={ changeValue } />
      <button onClick={ double } children='Double' />
      <button onClick={ add3 } children='Add 3' />
      <button onClick={ initial } children='Reset' />
    </div>
  )
}

================================================
FILE: demo/src/components/Counter.js
================================================
import React, { Component } from 'react'

export default function Counter({
  count,
  handlers: {
    increment,
    decrement,
    initial
  }
}) {
  return (
    <div className='h-spaced'>
      <button onClick={ decrement } children='−' />
      <span>{ count }</span>
      <button onClick={ increment } children='+' />
      <button onClick={ initial } children='Reset' />
    </div>
  )
}

================================================
FILE: demo/src/components/FriendsList.js
================================================
import React, { Component } from 'react'

export default function FriendsList({
  friendsList,
  selectedIndex,
  onSelectAtIndex,
  handlers: {
    addRandomFriend
  }
}) {
  return (
    <div>
      {
        !!friendsList ? (
          friendsList.map((friend, index) => (
            <div key={ index }
              style={{
                backgroundColor: selectedIndex === index ? '#00b4ff' : undefined
              }}
              data-index_number={ index }
              onClick={ onSelectAtIndex }
            >
              Name: { friend.name }
            </div>
          ))
        ) : (
          'Loading…'
        )
      }
      <div>
        <button onClick={ addRandomFriend } children='Add random friend' />
      </div>
    </div>
  )
}

================================================
FILE: demo/src/components/Items.js
================================================
import React, { Component } from 'react'

export default function Counter({
  items,
  collectionName,
  handlers: {
    load
  }
}) {
  return (
    <div>
      {
        !!items ? (
          `${items.length} ${collectionName}`
        ) : (
          'Loading…'
        )
      }
      <div>
        <button onClick={ load } children='Reload' />
      </div>
    </div>
  )
}

================================================
FILE: demo/src/components/Notifications.js
================================================
import React from 'react'

export default function Notifications({
  friends: {
    friendsList
  },
  photos: {
    photosList
  }
}) {
  return (
    <div>
      { `${friendsList.length} friends` }
      { ' | ' }
      { `${photosList.length} photos` }
    </div>
  )
}

================================================
FILE: demo/src/components/PhotosList.js
================================================
import React, { Component } from 'react'

export default function PhotosList({
  photosList,
  handlers: {
    addRandomPhoto,
    addPhoto
  }
}) {
  return (
    <div>
      {
        !!photosList ? (
          photosList.map((photo, index) => (
            <div key={ index }>
              <img src={ photo.url } />
            </div>
          ))
        ) : (
          'Loading…'
        )
      }
      <div>
        <form data-extract data-reset onSubmit={ addPhoto }>
          <label>
            {'URL: '}
            <input type='url' name='url' />
          </label>
          <button type='submit' children='Add photo' />
        </form>
      </div>
      <div>
        <button onClick={ addRandomPhoto } children='Add random photo' />
      </div>
    </div>
  )
}

================================================
FILE: demo/src/components/Row.js
================================================
import React from 'react'

const style = {
  display: 'flex',
  flexDirection: 'row'
}

export default function Row({
  children
}) {
  return (
    <div style={ style } children={ children } />
  )
}

================================================
FILE: demo/src/index.js
================================================
import React, { Component } from 'react'
import { render } from 'react-dom'

import CounterOrganism from './organisms/Counter'
import Counter2Organism from './organisms/Counter2'
import Counter3Organism from './organisms/Counter3'
import Counter4Organism from './organisms/Counter4'
import ItemsOrganism from './organisms/Items'
import ItemsChoiceOrganism from './organisms/ItemsChoice'
import CalculatorOrganism from './organisms/Calculator'
import SocialOrganism from './organisms/Social'

class Demo extends Component {
  render() {
    return <div>
      <h1><a href="https://github.com/RoyalIcing/react-organism">react-organism</a></h1>
      <h3>Simple counter:</h3>
      <CounterOrganism />
      <hr />
      <h3>Using props to customize:</h3>
      <Counter2Organism initialCount={ 9 } stride={ 3 } />
      <hr />
      <h3>Local storage (change and reload page):</h3>
      <Counter3Organism />
      <hr />
      <h3>Async animated:</h3>
      <Counter4Organism />
      <hr />
      <h3>Load data from API:</h3>
      <ItemsOrganism path='/posts' collectionName='posts' />
      <hr />
      <ItemsOrganism path='/photos' collectionName='photos' />
      <hr />
      <ItemsOrganism path='/todos' collectionName='todos' />
      <hr />
      <h3>Handling prop changes:</h3>
      <ItemsChoiceOrganism />
      <hr />
      <h3>Event handlers with calculator:</h3>
      <CalculatorOrganism />
      <hr />
      <h3>Multi-celled organism</h3>
      <SocialOrganism darkMode />

      <style>{`
* {
  padding: 0;
  font-size: inherit;
  box-sizing: border-box;
}

html {
  font-size: 18px;
  font-family: system, "-apple-system", "-webkit-system-font", BlinkMacSystemFont, "Helvetica Neue", "Helvetica", "Segoe UI", "Roboto", "Arial", "freesans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  line-height: 1.3;
}

a {
  color: inherit;
}

h1 {
  font-size: 1.5rem;
}

button, input {
  padding: 0.2em 0.5em;
  border: 1px solid #ccc;
  border-radius: 0.25em;
}
button {
  color: #222;
  background: #eee;
}

hr {
  border: none;
  border-top: 1px solid #ccc;
}

.h-spaced > * {
  margin-right: 0.5em;
}
`}</style>
    </div>
  }
}

render(<Demo/>, document.querySelector('#demo'))


================================================
FILE: demo/src/organisms/Calculator.js
================================================
import makeOrganism from '../../../src'
import extractFromDOM from '../../../src/adjustArgs/extractFromDOM'
import Calculator from '../components/Calculator'

export default makeOrganism(Calculator, {
  initial: ({ initialValue = 0 }) => ({ value: initialValue }),
  changeValue: (props, { value }) => ({ value: parseInt(value, 10) || '' }),
  // Or more robust number input handling with fallback to previous value:
  // changeValue: (props, { target: { value: newValue } }) => ({ value: previousValue }) => ({
  //   value: newValue && (parseInt(newValue, 10) || previousValue)
  // }),
  double: () => ({ value }) => ({ value: (value || 0) * 2 }),
  add3: () => ({ value }) => ({ value: (value || 0) + 3 })
}, {
  adjustArgs: extractFromDOM
})


================================================
FILE: demo/src/organisms/Counter.js
================================================
import makeOrganism from '../../../src'
import * as counterState from '../state/counter'
import Counter from '../components/Counter'

//export default makeOrganism(Counter, counterState)

export default makeOrganism(Counter, {
  initial: () => ({ count: 0 }),
  increment: () => ({ count }) => ({ count: count + 1 }),
  decrement: () => ({ count }) => ({ count: count - 1 })
})


================================================
FILE: demo/src/organisms/Counter2.js
================================================
import makeOrganism from '../../../src'
import Counter from '../components/Counter'

export default makeOrganism(Counter, {
  initial: ({ initialCount = 0 }) => ({ count: initialCount }),
  increment: () => ({ count }, { stride = 1 }) => ({ count: count + stride }),
  decrement: () => ({ count }, { stride = 1 }) => ({ count: count - stride })
})


================================================
FILE: demo/src/organisms/Counter3.js
================================================
import makeOrganism from '../../../src'
import Counter from '../components/Counter'

const localStorageKey = 'counter3'

export default makeOrganism(Counter, {
  initial: ({ initialCount = 13 }) => ({ count: initialCount }),
  load: async (props, prevProps) => {
    if (!prevProps) {
      // Try commenting out:
      /* throw (new Error('Oops!')) */

      // Load previously stored state, if present
      return await JSON.parse(localStorage.getItem(localStorageKey))
    }
  },
  increment: ({ stride = 1 }) => ({ count }) => ({ count: count + stride }),
  decrement: ({ stride = 1 }) => ({ count }) => ({ count: count - stride })
}, {
  onChange(state) {
    // When state changes, save in local storage
    localStorage.setItem(localStorageKey, JSON.stringify(state))
  }
})


================================================
FILE: demo/src/organisms/Counter4.js
================================================
import makeOrganism from '../../../src'
import Counter from '../components/Counter'

export default makeOrganism(Counter, {
  initial: ({ initialCount = 0 }) => ({ count: initialCount }),
  increment: function * ({ stride = 20 }) {
    while (stride > 0) {
      yield ({ count }) => ({ count: count + 1 })
      stride -= 1
    }
  },
  decrement: function * ({ stride = 20 }) {
    while (stride > 0) {
      yield ({ count }) => ({ count: count - 1 })
      stride -= 1
    }
  }
})


================================================
FILE: demo/src/organisms/Items.js
================================================
import makeOrganism from '../../../src'
import * as loadItemsState from '../state/placeholderAPI'
import Items from '../components/Items'

// export default makeOrganism(Items, loadItemsState)

const baseURL = 'https://jsonplaceholder.typicode.com'
const fetchAPI = (path) => fetch(baseURL + path).then(r => r.json())

export default makeOrganism(Items, {
  initial: () => ({ items: null }),

  load: function * ({ path }, prevProps) {
    if (!prevProps || path !== prevProps.path) {
      yield { items: null } // Clear so 'loading…' text appears
      yield fetchAPI(path).then(items => ({ items }))
    }
  }
})


================================================
FILE: demo/src/organisms/ItemsChoice.js
================================================
import React from 'react'
import makeOrganism from '../../../src'
import ItemsOrganism from './Items'

export default makeOrganism(({
  collection,
  handlers: {
    selectPosts,
    selectPhotos,
    selectTodos
  }
}) => (
  <div>
    <div className='h-spaced'>
      <button onClick={ selectPosts }>Posts</button>
      <button onClick={ selectPhotos }>Photos</button>
      <button onClick={ selectTodos }>Todos</button>
    </div>
    <ItemsOrganism path={ '/' + collection } collectionName={ collection } />
  </div>
), {
  initial: () => ({ collection: 'posts' }),
  selectPosts: () => ({ collection: 'posts' }),
  selectPhotos: () => ({ collection: 'photos' }),
  selectTodos: () => ({ collection: 'todos' })
})

================================================
FILE: demo/src/organisms/Social.js
================================================
import React from 'react'
import makeMultiCelledOrganism from '../../../src/multi'
import extractFromDOM from '../../../src/adjustArgs/extractFromDOM'
import PhotosList from '../components/PhotosList'
import FriendsList from '../components/FriendsList'
import Notifications from '../components/Notifications'
import Row from '../components/Row'

import * as friends from '../state/friends'
import * as photos from '../state/photos'
import * as selection from '../state/selection'

const styles = {
  light: {
    color: '#111',
    backgroundColor: '#f6f6f6'
  },
  dark: {
    color: '#f6f6f6',
    backgroundColor: '#222'
  }
}

function Social({
  darkMode = false,
  cells
}) {
  return (
    <div style={ darkMode ? styles.dark : styles.light }>
      <Notifications friends={ cells.friends } photos={ cells.photos } />
      <FriendsList
        { ...cells.friends }
        selectedIndex={ cells.selection.selectedFriendIndex }
        onSelectAtIndex={ cells.selection.handlers.selectFriendAtIndex }
      />
      <PhotosList
        { ...cells.photos }
        selectedIndex={ cells.selection.selectedPhotoIndex }
        onSelectAtIndex={ cells.selection.handlers.selectPhotoAtIndex }
      />
    </div>
  )
}

export default makeMultiCelledOrganism(Social, {
  friends,
  photos,
  selection
}, {
  adjustArgs: extractFromDOM
})

================================================
FILE: demo/src/state/counter.js
================================================
export const initial = () => ({ count: 0 })

export const increment = () => ({ count }) => ({ count: count + 1 })
export const decrement = () => ({ count }) => ({ count: count - 1 })

================================================
FILE: demo/src/state/friends.js
================================================
export const initial = () => ({
  friendsList: []
})

const convertUserToFriend = (user) => ({
  name: `${user.first} ${user.last}`
})

const fetchRandomFriends = () =>
  fetch('https://randomapi.com/api/6de6abfedb24f889e0b5f675edc50deb?fmt=raw&sole')
    .then(res => res.json())
    .then(users => users.slice(0, 10))
    .then(users => users.map(convertUserToFriend))

export const load = async (props, prevProps) => {
  if (!prevProps) {
    const newFriends = await fetchRandomFriends()
    return ({ friendsList }) => ({ friendsList: friendsList.concat(newFriends) })
  }
}

export const addFriend = (props, { name }) => ({ friendsList }) => ({ friendsList: friendsList.concat({ name }) })

export const addRandomFriend = async ({ handlers }) => {
  const [ newFriend ] = await fetchRandomFriends()
  handlers.addFriend(newFriend)
}

================================================
FILE: demo/src/state/photos.js
================================================
export const initial = () => ({
  photosList: []
})

const fetchRandomPhotoURL = () =>
  fetch(
    'https://source.unsplash.com/random/800x600',
    { method: 'HEAD', cache: 'no-cache' }
  )
    .then(res => res.url)

export const load = async (props, prevProps) => {
  if (!prevProps) {
    const url = await fetchRandomPhotoURL()
    return ({ photosList }) => ({ photosList: photosList.concat({ url }) })
  }
}

export const addPhoto = (props, { url }) => ({ photosList }) => ({ photosList: photosList.concat({ url }) })

export const addRandomPhoto = async (props) => {
  const url = await fetchRandomPhotoURL()
  return addPhoto({}, { url })
}

================================================
FILE: demo/src/state/placeholderAPI.js
================================================
const baseURL = 'https://jsonplaceholder.typicode.com'
const fetchAPI = (path) => fetch(baseURL + path).then(r => r.json())

export const initial = () => ({ items: null })

export const load = async ({ path }, prevProps) => {
  if (!prevProps || path !== prevProps.path) {
    console.log('load', path)
    return { items: await fetchAPI(path) }
  }
}


================================================
FILE: demo/src/state/selection.js
================================================
export const initial = () => ({
  selectedFriendIndex: null,
  selectedPhotoIndex: null
})

export const selectFriendAtIndex = (props, { index }) => ({ selectedFriendIndex: index })
export const selectPhotoAtIndex = (props, { index }) => ({ selectedPhotoIndex: index })


================================================
FILE: nwb.config.js
================================================
module.exports = {
  type: 'react-component',
  webpack: {
    hoisting: true
  },
  npm: {
    esModules: true,
    umd: {
      global: 'makeOrganism',
      externals: {
        'react': 'React'
      }
    }
  }
}


================================================
FILE: package.json
================================================
{
  "name": "react-organism",
  "version": "0.3.8",
  "description": "Separate React state in a dead simple functional way",
  "main": "lib/index.js",
  "module": "es/index.js",
  "types": "src/index.d.ts",
  "files": [
    "css",
    "es",
    "lib",
    "umd"
  ],
  "scripts": {
    "build": "nwb build-react-component",
    "clean": "nwb clean-module && nwb clean-demo",
    "dev": "nwb serve-react-demo",
    "test": "nwb test-react",
    "test:coverage": "nwb test-react --coverage",
    "test:watch": "nwb test-react --server",
    "now:deploy": "yarn run build && cd demo/dist && now deploy",
    "explore-bundle": "source-map-explorer",
    "prepublishOnly": "npm run test && npm run build"
  },
  "dependencies": {
    "awareness": "^1.1.4"
  },
  "peerDependencies": {
    "react": "^15.0.0-0 || ^16.0.0-0"
  },
  "devDependencies": {
    "nwb": "^0.21.5",
    "react": "^16.0.0-0",
    "react-dom": "^16.0.0-0",
    "source-map-explorer": "^1.3.3"
  },
  "author": "",
  "homepage": "",
  "license": "MIT",
  "repository": "",
  "keywords": [
    "react-component"
  ]
}


================================================
FILE: packages/create-react-organism/.gitignore
================================================
node_modules

================================================
FILE: packages/create-react-organism/README.md
================================================
# create-react-organism

Easily create [react-organism](https://github.com/RoyalIcing/react-organism) smart components.

## Usage

```sh
yarn create react-organism CustomName

# or

npm i -g create-react-organism
create-react-organism CustomName
```

Creates an **organisms** directory in your project, and a directory for your organism inside there: **[/src]/organisms/CustomName/**

Three files are created inside:
- **[CustomName].js:** a pure React component that renders the given state as props, as well as call action handlers defined in **state.js**
- **state.js:** a list of exported functions that handle the progression of state, from its `initial` form, to `load` data in asynchronously, to other action handlers that are called in response to events (e.g. onClick, onChange, etc).
- **index.js:** connects the pure component and state together and exports it for easy use.

To use the organism, simply import it:

```javascript
// pages/example.js
import CustomName from '../organisms/CustomName'

export default () => (
    <div>
        <CustomName prop1='one' />
    </div>
)
```

================================================
FILE: packages/create-react-organism/bin/create-react-organism.js
================================================
#!/usr/bin/env node

const Path = require('path')
const FS = require('fs')
const Spawn = require('cross-spawn')
const { resolve, coroutine, runNode } = require('creed')
const _ = require('lodash')

const accessibleFile = (path) => runNode(FS.access, path).map(() => path).catch(() => null)

const ensureDir = (path) => runNode(FS.mkdir, path).catch(e => {
    if (e.code === 'EEXIST') {
        return
    }

    throw e
})

const installPackage = coroutine(function * installPackage(projectPath, packageName) {
    const appPackage = require(Path.join(projectPath, 'package.json'))
    const dependencies = appPackage.dependencies || {}
    const hasInstalled = !!dependencies[packageName]
    if (hasInstalled) {
        return
    }

    const useYarn = !!(yield accessibleFile(Path.join(projectPath, 'yarn.lock')))
    const command = useYarn ? 'yarnpkg' : 'npm'
    let args = useYarn ? ['add'] : ['install', '--save']
    args.push(packageName)
    const proc = Spawn.sync(command, args, { stdio: 'inherit' })
    if (proc.status !== 0) {
      throw new Error(`\`${command} ${args.join(' ')}\` failed with status ${proc.status}`)
    }
})

function * run([ inputName, ...args ]) {
    //const fileName = _.kebabCase(inputName)
    const componentName = _.upperFirst(_.camelCase(inputName))

    let projectPath = process.cwd()
    let srcPath = yield accessibleFile(Path.resolve(projectPath, 'src'))
    const codePath = srcPath || projectPath

    // Add react-organism dependency
    yield installPackage(projectPath, 'react-organism')

    // organisms/
    const organismsDirPath = Path.resolve(codePath, 'organisms')
    yield ensureDir(organismsDirPath)

    // organisms/:fileName
    const organismPath = Path.join(organismsDirPath, componentName)
    yield ensureDir(organismPath)

    // organisms/:fileName/component.js
    yield runNode(FS.writeFile, Path.join(organismPath, 'component.js'), makeComponentJS(componentName))
    // organisms/:fileName/state.js
    yield runNode(FS.writeFile, Path.join(organismPath, 'state.js'), makeStateJS(componentName))
    // organisms/:fileName/index.js
    yield runNode(FS.writeFile, Path.join(organismPath, 'index.js'), makeIndexJS(componentName))
}


function makeStateJS(componentName) {
    return `
export const initial = () => ({
  // TODO: initial state properties
})

export const example = (props, ...args) => (prevState) => {
    // TODO: return changed state
    return prevState
}
`.trim()
}


function makeComponentJS(componentName) {
    return `
import React from 'react'

export default function ${componentName}({
    // TODO: props
    handlers: {
        example
        // TODO: state handlers
    }
}) {
  return (
    <div>
    </div>
  )
}
`.trim()
}

function makeIndexJS(componentName) {
    return `
import makeOrganism from 'react-organism'
import ${componentName} from './component'
import * as state from './state'

export default makeOrganism(${componentName}, state)
`.trim()
}

coroutine(run)(process.argv.slice(2))
    .catch(error => {
        console.error(error.message)
    })


================================================
FILE: packages/create-react-organism/package.json
================================================
{
  "name": "create-react-organism",
  "version": "0.2.0",
  "description": "Tool to easily create react-organism smart components",
  "engines": {
    "node": ">=6"
  },
  "bin": {
    "create-react-organism": "./bin/create-react-organism.js"
  },
  "main": "index.js",
  "repository": "https://github.com/RoyalIcing/react-organism",
  "author": "Patrick Smith <patrick@burntcaramel.com>",
  "license": "MIT",
  "dependencies": {
    "creed": "^1.2.1",
    "cross-spawn": "^5.1.0",
    "lodash": "^4.17.4"
  }
}


================================================
FILE: src/adjustArgs/extractFromDOM.js
================================================
import extractValuesFromDOMEvent from 'awareness/lib/extractValuesFromDOMEvent'

export default function extractFromDOM(args) {
  if (args[0]) {
    const values = extractValuesFromDOMEvent(args[0])

    // Place extracted dataset values first, followed by original arguments
    args = [values].concat(args)
  }

  return args
}

================================================
FILE: src/index.d.ts
================================================
declare module 'react-organism' {
  import React from 'react'

  export interface ReceiverProps<HandlersOut> {
    handlers: HandlersOut
  }

  function makeOrganism<Props, State, HandlersIn, HandlersOut>(
    Pure:
      | React.ComponentClass<State & ReceiverProps<HandlersOut>>
      | React.StatelessComponent<State & ReceiverProps<HandlersOut>>,
    handlersIn: HandlersIn,
    options?: {
      onChange: (newState: State) => {}
      adjustArgs: (args: any[]) => any[]
    }
  ): React.ComponentClass<Props>

  export default makeOrganism
}


================================================
FILE: src/index.js
================================================
import React, { PureComponent } from 'react'
import makeAwareness from 'awareness'

// Returns a new stateful component, given the specified state handlers and a pure component to render with
export default (
  Pure,
  handlersIn,
  {
    onChange,
    adjustArgs
  } = {}
) => class Organism extends PureComponent {
  static initialStateForProps(props) {
    return handlersIn.initial(props)
  }

  get currentState() {
    return this.props.getState ? this.props.getState() : this.state
  }

  alterState = (stateChanger) => {
    // Can either be a plain object or a callback to transform the existing state
    (this.props.setState || this.setState).call(
      this,
      stateChanger,
      // Call onChange once updated with current version of state
      onChange ? () => { onChange(this.currentState) } : undefined
    )
  }

  awareness = makeAwareness(this.alterState, handlersIn, {
    getProps: () => this.props,
    adjustArgs
  })

  state = this.awareness.state

  componentDidMount() {
    this.awareness.loadAsync(this.props, null, this.currentState)
  }

  componentDidUpdate(prevProps, prevState) {
    this.awareness.loadAsync(this.props, prevProps, this.currentState)
  }

  render() {
    // Render the pure component, passing both props and state, plus handlers bundled together
    return <Pure { ...this.props } { ...this.currentState } handlers={ this.awareness.handlers } />
  }
}


================================================
FILE: src/multi.js
================================================
import React from 'react'
import makeAwareness from 'awareness'
import nextFrame from './nextFrame'

function cellStateChangerCatchingError(cellKey, stateChanger, errorKey) {
  return (prevState, props) => {
    let cellChanges = {}
    // Check if stateChanger is a function
    if (typeof(stateChanger) === typeof(stateChanger.call)) {
      try {
        // Call state changer
        cellChanges = stateChanger(prevState[cellKey], props)
      }
      // State changer may throw
      catch (error) {
        // Store error in state
        return { [errorKey]: error }
      }
    }
    // Else just an object with changes
    else {
      cellChanges = stateChanger
    }
    return {
      [cellKey]: Object.assign({}, prevState[cellKey], cellChanges)
    }
  }
}

function processStateChanger(changeState, stateChanger, storeError) {
  if (!stateChanger) {
    return;
  }

  // Check if thenable (i.e. a Promise)
  if (typeof stateChanger.then === typeof Object.assign) {
    return stateChanger.then(stateChanger => (
      stateChanger && changeState(stateChanger)
    ))
    .catch(storeError)
  }
  // Check if iterator
  else if (typeof stateChanger.next === typeof Object.assign) {
    return processIterator(changeState, stateChanger, storeError)
  }
  // Otherwise, change state immediately
  // Required for things like <textarea> onChange to keep cursor in correct position
  else {
    changeState(stateChanger)
  }
}

function processIterator(changeState, iterator, storeError, previousValue) {
  return Promise.resolve(processStateChanger(changeState, previousValue, storeError)) // Process the previous changer
  .then(() => nextFrame()) // Wait for next frame
  .then(() => {
    const result = iterator.next() // Get the next step from the iterator
    if (result.done) { // No more iterations remaining
      return processStateChanger(changeState, result.value, storeError) // Process the changer
    }
    else {
      return processIterator(changeState, iterator, storeError, result.value) // Process the iterator’s following steps
    }
  })
}

export default function makeMultiOrganism(
  Parent,
  cells,
  {
    onChange,
    adjustArgs
  } = {}
) {
  return class OrganismMulticelled extends React.Component {
    getProps = () => this.props

    cellsAwareness = Object.keys(cells).reduce((out, cellKey) => {
      const alterState = (stateChanger) => {
        // Can either be a plain object or a callback to transform the existing state
        this.setState(
          (prevState, props) => {
            const cellChanges = stateChanger(prevState[cellKey])
            if (cellChanges.loadError || cellChanges.handlerError) {
              return cellChanges
            }
            return {
              [cellKey]: Object.assign({}, prevState[cellKey], cellChanges)
            }
          },
          // Call onChange once updated with current version of state
          onChange ? () => { onChange(this.state) } : undefined
        )
      }

      out[cellKey] = makeAwareness(alterState, cells[cellKey], {
        getProps: this.getProps,
        adjustArgs
      })
      return out
    }, {})
    
    state = Object.keys(this.cellsAwareness).reduce((state, cellKey) => {
      // Grab each cell’s initial value
      state[cellKey] = this.cellsAwareness[cellKey].state
      return state
    }, {
      loadError: null,
      handlerError: null
    })

    changeState(stateChanger) {
      // Can either be a plain object or a callback to transform the existing state
      this.setState(
        stateChanger,
        // Call onChange once updated with current version of state
        onChange ? () => { onChange(this.state) } : undefined
      )
    }

    loadPromises(nextProps, prevProps) {
      return Object.keys(cells).map(cellKey => {
        const handlersIn = cells[cellKey]
        if (handlersIn.load) {
          // Wrap in Promise to safely catch any errors thrown by `load`
          return Promise.resolve(true)
            .then(() => handlersIn.load(nextProps, prevProps))
            .then(values => (!!values ? { values, cellKey } : undefined))
        }
      })
          .filter(Boolean) // Filter out cells without .load
    }

    // Uses `load` handler, if present, to asynchronously load initial state
    loadAsync(nextProps, prevProps) {
      this.loadPromises(nextProps, prevProps)
        .forEach(promise => {
          promise.then(result => {
            if (!result) {
              return
            }
            this.changeState(cellStateChangerCatchingError(result.cellKey, result.values, 'loadError'))
          })
          .catch(error => this.changeState({ loadError: error }))
        })
    }

    // Used by Next.js
    getInitialProps(props) {
      return Promise.all(this.loadPromises(props, null))
        .then(results => results.filter(Boolean)) // Filter out .load that returned nothing
        .then(results => (
          results.reduce((initialCellValues, { values, cellKey }) => {
            initialCellValues[cellKey] = values
            return initialCellValues
          }, {})
        ))
    }

    componentDidMount() {
      this.loadAsync(this.props, null)
    }

    componentWillReceiveProps(nextProps) {
      this.loadAsync(nextProps, this.props)
    }

    cellsProxy = Object.keys(cells).reduce((cellsProxy, cellKey) => {
      const { handlers } = this.cellsAwareness[cellKey]

      Object.defineProperty(cellsProxy, cellKey, {
        get: () => {
          // Track which cells are used
          //this.usedCells[cellKey] = true
          return Object.assign({}, this.state[cellKey], { handlers })
        }
      })
      return cellsProxy
    }, {})

    render() {
      // Render the pure component, passing both props and cells
      return <Parent { ...this.props } cells={ this.cellsProxy } />
    }
  }
}


================================================
FILE: src/nextFrame.js
================================================
export default () => new Promise((resolve) => {
  window.requestAnimationFrame(resolve)
})


================================================
FILE: tests/.eslintrc
================================================
{
  "env": {
    "mocha": true
  }
}


================================================
FILE: tests/extractFromDOM-test.js
================================================
import expect from 'expect'
import React from 'react'
import {render, unmountComponentAtNode} from 'react-dom'
import ReactTestUtils from 'react-dom/test-utils'

import makeOrganism from 'src/'
import extractFromDOM from 'src/adjustArgs/extractFromDOM'

const waitMs = duration => new Promise(resolve => setTimeout(resolve, duration))

function PhotosList({
  photosList,
  selectedPhotoIndex,
  handlers: {
    addPhoto,
    selectPhotoAtIndex
  }
}) {
  return (
    <div>
      <div id='selectionStatus'>
      {
        selectedPhotoIndex == null ? (
          'No photo selected'
        ) : (
          `Selected ${selectedPhotoIndex}`
        )
      }
      </div>
      <div id='photos'>
      {
        photosList.length > 0 ? (
          photosList.map((photo, index) => (
            <button key={ index }
              id={`photo-${index}`}
              data-index_number={ index }
              onClick={ selectPhotoAtIndex }
              >
              <img src={ photo.url } />
            </button>
          ))
        ) : (
          'No photos'
        )
      }
      </div>
      <div>
        <form id='addPhotoForm' data-extract data-reset onSubmit={ addPhoto }>
          <label>
            {'URL: '}
            <input id='urlField' type='url' name='url' />
          </label>
          <button id='addPhoto' type='submit' children='Add photo' />
        </form>
      </div>
    </div>
  )
}

describe('extractFromDOM', () => {
  let node

  beforeEach(() => {
    node = document.createElement('div')
  })

  afterEach(() => {
    unmountComponentAtNode(node)
  })

  it('Reads form values', (done) => {
    const PhotosListOrganism = makeOrganism(PhotosList, {
      initial: () => ({
        photosList: [],
        selectedPhotoIndex: null
      }),
      addPhoto: (props, { url }) => ({ photosList }) => ({ photosList: photosList.concat({ url }) }),
      selectPhotoAtIndex: (props, { index }) => ({ selectedPhotoIndex: index })
    }, {
      adjustArgs: extractFromDOM
    })
    const $ = (selector) => node.querySelector(selector)
    render(<PhotosListOrganism />, node, async () => {
      expect($('#photos').innerHTML).toContain('No photos')

      $('#urlField').value = 'https://via.placeholder.com/350x150'
      ReactTestUtils.Simulate.change($('#urlField'))

      // Should extract data using named field
      ReactTestUtils.Simulate.submit($('#addPhotoForm'))
      await waitMs(10)
      expect($('#photos').innerHTML).toContain('https://via.placeholder.com/350x150')
      // Should reset fields if data-reset present
      expect($('#urlField').value).toBe('')

      // Should extract data- attributes
      expect($('#selectionStatus').innerHTML).toContain('No photo selected')
      
      ReactTestUtils.Simulate.click($('#photo-0'))
      expect($('#selectionStatus').innerHTML).toContain('Selected 0')

      done()
    })
  })

})


================================================
FILE: tests/index-test.js
================================================
import expect from 'expect'
import React, { Component } from 'react'
import {render, unmountComponentAtNode} from 'react-dom'
import ReactTestUtils from 'react-dom/test-utils'

import makeOrganism from 'src/'

const waitMs = duration => new Promise(resolve => setTimeout(resolve, duration))

const nextFrame = () => new Promise((resolve) => {
  window.requestAnimationFrame(resolve)
})


function Counter({
  count,
  handlers: {
    increment,
    decrement,
    delayedIncrement,
    delayedIncrementGenerator,
    doNothing,
    blowUp,
    blowUp2,
    blowUpDelayed,
    initial,
    load
  }
}) {
  return (
    <div>
      <button id='decrement' onClick={ decrement } children='−' />
      <span>{ count }</span>
      <button id='increment' onClick={ increment } children='+' />
      { delayedIncrement &&
        <button id='delayedIncrement' onClick={ delayedIncrement } children='+' />
      }
      { delayedIncrementGenerator &&
        <button id='delayedIncrementGenerator' onClick={ delayedIncrementGenerator } children='+' />
      }
      { doNothing &&
        <button id='doNothing' onClick={ doNothing } children='Do Nothing' />
      }
      { blowUp &&
        <button id='blowUp' onClick={ blowUp } children='Blow Up' />
      }
      { blowUp2 &&
        <button id='blowUp2' onClick={ blowUp2 } children='Blow Up 2' />
      }
      { blowUpDelayed &&
        <button id='blowUpDelayed' onClick={ blowUpDelayed } children='Blow Up Delayed' />
      }
      <button id='initial' onClick={ initial } children='Reset' />
      { load &&
        <button id='reload' onClick={ load } children='Reload' />
      }
    </div>
  )
}

describe('makeOrganism', () => {
  let node;
  let latestState;
  const $ = (selector) => node.querySelector(selector)
  const promiseRender = (element) => new Promise((resolve) => {
    render(element, node, resolve)  
  })

  beforeEach(() => {
    try {
    node = document.createElement('div')
    }
    catch (error) {
      console.error('ERRORS', error)
      throw error
    }
  })

  afterEach(() => {
    unmountComponentAtNode(node)
  })

  it('Sends click events', async () => {
    let changeCount = 0
    const delayWait = 20

    console.log('before makeOrganism')
    const CounterOrganism = makeOrganism(Counter, {
      initial: ({ initialCount = 0 }) => ({ count: initialCount }),
      increment: () => ({ count }) => ({ count: count + 1 }),
      decrement: () => ({ count }) => ({ count: count - 1 }),
      delayedIncrement: async () => {
        await waitMs(delayWait / 2)
        await waitMs(delayWait / 2)
        return ({ count }) => ({ count: count + 1 })
      },
      delayedIncrementGenerator: function *() {
        yield waitMs(delayWait / 2)
        yield waitMs(delayWait / 2)
        yield ({ count }) => ({ count: count + 1 })
      },
      doNothing: () => {},
      blowUp: () => {
        throw new Error('Whoops')
      },
      blowUp2: () => (prevState) => {
        throw new Error('Whoops 2')
      },
      blowUpDelayed: async () => {
        await waitMs(delayWait)
        throw new Error('Whoops Delayed')
      }
    }, {
      onChange(state) {
        latestState = state
        changeCount++
      }
    })
    console.log('after makeOrganism')

    await promiseRender(<CounterOrganism initialCount={ 2 } />)
    expect(node.innerHTML).toContain('2')
    console.log(node.innerHTML)

    // Click increment
    ReactTestUtils.Simulate.click($('#increment'))
    expect(node.innerHTML).toContain('3')

    // Click decrement
    ReactTestUtils.Simulate.click($('#decrement'))
    expect(node.innerHTML).toContain('2')
    expect(changeCount).toBe(2)

    // Click delayedIncrement
    ReactTestUtils.Simulate.click($('#delayedIncrement'))
    await waitMs(delayWait + 5)
    expect(node.innerHTML).toContain('3')
    expect(changeCount).toBe(3)

    // Click delayedIncrementGenerator
    ReactTestUtils.Simulate.click($('#delayedIncrementGenerator'))
    await waitMs(delayWait / 2)
    await nextFrame()
    await waitMs(delayWait / 2)
    await nextFrame()
    await waitMs(5)
    await nextFrame()
    expect(node.innerHTML).toContain('4')
    expect(changeCount).toBe(4)

    ReactTestUtils.Simulate.click($('#doNothing'))
    expect(node.innerHTML).toContain('4')
    expect(changeCount).toBe(4)

    // Click blowUp
    ReactTestUtils.Simulate.click($('#blowUp'))
    expect(latestState.handlerError).toExist()
    expect(latestState.handlerError.message).toBe('Whoops')

    // Click blowUp2
    ReactTestUtils.Simulate.click($('#blowUp2'))
    expect(latestState.handlerError).toExist()
    expect(latestState.handlerError.message).toBe('Whoops 2')

    // Click blowUpDelayed
    ReactTestUtils.Simulate.click($('#blowUpDelayed'))
    await waitMs(delayWait + 5)
    expect(latestState.handlerError).toExist()
    expect(latestState.handlerError.message).toBe('Whoops Delayed')

    expect(changeCount).toBe(7)
  })

  it('Calls load handler', async () => {
    let changeCount = 0
    let latestState;
    const loadWait = 35

    const CounterOrganism = makeOrganism(Counter, {
      initial: ({ initialCount = 0 }) => ({ count: initialCount }),
      load: async ({ loadedCount }, prevProps) => {
        if (!prevProps || loadedCount !== prevProps.loadedCount) {
          await waitMs(loadWait)
          const count = loadedCount * 2 // Multiply to be sure we are using this loaded value
          if (Number.isNaN(count)) {
            throw new Error('Loaded count is invalid')
          }
          return { count }
        }
      },
      increment: () => ({ count }) => ({ count: count + 1 }),
      decrement: () => ({ count }) => ({ count: count - 1 })
    }, {
      onChange(state) {
        latestState = state
        changeCount++
      }
    })

    await promiseRender(<CounterOrganism initialCount={ 2 } loadedCount={ 7 } />)
    expect(node.innerHTML).toContain('2')

    // Click increment
    ReactTestUtils.Simulate.click($('#increment'))
    expect(node.innerHTML).toContain('3')

    // Click decrement
    ReactTestUtils.Simulate.click($('#decrement'))
    expect(node.innerHTML).toContain('2')

    expect(changeCount).toBe(2)

    await waitMs(loadWait + 5)
    expect(node.innerHTML).toContain(14)
    expect(changeCount).toBe(3)

    await promiseRender(<CounterOrganism initialCount={ 22 } loadedCount={ 7 } />)
    expect(node.innerHTML).toContain(14)
    expect(changeCount).toBe(3)

    await promiseRender(<CounterOrganism initialCount={ 22 } loadedCount={ 9 } />)
    await waitMs(loadWait + 5)
    expect(node.innerHTML).toContain(18)
    expect(changeCount).toBe(4)

    // Click reload
    ReactTestUtils.Simulate.click($('#reload'))
    await waitMs(loadWait + 5)
    expect(node.innerHTML).toContain(18)
    expect(changeCount).toBe(5)

    // Load error
    promiseRender(<CounterOrganism initialCount={ 22 } loadedCount='Not a number' />)
    await waitMs(loadWait + 5)
    expect(latestState.loadError).toExist()
    expect(changeCount).toBe(6)
  })

  it('Supports getState/setState props', async () => {
    let changeCount = 0
    const delayWait = 20

    const CounterOrganism = makeOrganism(Counter, {
      initial: ({ initialCount = 0 }) => ({ count: initialCount }),
      increment: () => ({ count }) => ({ count: count + 1 }),
      decrement: () => ({ count }) => ({ count: count - 1 }),
      delayedIncrement: async () => {
        await waitMs(delayWait / 2)
        await waitMs(delayWait / 2)
        return ({ count }) => ({ count: count + 1 })
      },
      delayedIncrementGenerator: function *() {
        yield waitMs(delayWait / 2)
        yield waitMs(delayWait / 2)
        yield ({ count }) => ({ count: count + 1 })
      },
      doNothing: () => {},
      blowUp: () => {
        throw new Error('Whoops')
      },
      blowUp2: () => (prevState) => {
        throw new Error('Whoops 2')
      },
      blowUpDelayed: async () => {
        await waitMs(delayWait)
        throw new Error('Whoops Delayed')
      }
    }, {
      onChange(state) {
        latestState = state
        changeCount++
      }
    })

    class Wrapper extends Component {
      state = CounterOrganism.initialStateForProps(this.props)

      render() {
        return <CounterOrganism
          getState={ () => this.state }
          setState={ this.setState.bind(this) }
        />
      }
    }

    await promiseRender(<Wrapper initialCount={ 2 } />)
    expect(node.innerHTML).toContain('2')
    console.log(node.innerHTML)

    // Click increment
    ReactTestUtils.Simulate.click($('#increment'))
    expect(node.innerHTML).toContain('3')

    // Click decrement
    ReactTestUtils.Simulate.click($('#decrement'))
    expect(node.innerHTML).toContain('2')
    expect(changeCount).toBe(2)

    // Click delayedIncrement
    ReactTestUtils.Simulate.click($('#delayedIncrement'))
    await waitMs(delayWait + 5)
    expect(node.innerHTML).toContain('3')
    expect(changeCount).toBe(3)
  })

})


================================================
FILE: tests/multi-test.js
================================================
import expect from 'expect'
import React from 'react'
import {render, unmountComponentAtNode} from 'react-dom'
import ReactTestUtils from 'react-dom/test-utils'

import makeMulticelledOrganism from 'src/multi'

const waitMs = duration => new Promise(resolve => setTimeout(resolve, duration))

const nextFrame = () => new Promise((resolve) => {
  window.requestAnimationFrame(resolve)
})

function Counter({
  id,
  count,
  title,
  handlers: {
    increment,
    decrement,
    delayedIncrement,
    delayedIncrementGenerator,
    doNothing,
    blowUp,
    blowUp2,
    blowUpDelayed,
    initial,
    load,
    changeTitle,
    uppercaseTitle,
    makeTitleHeading
  }
}) {
  return (
    <div>
      <h2 id={`${id}-title`}>{ title }</h2>
      <button id={`${id}-decrement`} onClick={ decrement } children='−' />
      <span id={`${id}-currentCount`}>{ id }: { count }</span>
      <button id={`${id}-increment`} onClick={ increment } children='+' />
      { delayedIncrement &&
        <button id={`${id}-delayedIncrement`} onClick={ delayedIncrement } children='+' />
      }
      { delayedIncrementGenerator &&
        <button id={`${id}-delayedIncrementGenerator`} onClick={ delayedIncrementGenerator } children='+' />
      }
      { doNothing &&
        <button id={`${id}-doNothing`} onClick={ doNothing } children='Do Nothing' />
      }
      { blowUp &&
        <button id={`${id}-blowUp`} onClick={ blowUp } children='Blow Up' />
      }
      { blowUp2 &&
        <button id={`${id}-blowUp2`} onClick={ blowUp2 } children='Blow Up 2' />
      }
      { blowUpDelayed &&
        <button id={`${id}-blowUpDelayed`} onClick={ blowUpDelayed } children='Blow Up Delayed' />
      }
      <button id={`${id}-initial`} onClick={ initial } children='Reset' />
      { load &&
        <button id='reload' onClick={ load } children='Reload' />
      }

      <input id={`${id}-changeTitle`} onChange={ changeTitle } />
      <button id={`${id}-uppercaseTitle`} onClick={ uppercaseTitle } children='uppercaseTitle' />
      <button id={`${id}-makeTitleHeading`} onClick={ makeTitleHeading } children='makeTitleHeading' />
    </div>
  )
}

const counterModel = {
  initial: ({ initialCount = 0 }) => ({ count: initialCount }),
  increment: () => ({ count }) => ({ count: count + 1 }),
  decrement: () => ({ count }) => ({ count: count - 1 }),
  delayedIncrement: async () => {
    await waitMs(delayWait)
    return ({ count }) => ({ count: count + 1 })
  },
  delayedIncrementGenerator: function *() {
    yield waitMs(delayWait / 2)
    yield waitMs(delayWait / 2)
    yield ({ count }) => ({ count: count + 1 })
  },
  doNothing: () => {},
  blowUp: () => {
    throw new Error('Whoops')
  },
  blowUp2: () => (prevState) => {
    throw new Error('Whoops 2')
  },
  blowUpDelayed: async () => {
    await waitMs(delayWait)
    throw new Error('Whoops Delayed')
  }
}

const loadWait = 35
const delayWait = 20
const counterLoadModel = {
  initial: ({ initialCount = 0 }) => ({
    count: initialCount,
    title: 'Counter'
  }),
  load: async ({ loadedCount }, prevProps) => {
    if (!prevProps || loadedCount !== prevProps.loadedCount) {
      await waitMs(loadWait)
      const count = loadedCount * 2 // Multiply to be sure we are using this loaded value
      if (Number.isNaN(count)) {
        throw new Error('Loaded count is invalid')
      }
      return { count }
    }
  },
  increment: () => ({ count }) => ({ count: count + 1 }),
  decrement: () => ({ count }) => ({ count: count - 1 }),
  changeTitle: (props, { value }) => ({ title: value }),
  uppercaseTitle: () => ({ title }) => ({ title: title.toUpperCase() }),
  makeTitleHeading: () => ({ title: 'Heading' })
}

describe('makeMulticelledOrganism', () => {
  let node;
  const $ = (selector) => node.querySelector(selector)
  let promiseRender;

  beforeEach(() => {
    node = document.createElement('div')
    promiseRender = (element) => new Promise((resolve) => {
      render(element, node, resolve)
    })
  })

  afterEach(() => {
    unmountComponentAtNode(node)
  })

  it('Sends click events', async () => {
    let changeCount = 0
    let latestState;

    const Organism = makeMulticelledOrganism(({
      cells: {
        counterA,
        counterB
      }
    }) => (
      <div>
        <Counter id='a' { ...counterA } />
        <Counter id='b' { ...counterB } />
      </div>
    ), {
      counterA: counterModel,
      counterB: counterModel
    }, {
      onChange(state) {
        latestState = state
        changeCount++
      }
    })

    await promiseRender(<Organism initialCount={ 2 } />)
    const $aCurrentCount = $('#a-currentCount')
    const $bCurrentCount = $('#b-currentCount')
    expect($aCurrentCount.textContent).toBe('a: 2')
    expect($bCurrentCount.textContent).toBe('b: 2')

    // Click a increment
    ReactTestUtils.Simulate.click($('#a-increment'))
    expect($aCurrentCount.textContent).toBe('a: 3')
    expect($bCurrentCount.textContent).toBe('b: 2')

    // Click a decrement
    ReactTestUtils.Simulate.click($('#a-decrement'))
    expect($aCurrentCount.textContent).toBe('a: 2')
    expect($bCurrentCount.textContent).toBe('b: 2')

    // Click b decrement
    ReactTestUtils.Simulate.click($('#b-decrement'))
    expect($aCurrentCount.textContent).toBe('a: 2')
    expect($bCurrentCount.textContent).toBe('b: 1')
    expect(changeCount).toBe(3)

    // Click delayedIncrement
    ReactTestUtils.Simulate.click($('#a-delayedIncrement'))
    await waitMs(delayWait + 5)
    expect($aCurrentCount.textContent).toBe('a: 3')
    expect($bCurrentCount.textContent).toBe('b: 1')
    expect(changeCount).toBe(4)

    // Click delayedIncrementGenerator
    ReactTestUtils.Simulate.click($('#a-delayedIncrementGenerator'))
    await waitMs(delayWait / 2)
    await nextFrame()
    await waitMs(delayWait / 2)
    await nextFrame()
    await waitMs(5)
    expect($aCurrentCount.textContent).toBe('a: 4')
    expect($bCurrentCount.textContent).toBe('b: 1')
    expect(changeCount).toBe(5)

    ReactTestUtils.Simulate.click($('#b-doNothing'))
    expect($aCurrentCount.textContent).toBe('a: 4')
    expect($bCurrentCount.textContent).toBe('b: 1')
    expect(changeCount).toBe(5)

    // Click blowUp
    ReactTestUtils.Simulate.click($('#a-blowUp'))
    expect(latestState.handlerError).toExist()
    expect(latestState.handlerError.message).toBe('Whoops')

    // Click blowUp2
    ReactTestUtils.Simulate.click($('#b-blowUp2'))
    expect(latestState.handlerError).toExist()
    expect(latestState.handlerError.message).toBe('Whoops 2')

    // Click blowUpDelayed
    ReactTestUtils.Simulate.click($('#a-blowUpDelayed'))
    await waitMs(delayWait + 5)
    expect(latestState.handlerError).toExist()
    expect(latestState.handlerError.message).toBe('Whoops Delayed')
  })

  it('Calls load handler', async () => {
    let changeCount = 0
    let latestState;
    const loadWait = 35

    const Organism = makeMulticelledOrganism(({
      cells: {
        counterA,
        counterB
      }
    }) => (
      <div>
        <Counter id='a' { ...counterA } />
        <Counter id='b' { ...counterB } />
      </div>
    ), {
      counterA: counterLoadModel,
      counterB: counterLoadModel
    }, {
      onChange(state) {
        latestState = state
        changeCount++
      }
    })

    await promiseRender(<Organism initialCount={ 2 } loadedCount={ 7 } />)
    const $aCurrentCount = $('#a-currentCount')
    const $aTitle = $('#a-title')

    expect($aCurrentCount.textContent).toContain('2')

    // Click increment
    ReactTestUtils.Simulate.click($('#a-increment'))
    expect($aCurrentCount.textContent).toContain('3')

    // Click decrement
    ReactTestUtils.Simulate.click($('#a-decrement'))
    expect($aCurrentCount.textContent).toContain('2')
    expect(changeCount).toBe(2)

    await waitMs(loadWait + 5)
    // Loaded
    expect($aCurrentCount.textContent).toContain('14')
    expect(changeCount).toBe(4) // Both cells loaded, so change count increases by 2

    await promiseRender(<Organism initialCount={ 22 } loadedCount={ 7 } />)
    // Takes some time to load, so shouldn’t have changed yet
    expect($aCurrentCount.textContent).toContain('14')
    expect(changeCount).toBe(4)

    await promiseRender(<Organism initialCount={ 22 } loadedCount={ 9 } />)
    await waitMs(loadWait + 5)
    // Loaded from new props
    expect($aCurrentCount.textContent).toContain('18')
    expect(changeCount).toBe(6)

    expect($aTitle.textContent).toBe('Counter')

    ReactTestUtils.Simulate.click($('#a-uppercaseTitle'))
    expect($aTitle.textContent).toBe('COUNTER')
    expect(changeCount).toBe(7)

    ReactTestUtils.Simulate.click($('#a-makeTitleHeading'))
    expect($aTitle.textContent).toBe('Heading')
    expect(changeCount).toBe(8)

    // Click reload
    ReactTestUtils.Simulate.click($('#reload'))
    await waitMs(loadWait + 5)
    expect($aCurrentCount.textContent).toContain('18')
    expect(changeCount).toBe(9)

    // Load error
    await promiseRender(<Organism initialCount={ 22 } loadedCount='Not a number' />)
    await waitMs(loadWait + 5)
    expect(latestState.loadError).toExist()
    expect(changeCount).toBe(11)
  })

  it('getInitialProps()', async () => {
    let changeCount = 0
    let latestState;
    const loadWait = 35

    const Organism = makeMulticelledOrganism(({
      cells: {
        counterA,
        counterB
      }
    }) => (
      <div>
        <Counter id='a' { ...counterA } />
        <Counter id='b' { ...counterB } />
      </div>
    ), {
      counterA: counterLoadModel,
      counterB: counterLoadModel
    }, {
      onChange(state) {
        latestState = state
        changeCount++
      }
    })

    const element = <Organism initialCount={ 2 } loadedCount={ 7 } />
    const instance = new Organism(element.props)

    const initialProps = await instance.getInitialProps(element.props)
    expect(initialProps).toEqual({
      counterA: { count: 14 },
      counterB: { count: 14 }
    })
  })

})


================================================
FILE: umd/react-organism.js
================================================
/*!
 * react-organism v0.3.6
 * MIT Licensed
 */
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory(require("react"));
	else if(typeof define === 'function' && define.amd)
		define(["react"], factory);
	else if(typeof exports === 'object')
		exports["makeOrganism"] = factory(require("react"));
	else
		root["makeOrganism"] = factory(root["React"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_2__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

module.exports = __webpack_require__(1);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_react__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_awareness__ = __webpack_require__(3);
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }




// Returns a new stateful component, given the specified state handlers and a pure component to render with
/* harmony default export */ __webpack_exports__["default"] = (function (Pure, handlersIn) {
  var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
      onChange = _ref.onChange,
      adjustArgs = _ref.adjustArgs;

  return function (_PureComponent) {
    _inherits(Organism, _PureComponent);

    function Organism() {
      var _temp, _this, _ret;

      _classCallCheck(this, Organism);

      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }

      return _ret = (_temp = (_this = _possibleConstructorReturn(this, _PureComponent.call.apply(_PureComponent, [this].concat(args))), _this), _this.alterState = function (stateChanger) {
        // Can either be a plain object or a callback to transform the existing state
        (_this.props.setState || _this.setState).call(_this, stateChanger,
        // Call onChange once updated with current version of state
        onChange ? function () {
          onChange(_this.currentState);
        } : undefined);
      }, _this.awareness = Object(__WEBPACK_IMPORTED_MODULE_1_awareness__["a" /* default */])(_this.alterState, handlersIn, {
        getProps: function getProps() {
          return _this.props;
        },
        adjustArgs: adjustArgs
      }), _this.state = _this.awareness.state, _temp), _possibleConstructorReturn(_this, _ret);
    }

    Organism.initialStateForProps = function initialStateForProps(props) {
      return handlersIn.initial(props);
    };

    Organism.prototype.componentDidMount = function componentDidMount() {
      this.awareness.loadAsync(this.props, null, this.currentState);
    };

    Organism.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
      this.awareness.loadAsync(nextProps, this.props, this.currentState);
    };

    Organism.prototype.render = function render() {
      // Render the pure component, passing both props and state, plus handlers bundled together
      return __WEBPACK_IMPORTED_MODULE_0_react___default.a.createElement(Pure, _extends({}, this.props, this.currentState, { handlers: this.awareness.handlers }));
    };

    _createClass(Organism, [{
      key: 'currentState',
      get: function get() {
        return this.props.getState ? this.props.getState() : this.state;
      }
    }]);

    return Organism;
  }(__WEBPACK_IMPORTED_MODULE_0_react__["PureComponent"]);
});

/***/ }),
/* 2 */
/***/ (function(module, exports) {

module.exports = __WEBPACK_EXTERNAL_MODULE_2__;

/***/ }),
/* 3 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* unused harmony export callHandler */
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

var nextFrame = function nextFrame() {
  return new Promise(function (resolve) {
    window.requestAnimationFrame(resolve);
  });
};

function stateChangerCatchingError(stateChanger, transformError) {
  return function (prevState, props) {
    // Check if stateChanger is a function
    if ((typeof stateChanger === 'undefined' ? 'undefined' : _typeof(stateChanger)) === _typeof(stateChanger.call)) {
      try {
        // Call state changer
        return stateChanger(prevState, props);
      }
      // State changer may throw
      catch (error) {
        // Store error in state
        return transformError(error);
      }
    }
    // Else just an object with changes
    else {
        return stateChanger;
      }
  };
}

function processStateChanger(changeState, stateChanger, storeError) {
  if (!stateChanger) {
    return;
  }

  // Check if thenable (i.e. a Promise)
  if (_typeof(stateChanger.then) === _typeof(Object.assign)) {
    return stateChanger.then(function (stateChanger) {
      return stateChanger && changeState(stateChanger);
    }).catch(storeError);
  }
  // Check if iterator
  else if (_typeof(stateChanger.next) === _typeof(Object.assign)) {
      return processIterator(changeState, stateChanger, storeError);
    }
    // Otherwise, change state immediately
    // Required for things like <textarea> onChange to keep cursor in correct position
    else {
        changeState(stateChanger);
      }
}

function processIterator(changeState, iterator, storeError, previousValue) {
  return Promise.resolve(processStateChanger(changeState, previousValue, storeError)) // Process the previous changer
  .then(function () {
    return nextFrame();
  }) // Wait for next frame
  .then(function () {
    var result = iterator.next(); // Get the next step from the iterator
    if (result.done) {
      // No more iterations remaining
      return processStateChanger(changeState, result.value, storeError); // Process the changer
    } else {
      return processIterator(changeState, iterator, storeError, result.value); // Process the iterator’s following steps
    }
  });
}

function callHandler(handler, transformError, args, alterState) {
  if ((typeof transformError === 'undefined' ? 'undefined' : _typeof(transformError)) === _typeof('')) {
    var errorKey = transformError;
    transformError = function transformError(error) {
      var _ref;

      return _ref = {}, _ref[errorKey] = error, _ref;
    };
  }

  var storeError = function storeError(error) {
    alterState(function () {
      return transformError(error);
    });
  };
  // Call handler function, props first, then rest of args
  try {
    var changeState = function changeState(stateChanger) {
      alterState(stateChangerCatchingError(stateChanger, transformError));
    };
    var result = handler.apply(null, args);
    // Can return multiple state changers, ensure array, and then loop through
    [].concat(result).forEach(function (stateChanger) {
      processStateChanger(changeState, stateChanger, storeError);
    });
  }
  // Catch error within handler’s (first) function
  catch (error) {
    storeError(error);
  }
}

var defaultTransformErrorForKey = function defaultTransformErrorForKey(key) {
  return function (error) {
    var _ref2;

    var stateKey = (key === 'load' ? 'load' : 'handler') + 'Error';
    return _ref2 = {}, _ref2[stateKey] = error, _ref2;
  };
};

// Returns a new stateful component, given the specified state handlers and a pure component to render with
/* harmony default export */ __webpack_exports__["a"] = (function (alterState, handlersIn) {
  var _ref3 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
      _ref3$getProps = _ref3.getProps,
      getProps = _ref3$getProps === undefined ? function () {
    return {};
  } : _ref3$getProps,
      _ref3$transformErrorF = _ref3.transformErrorForKey,
      transformErrorForKey = _ref3$transformErrorF === undefined ? defaultTransformErrorForKey : _ref3$transformErrorF,
      adjustArgs = _ref3.adjustArgs;

  var state = Object.assign({
    loadError: null,
    handlerError: null
  }, handlersIn.initial(getProps()));

  // Uses `load` handler, if present, to asynchronously load initial state
  function loadAsync() {
    if (handlersIn.load) {
      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }

      callHandler(handlersIn.load, transformErrorForKey('load'), args, alterState);
    }
  }

  var handlers = Object.keys(handlersIn).reduce(function (out, key) {
    // Special case for `load` handler to reload fresh
    if (key === 'load') {
      out.load = function () {
        loadAsync(getProps(), null);
      };
      return out;
    }

    out[key] = function () {
      for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }

      if (adjustArgs) {
        args = adjustArgs(args);
      }

      callHandler(handlersIn[key], transformErrorForKey(key), [Object.assign({}, getProps(), { handlers: handlers })].concat(args), alterState);
    };
    return out;
  }, {});

  return {
    state: state,
    loadAsync: loadAsync,
    handlers: handlers
  };
});

/***/ })
/******/ ]);
});
Download .txt
gitextract_z5_js_hy/

├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── demo/
│   └── src/
│       ├── components/
│       │   ├── Calculator.js
│       │   ├── Counter.js
│       │   ├── FriendsList.js
│       │   ├── Items.js
│       │   ├── Notifications.js
│       │   ├── PhotosList.js
│       │   └── Row.js
│       ├── index.js
│       ├── organisms/
│       │   ├── Calculator.js
│       │   ├── Counter.js
│       │   ├── Counter2.js
│       │   ├── Counter3.js
│       │   ├── Counter4.js
│       │   ├── Items.js
│       │   ├── ItemsChoice.js
│       │   └── Social.js
│       └── state/
│           ├── counter.js
│           ├── friends.js
│           ├── photos.js
│           ├── placeholderAPI.js
│           └── selection.js
├── nwb.config.js
├── package.json
├── packages/
│   └── create-react-organism/
│       ├── .gitignore
│       ├── README.md
│       ├── bin/
│       │   └── create-react-organism.js
│       └── package.json
├── src/
│   ├── adjustArgs/
│   │   └── extractFromDOM.js
│   ├── index.d.ts
│   ├── index.js
│   ├── multi.js
│   └── nextFrame.js
├── tests/
│   ├── .eslintrc
│   ├── extractFromDOM-test.js
│   ├── index-test.js
│   └── multi-test.js
└── umd/
    └── react-organism.js
Download .txt
SYMBOL INDEX (47 symbols across 19 files)

FILE: demo/src/components/Calculator.js
  function Calculator (line 3) | function Calculator({

FILE: demo/src/components/Counter.js
  function Counter (line 3) | function Counter({

FILE: demo/src/components/FriendsList.js
  function FriendsList (line 3) | function FriendsList({

FILE: demo/src/components/Items.js
  function Counter (line 3) | function Counter({

FILE: demo/src/components/Notifications.js
  function Notifications (line 3) | function Notifications({

FILE: demo/src/components/PhotosList.js
  function PhotosList (line 3) | function PhotosList({

FILE: demo/src/components/Row.js
  function Row (line 8) | function Row({

FILE: demo/src/index.js
  class Demo (line 13) | class Demo extends Component {
    method render (line 14) | render() {

FILE: demo/src/organisms/Counter3.js
  method onChange (line 20) | onChange(state) {

FILE: demo/src/organisms/Social.js
  function Social (line 24) | function Social({

FILE: packages/create-react-organism/bin/create-react-organism.js
  function makeStateJS (line 65) | function makeStateJS(componentName) {
  function makeComponentJS (line 79) | function makeComponentJS(componentName) {
  function makeIndexJS (line 98) | function makeIndexJS(componentName) {

FILE: src/adjustArgs/extractFromDOM.js
  function extractFromDOM (line 3) | function extractFromDOM(args) {

FILE: src/index.d.ts
  type ReceiverProps (line 4) | interface ReceiverProps<HandlersOut> {

FILE: src/index.js
  method initialStateForProps (line 13) | static initialStateForProps(props) {
  method currentState (line 17) | get currentState() {
  method componentDidMount (line 38) | componentDidMount() {
  method componentDidUpdate (line 42) | componentDidUpdate(prevProps, prevState) {
  method render (line 46) | render() {

FILE: src/multi.js
  function cellStateChangerCatchingError (line 5) | function cellStateChangerCatchingError(cellKey, stateChanger, errorKey) {
  function processStateChanger (line 30) | function processStateChanger(changeState, stateChanger, storeError) {
  function processIterator (line 53) | function processIterator(changeState, iterator, storeError, previousValu...
  function makeMultiOrganism (line 67) | function makeMultiOrganism(

FILE: tests/extractFromDOM-test.js
  function PhotosList (line 11) | function PhotosList({

FILE: tests/index-test.js
  function Counter (line 15) | function Counter({
  method onChange (line 114) | onChange(state) {
  method onChange (line 194) | onChange(state) {
  method onChange (line 269) | onChange(state) {
  class Wrapper (line 275) | class Wrapper extends Component {
    method render (line 278) | render() {

FILE: tests/multi-test.js
  function Counter (line 14) | function Counter({
  method onChange (line 154) | onChange(state) {
  method onChange (line 241) | onChange(state) {
  method onChange (line 320) | onChange(state) {

FILE: umd/react-organism.js
  function __webpack_require__ (line 20) | function __webpack_require__(moduleId) {
  function defineProperties (line 98) | function defineProperties(target, props) { for (var i = 0; i < props.len...
  function _classCallCheck (line 100) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
  function _possibleConstructorReturn (line 102) | function _possibleConstructorReturn(self, call) { if (!self) { throw new...
  function _inherits (line 104) | function _inherits(subClass, superClass) { if (typeof superClass !== "fu...
  function Organism (line 118) | function Organism() {
  function stateChangerCatchingError (line 190) | function stateChangerCatchingError(stateChanger, transformError) {
  function processStateChanger (line 211) | function processStateChanger(changeState, stateChanger, storeError) {
  function processIterator (line 233) | function processIterator(changeState, iterator, storeError, previousValu...
  function callHandler (line 249) | function callHandler(handler, transformError, args, alterState) {
  function loadAsync (line 307) | function loadAsync() {
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (84K chars).
[
  {
    "path": ".gitignore",
    "chars": 59,
    "preview": "/coverage\n/demo/dist\n/es\n/lib\n/node_modules\nnpm-debug.log*\n"
  },
  {
    "path": ".travis.yml",
    "chars": 314,
    "preview": "sudo: false\n\nlanguage: node_js\nnode_js:\n  - 4\n  - 6\n  - 7\n  - 8\n\nbefore_install:\n  - npm install codecov.io coveralls\n\na"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 746,
    "preview": "## Prerequisites\n\n[Node.js](http://nodejs.org/) >= v4 must be installed.\n\n## Installation\n\n- Running `npm install` in th"
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2017 Patrick Smith\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 13662,
    "preview": "# React Organism\n\n[![Travis][build-badge]][build]\n[![npm package][npm-badge]][npm]\n[![Coveralls][coveralls-badge]][cover"
  },
  {
    "path": "demo/src/components/Calculator.js",
    "chars": 436,
    "preview": "import React, { Component } from 'react'\n\nexport default function Calculator({\n  value,\n  handlers: {\n    changeValue,\n "
  },
  {
    "path": "demo/src/components/Counter.js",
    "chars": 395,
    "preview": "import React, { Component } from 'react'\n\nexport default function Counter({\n  count,\n  handlers: {\n    increment,\n    de"
  },
  {
    "path": "demo/src/components/FriendsList.js",
    "chars": 764,
    "preview": "import React, { Component } from 'react'\n\nexport default function FriendsList({\n  friendsList,\n  selectedIndex,\n  onSele"
  },
  {
    "path": "demo/src/components/Items.js",
    "chars": 378,
    "preview": "import React, { Component } from 'react'\n\nexport default function Counter({\n  items,\n  collectionName,\n  handlers: {\n   "
  },
  {
    "path": "demo/src/components/Notifications.js",
    "chars": 272,
    "preview": "import React from 'react'\n\nexport default function Notifications({\n  friends: {\n    friendsList\n  },\n  photos: {\n    pho"
  },
  {
    "path": "demo/src/components/PhotosList.js",
    "chars": 781,
    "preview": "import React, { Component } from 'react'\n\nexport default function PhotosList({\n  photosList,\n  handlers: {\n    addRandom"
  },
  {
    "path": "demo/src/components/Row.js",
    "chars": 200,
    "preview": "import React from 'react'\n\nconst style = {\n  display: 'flex',\n  flexDirection: 'row'\n}\n\nexport default function Row({\n  "
  },
  {
    "path": "demo/src/index.js",
    "chars": 2220,
    "preview": "import React, { Component } from 'react'\nimport { render } from 'react-dom'\n\nimport CounterOrganism from './organisms/Co"
  },
  {
    "path": "demo/src/organisms/Calculator.js",
    "chars": 747,
    "preview": "import makeOrganism from '../../../src'\nimport extractFromDOM from '../../../src/adjustArgs/extractFromDOM'\nimport Calcu"
  },
  {
    "path": "demo/src/organisms/Counter.js",
    "chars": 378,
    "preview": "import makeOrganism from '../../../src'\nimport * as counterState from '../state/counter'\nimport Counter from '../compone"
  },
  {
    "path": "demo/src/organisms/Counter2.js",
    "chars": 348,
    "preview": "import makeOrganism from '../../../src'\nimport Counter from '../components/Counter'\n\nexport default makeOrganism(Counter"
  },
  {
    "path": "demo/src/organisms/Counter3.js",
    "chars": 783,
    "preview": "import makeOrganism from '../../../src'\nimport Counter from '../components/Counter'\n\nconst localStorageKey = 'counter3'\n"
  },
  {
    "path": "demo/src/organisms/Counter4.js",
    "chars": 486,
    "preview": "import makeOrganism from '../../../src'\nimport Counter from '../components/Counter'\n\nexport default makeOrganism(Counter"
  },
  {
    "path": "demo/src/organisms/Items.js",
    "chars": 616,
    "preview": "import makeOrganism from '../../../src'\nimport * as loadItemsState from '../state/placeholderAPI'\nimport Items from '../"
  },
  {
    "path": "demo/src/organisms/ItemsChoice.js",
    "chars": 719,
    "preview": "import React from 'react'\nimport makeOrganism from '../../../src'\nimport ItemsOrganism from './Items'\n\nexport default ma"
  },
  {
    "path": "demo/src/organisms/Social.js",
    "chars": 1341,
    "preview": "import React from 'react'\nimport makeMultiCelledOrganism from '../../../src/multi'\nimport extractFromDOM from '../../../"
  },
  {
    "path": "demo/src/state/counter.js",
    "chars": 182,
    "preview": "export const initial = () => ({ count: 0 })\n\nexport const increment = () => ({ count }) => ({ count: count + 1 })\nexport"
  },
  {
    "path": "demo/src/state/friends.js",
    "chars": 838,
    "preview": "export const initial = () => ({\n  friendsList: []\n})\n\nconst convertUserToFriend = (user) => ({\n  name: `${user.first} ${"
  },
  {
    "path": "demo/src/state/photos.js",
    "chars": 649,
    "preview": "export const initial = () => ({\n  photosList: []\n})\n\nconst fetchRandomPhotoURL = () =>\n  fetch(\n    'https://source.unsp"
  },
  {
    "path": "demo/src/state/placeholderAPI.js",
    "chars": 352,
    "preview": "const baseURL = 'https://jsonplaceholder.typicode.com'\nconst fetchAPI = (path) => fetch(baseURL + path).then(r => r.json"
  },
  {
    "path": "demo/src/state/selection.js",
    "chars": 270,
    "preview": "export const initial = () => ({\n  selectedFriendIndex: null,\n  selectedPhotoIndex: null\n})\n\nexport const selectFriendAtI"
  },
  {
    "path": "nwb.config.js",
    "chars": 218,
    "preview": "module.exports = {\n  type: 'react-component',\n  webpack: {\n    hoisting: true\n  },\n  npm: {\n    esModules: true,\n    umd"
  },
  {
    "path": "package.json",
    "chars": 1083,
    "preview": "{\n  \"name\": \"react-organism\",\n  \"version\": \"0.3.8\",\n  \"description\": \"Separate React state in a dead simple functional w"
  },
  {
    "path": "packages/create-react-organism/.gitignore",
    "chars": 12,
    "preview": "node_modules"
  },
  {
    "path": "packages/create-react-organism/README.md",
    "chars": 1095,
    "preview": "# create-react-organism\n\nEasily create [react-organism](https://github.com/RoyalIcing/react-organism) smart components.\n"
  },
  {
    "path": "packages/create-react-organism/bin/create-react-organism.js",
    "chars": 3074,
    "preview": "#!/usr/bin/env node\n\nconst Path = require('path')\nconst FS = require('fs')\nconst Spawn = require('cross-spawn')\nconst { "
  },
  {
    "path": "packages/create-react-organism/package.json",
    "chars": 513,
    "preview": "{\n  \"name\": \"create-react-organism\",\n  \"version\": \"0.2.0\",\n  \"description\": \"Tool to easily create react-organism smart "
  },
  {
    "path": "src/adjustArgs/extractFromDOM.js",
    "chars": 329,
    "preview": "import extractValuesFromDOMEvent from 'awareness/lib/extractValuesFromDOMEvent'\n\nexport default function extractFromDOM("
  },
  {
    "path": "src/index.d.ts",
    "chars": 548,
    "preview": "declare module 'react-organism' {\n  import React from 'react'\n\n  export interface ReceiverProps<HandlersOut> {\n    handl"
  },
  {
    "path": "src/index.js",
    "chars": 1410,
    "preview": "import React, { PureComponent } from 'react'\nimport makeAwareness from 'awareness'\n\n// Returns a new stateful component,"
  },
  {
    "path": "src/multi.js",
    "chars": 5849,
    "preview": "import React from 'react'\nimport makeAwareness from 'awareness'\nimport nextFrame from './nextFrame'\n\nfunction cellStateC"
  },
  {
    "path": "src/nextFrame.js",
    "chars": 91,
    "preview": "export default () => new Promise((resolve) => {\n  window.requestAnimationFrame(resolve)\n})\n"
  },
  {
    "path": "tests/.eslintrc",
    "chars": 37,
    "preview": "{\n  \"env\": {\n    \"mocha\": true\n  }\n}\n"
  },
  {
    "path": "tests/extractFromDOM-test.js",
    "chars": 2895,
    "preview": "import expect from 'expect'\nimport React from 'react'\nimport {render, unmountComponentAtNode} from 'react-dom'\nimport Re"
  },
  {
    "path": "tests/index-test.js",
    "chars": 8979,
    "preview": "import expect from 'expect'\nimport React, { Component } from 'react'\nimport {render, unmountComponentAtNode} from 'react"
  },
  {
    "path": "tests/multi-test.js",
    "chars": 10004,
    "preview": "import expect from 'expect'\nimport React from 'react'\nimport {render, unmountComponentAtNode} from 'react-dom'\nimport Re"
  },
  {
    "path": "umd/react-organism.js",
    "chars": 13607,
    "preview": "/*!\n * react-organism v0.3.6\n * MIT Licensed\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof "
  }
]

About this extraction

This page contains the full source code of the RoyalIcing/react-organism GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (76.9 KB), approximately 20.7k tokens, and a symbol index with 47 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!