{"id":14384775,"url":"https://github.com/RoyalIcing/react-organism","last_synced_at":"2025-08-23T18:30:52.847Z","repository":{"id":21841795,"uuid":"93404179","full_name":"RoyalIcing/react-organism","owner":"RoyalIcing","description":"Dead simple React state management to bring pure components alive","archived":false,"fork":false,"pushed_at":"2022-12-08T17:15:43.000Z","size":1682,"stargazers_count":223,"open_issues_count":29,"forks_count":9,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-04-13T23:55:08.756Z","etag":null,"topics":["async","functional-programming","promise","react","state","state-management"],"latest_commit_sha":null,"homepage":"https://react-organism.now.sh/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RoyalIcing.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-06-05T13:00:15.000Z","updated_at":"2024-02-08T19:42:25.000Z","dependencies_parsed_at":"2023-01-11T21:22:35.265Z","dependency_job_id":null,"html_url":"https://github.com/RoyalIcing/react-organism","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoyalIcing%2Freact-organism","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoyalIcing%2Freact-organism/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoyalIcing%2Freact-organism/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoyalIcing%2Freact-organism/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RoyalIcing","download_url":"https://codeload.github.com/RoyalIcing/react-organism/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":217384771,"owners_count":16168802,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","functional-programming","promise","react","state","state-management"],"created_at":"2024-08-28T18:01:39.646Z","updated_at":"2024-08-28T18:05:25.052Z","avatar_url":"https://github.com/RoyalIcing.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# React Organism\n\n[![Travis][build-badge]][build]\n[![npm package][npm-badge]][npm]\n[![Coveralls][coveralls-badge]][coveralls]\n\n**Dead simple React/Preact state management to bring pure components alive**\n\n- Supports `async`/`await` and easy loading (e.g. `fetch()`)\n- Reload when particular props change\n- Animate using generator functions: just `yield` the new state for each frame\n- Tiny: 1.69 KB gzipped (3.49 KB uncompressed)\n- Embraces the existing functional `setState` while avoiding boilerplate (no writing `this.setState()` or `.bind` again)\n- Easy to unit test\n\n#### Table of contents\n\n- [Installation](#installation)\n- [Demos](#demos)\n- [Usage](#usage)\n  - [Basic](#basic)\n  - [Using props](#using-props)\n  - [Async \u0026 promises](#async)\n  - [Handling events](#handling-events)\n  - [Animation](#animation)\n  - [Serialization: Local storage](#serialization-local-storage)\n  - [Separate and reuse state handlers](#separate-and-reuse-state-handlers)\n  - [Multicelled organisms](#multicelled-organisms)\n- [API](#api)\n  - [`makeOrganism(PureComponent, StateFunctions, options)`](#makeorganismpurecomponent-statefunctions-options)\n  - [State functions](#state-functions)\n  - [Argument enhancers](#argument-enhancers)\n- [Why instead of Redux?](#why-instead-of-redux)\n\n## Installation\n\n```\nnpm i react-organism --save\n```\n\n## Demos\n\n- [Animated counter](https://codesandbox.io/s/2vx12v3qmn)\n- [Dynamic loading with `import()`](https://codesandbox.io/s/X6mLEwG7W)\n- [Live form error validation with Yup](https://codesandbox.io/s/4xQpKRRWx)\n- [Multicelled component — using multiple states](https://codesandbox.io/s/Yv7j1xLqM)\n- [Todo List](https://codesandbox.io/s/yME5Y3Yz)\n- [Inputs, forms, animation, fetch](https://react-organism.now.sh) · [code](https://github.com/BurntCaramel/react-organism/tree/master/demo/src)\n- [User Stories Maker](https://codesandbox.io/s/xkZ5ZONl)\n- [React Cheat Sheet](https://react-cheat.now.sh/) · [code](https://github.com/BurntCaramel/react-cheat)\n\n## Usage\n\n### Basic\n\n```js\n// organisms/Counter.js\nimport makeOrganism from 'react-organism'\nimport Counter from './components/Counter'\n\nexport default makeOrganism(Counter, {\n  initial: () =\u003e ({ count: 0 }),\n  increment: () =\u003e ({ count }) =\u003e ({ count: count + 1 }),\n  decrement: () =\u003e ({ count }) =\u003e ({ count: count - 1 })\n})\n```\n\n```js\n// components/Counter.js\nimport React, { Component } from 'react'\n\nexport default function Counter({\n  count,\n  handlers: {\n    increment,\n    decrement\n  }\n}) {\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={ decrement } children='−' /\u003e\n      \u003cspan\u003e{ count }\u003c/span\u003e\n      \u003cbutton onClick={ increment } children='+' /\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Using props\n\nThe handlers can easily use props, which are always passed as the first argument\n\n```js\n// organisms/Counter.js\nimport makeOrganism from 'react-organism'\nimport Counter from './components/Counter'\n\nexport default makeOrganism(Counter, {\n  initial: ({ initialCount = 0 }) =\u003e ({ count: initialCount }),\n  increment: ({ stride = 1 }) =\u003e ({ count }) =\u003e ({ count: count + stride }),\n  decrement: ({ stride = 1 }) =\u003e ({ count }) =\u003e ({ count: count - stride })\n})\n\n// Render passing prop: \u003cCounterOrganism stride={ 20 } /\u003e\n```\n\n### Async\n\nAsynchronous code to load from an API is easy:\n\n```js\n// components/Items.js\nimport React, { Component } from 'react'\n\nexport default function Items({\n  items,\n  collectionName,\n  handlers: {\n    load\n  }\n}) {\n  return (\n    \u003cdiv\u003e\n      {\n        !!items ? (\n          `${items.length} ${collectionName}`\n        ) : (\n          'Loading…'\n        )\n      }\n      \u003cdiv\u003e\n        \u003cbutton onClick={ load } children='Reload' /\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n```js\n// organisms/Items.js\nimport makeOrganism from 'react-organism'\nimport Items from '../components/Items'\n\nconst baseURL = 'https://jsonplaceholder.typicode.com'\nconst fetchAPI = (path) =\u003e fetch(baseURL + path).then(r =\u003e r.json())\n\nexport default makeOrganism(Items, {\n  initial: () =\u003e ({ items: null }),\n\n  load: async ({ path }, prevProps) =\u003e {\n    if (!prevProps || path !== prevProps.path) {\n      return { items: await fetchAPI(path) }\n    }\n  }\n})\n```\n\n```js\n\u003cdiv\u003e\n  \u003cItemsOrganism path='/photos' collectionName='photos' /\u003e\n  \u003cItemsOrganism path='/todos' collectionName='todo items' /\u003e\n\u003c/div\u003e\n```\n\n### Handling events\n\nHandlers can easily accept arguments such as events.\n\n```js\n// components/Calculator.js\nimport React, { Component } from 'react'\n\nexport default function Calculator({\n  value,\n  handlers: {\n    changeValue,\n    double,\n    add3,\n    initial\n  }\n}) {\n  return (\n    \u003cdiv\u003e\n      \u003cinput value={ value } onChange={ changeValue } /\u003e\n      \u003cbutton onClick={ double } children='Double' /\u003e\n      \u003cbutton onClick={ add3 } children='Add 3' /\u003e\n      \u003cbutton onClick={ initial } children='reset' /\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n```js\n// organisms/Calculator.js\nimport makeOrganism from 'react-organism'\nimport Calculator from '../components/Calculator'\n\nexport default makeOrganism(Calculator, {\n  initial: ({ initialValue = 0 }) =\u003e ({ value: initialValue }),\n  // Destructure event to get target\n  changeValue: (props, { target }) =\u003e ({ value }) =\u003e ({ value: parseInt(target.value, 10) }),\n  double: () =\u003e ({ value }) =\u003e ({ value: value * 2 }),\n  add3: () =\u003e ({ value }) =\u003e ({ value: value + 3 })\n})\n```\n\n### Animation\n\n```js\nimport makeOrganism from 'react-organism'\nimport Counter from '../components/Counter'\n\nexport default makeOrganism(Counter, {\n  initial: ({ initialCount = 0 }) =\u003e ({ count: initialCount }),\n  increment: function * ({ stride = 20 }) {\n    while (stride \u003e 0) {\n      yield ({ count }) =\u003e ({ count: count + 1 })\n      stride -= 1\n    }\n  },\n  decrement: function * ({ stride = 20 }) {\n    while (stride \u003e 0) {\n      yield ({ count }) =\u003e ({ count: count - 1 })\n      stride -= 1\n    }\n  }\n})\n```\n\n### Automatically extract from `data-` attributes and `\u003cforms\u003e`\n\nExample coming soon\n\n### Serialization: Local storage\n\n```js\n// organisms/Counter.js\nimport makeOrganism from 'react-organism'\nimport Counter from '../components/Counter'\n\nconst localStorageKey = 'counter'\n\nexport default makeOrganism(Counter, {\n  initial: ({ initialCount = 0 }) =\u003e ({ count: initialCount }),\n  load: async (props, prevProps) =\u003e {\n    if (!prevProps) {\n      // Try commenting out:\n      /* throw (new Error('Oops!')) */\n\n      // Load previously stored state, if present\n      return await JSON.parse(localStorage.getItem(localStorageKey))\n    }\n  },\n  increment: ({ stride = 1 }) =\u003e ({ count }) =\u003e ({ count: count + stride }),\n  decrement: ({ stride = 1 }) =\u003e ({ count }) =\u003e ({ count: count - stride })\n}, {\n  onChange(state) {\n    // When state changes, save in local storage\n    localStorage.setItem(localStorageKey, JSON.stringify(state))\n  }\n})\n```\n\n### Separate and reuse state handlers\n\nReact Organism supports separating state handlers and the component into their own files. This means state handlers could be reused by multiple smart components.\n\nHere’s an example of separating state:\n\n```js\n// state/counter.js\nexport const initial = () =\u003e ({\n  count: 0\n})\n\nexport const increment = () =\u003e ({ count }) =\u003e ({ count: count + 1 })\nexport const decrement = () =\u003e ({ count }) =\u003e ({ count: count - 1 })\n```\n\n```js\n// organisms/Counter.js\nimport makeOrganism from 'react-organism'\nimport Counter from './components/Counter'\nimport * as counterState from './state/counter'\n\nexport default makeOrganism(Counter, counterState)\n```\n\n```js\n// App.js\nimport React from 'react'\nimport CounterOrganism from './organisms/Counter'\n\nclass App extends React.Component {\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cCounterOrganism /\u003e\n      \u003c/div\u003e\n    )\n  }\n}\n```\n\n### Multicelled Organisms\n\nExample coming soon.\n\n\n## API\n\n### `makeOrganism(PureComponent, StateFunctions, options?)`\n```js\nimport makeOrganism from 'react-organism'\n```\nCreates a smart component, rendering using React component `PureComponent`, and managing state using `StateFunctions`.\n\n#### `PureComponent`\nA React component, usually a pure functional component. This component is passed as its props:\n\n- The props passed to the smart component, combined with\n- The current state, combined with\n- `handlers` which correspond to each function in `StateFunctions` and are ready to be passed to e.g. `onClick`, `onChange`, etc.\n- `loadError?`: Error produced by the `load` handler\n- `handlerError?`: Error produced by any other handler\n\n#### `StateFunctions`\nObject with functional handlers. See [state functions below](#state-functions).\n\nEither 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 '...'`.\n\n#### `options`\n\n##### `adjustArgs?(args: array) =\u003e newArgs: array`\n\nUsed to enhance handlers. See [built-in handlers below](#argument-enhancers).\n\n##### `onChange?(state)`\n\nCalled after the state has changed, making it ideal for saving the state somewhere (e.g. Local Storage).\n\n\n### State functions\n\nYour 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.\n\nEach handler is passed the current props first, followed by the called arguments:\n- `(props, event)`: most event handlers, e.g. `onClick`, `onChange`\n- `(props, first, second)`: e.g. `handler(first, second)`\n- `(props, ...args)`: get all arguments passed\n- `(props)`: ignore any arguments\n- `()`: ignore props and arguments\n\nHandlers must return one of the following:\n- An object with new state changes, a la React’s `setState(changes)`.\n- A function accepting the previous state and current props, and returns the new state, a la React’s `setState((prevState, props) =\u003e changes)`.\n- 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.\n- 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).\n- An array of any of the above (object / function / promise / iterator).\n- Or optionally, nothing.\n\nThere are some handlers for special tasks, specifically:\n\n#### `initial(props) =\u003e object` (required)\nReturn initial state to start off with, a la React’s `initialState`. Passed props.\n\n#### `load(props: object, prevProps: object?, { handlers: object }) =\u003e object | Promise\u003cobject\u003e | void` (optional)\nPassed 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.\n\nIf this is the first time loaded or if being reloaded, then `prevProps` is `null`.\n\nUsual pattern is to check for either `prevProps` being `null` or if the prop of interest has changed from its previous value:\n```js\nexport const load = async ({ id }, prevProps) =\u003e {\n  if (!prevProps || id !== prevProps.id) {\n    return { item: await loadItem(id) }\n  }\n}\n```\n\nYour `load` handler will be called in React’s lifecycle: `componentDidMount` and `componentWillReceiveProps`.\n\n\n### Argument enhancers\n\nHandler arguments can be adjusted, to cover many common cases. Pass them to the `adjustArgs` option. The following enhancers are built-in:\n\n#### `extractFromDOM(args: array) =\u003e newArgs: array`\n```js\nimport extractFromDOM from 'react-organism/lib/adjustArgs/extractFromDOM'\n```\n\nExtract values from DOM, specifically:\n- 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.\n- For `submit` events, extracts values of `\u003cinput\u003e` fields in a `\u003cform\u003e`. 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 `\u003cform\u003e`. Form must have `data-extract` attribute present. To clear the form after submit, add `data-reset` to the form.\n\n\n## Why instead of Redux?\n\n- Like Redux, separate your state management from rendering\n- Unlike Redux, avoid loose strings for identifying actions\n- Redux encourages having state in one bundle, whereas dynamic `import()` encourages breaking apps into sections\n- Easier to reuse functionality, as action handlers are totally encapsulated\n- No ability to reach across to the other side of your state tree\n- Encourages composition of components\n- Supports `async` and `await` in any action\n- Supports generator functions to allow multiple state changes — great for animation\n- No `switch` statements\n- No boilerplate or additional helper libraries needed\n\n\n[build-badge]: https://img.shields.io/travis/RoyalIcing/react-organism/master.png?style=flat-square\n[build]: https://travis-ci.org/RoyalIcing/react-organism\n\n[npm-badge]: https://img.shields.io/npm/v/react-organism.png?style=flat-square\n[npm]: https://www.npmjs.org/package/react-organism\n\n[coveralls-badge]: https://img.shields.io/coveralls/RoyalIcing/react-organism/master.png?style=flat-square\n[coveralls]: https://coveralls.io/github/RoyalIcing/react-organism\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRoyalIcing%2Freact-organism","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRoyalIcing%2Freact-organism","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRoyalIcing%2Freact-organism/lists"}