├── .babelrc
├── .circleci
└── config.yml
├── .gitignore
├── .npmignore
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── StateDiagram.png
├── examples
├── 1.Normal-Component-State.js
└── 2.With-Redux.js
├── img
└── logo.svg
├── package.json
├── src
├── ManagedStateMachine.js
├── StateMachine.js
└── index.js
├── test
├── configure-tests.js
├── index.test.js
└── mock-components.js
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env", "@babel/react"],
3 | "plugins": [
4 | "@babel/plugin-proposal-object-rest-spread",
5 | "@babel/plugin-proposal-class-properties"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | # specify the version you desire here
6 | - image: circleci/node:7.10
7 |
8 | # Specify service dependencies here if necessary
9 | # CircleCI maintains a library of pre-built images
10 | # documented at https://circleci.com/docs/2.0/circleci-images/
11 | # - image: circleci/mongo:3.4.4
12 |
13 | working_directory: ~/repo
14 |
15 | steps:
16 | - checkout
17 |
18 | # Download and cache dependencies
19 | - restore_cache:
20 | keys:
21 | - v1-dependencies-{{ checksum "package.json" }}
22 | # fallback to using the latest cache if no exact match is found
23 | - v1-dependencies-
24 |
25 | - run: yarn install
26 | - run: yarn add jest
27 |
28 | - save_cache:
29 | paths:
30 | - node_modules
31 | key: v1-dependencies-{{ checksum "package.json" }}
32 |
33 | # run tests!
34 | - run: yarn test
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /lib
3 | /coverage
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | docs
2 | src
3 | .babelrc
4 | webpack.config.js
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Debug CRA Tests",
9 | "type": "node",
10 | "request": "launch",
11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts",
12 | "args": [
13 | "test",
14 | "--runInBand",
15 | "--no-cache",
16 | "--env=jsdom",
17 | "${relativeFile}"
18 | ],
19 | "cwd": "${workspaceRoot}",
20 | "protocol": "inspector",
21 | "console": "integratedTerminal",
22 | "internalConsoleOptions": "neverOpen"
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Francis Stokes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Machinery
2 |
3 | []()
4 | []()
5 | []()
6 |
7 |
8 |
9 |
10 | ⚙️ State Machine Modelling for React
11 |
12 | - [Description](#description)
13 | - [Examples](#examples)
14 | - [Installation](#installation)
15 | - [API](#api)
16 | - [StateMachine](#statemachine)
17 | - [getCurrentState](#getcurrentstate)
18 | - [setNewState](#setnewstate)
19 | - [states](#states)
20 | - [data](#data)
21 |
22 | ## Description
23 |
24 | 🔥 `React Machinery` provides a simple to use, component based approach to state machines in react.
25 |
26 | Describe your states, transitions, and which component each renders, and plug it into the `StateMachine` component. It accepts two extra functions; one for getting the current state name and one for setting it. This allows your app to flexibly use and swap out different ways of storing data - be it in component state, redux, mobx, whatever.
27 |
28 | ### Examples
29 |
30 | Examples of how the following state diagram can be implemented in both vanilla react and using redux can be found in the examples folder.
31 |
32 | - [Example using react](examples/1.Normal-Component-State.js)
33 | - [Example using redux](examples/2.With-Redux.js)
34 |
35 | 
36 |
37 | ## Installation
38 |
39 | ```bash
40 | # with yarn
41 | yarn add react-machinery
42 |
43 | # or with npm
44 | npm i react-machinery
45 | ```
46 |
47 | ## API
48 |
49 | ### StateMachine
50 |
51 | All props for the `StateMachine` component are required.
52 |
53 | #### getCurrentState
54 |
55 | ##### function()
56 |
57 | A function returning the current state name, stored somewhere like a react component state, or in redux.
58 |
59 | #### setNewState
60 |
61 | ##### function(newStateName)
62 |
63 | A function that updates the current state, stored somewhere like a react component state, or in redux.
64 |
65 | #### states
66 |
67 | ##### Array of state definitions
68 |
69 | A state definition is a plain javascript object with the following properties:
70 |
71 | ```javascript
72 | {
73 | // This name corresponds to the one coming from getCurrentState() and
74 | // being set by setNewState()
75 | name: 'State Name',
76 |
77 | // Array of plain objects that describe automatic transitions
78 | // These are evaluated after a transition and when props change
79 | autoTransitions: [
80 | // A transition object must contain two properties:
81 | // test, which is a function that recieves the StateMachine data, and returns true if a state change should take place
82 | // newState, which is the name of the state to transition to when the test function returns true
83 | {
84 | test: data => data === 'expected for state change',
85 | newState: 'Name of new state'
86 | }
87 | ],
88 |
89 | // This is a list of states that can be transitioned to from the current state using the transitionTo
90 | // prop passed to a state component. Trying to use transitionTo with a state not specified in this list
91 | // will throw an error. This list, together with any 'newState's described in autoTransitions form the
92 | // full set of valid transitions for this state.
93 | validTransitions: [
94 | 'Name of valid state'
95 | ],
96 |
97 | // beforeRender is an optional function that can run some code before this states component
98 | // is rendered. For anything sufficiently complex however, it's better to use a react class
99 | // component and take advantage of lifecycle hooks
100 | beforeRender: (data) => {
101 | data.startAPIRequest();
102 | },
103 |
104 | // One of the following two properties must be implemented:
105 |
106 | // a render prop that recieves the 'props' object supplied to the StateMachine
107 | // the props object will also include a 'transitionTo' function and a 'currentState' string
108 | render: (data) => {
109 | return
110 | },
111 |
112 | // Or just a regular react component
113 | component: SomeReactComponent
114 | }
115 | ```
116 |
117 | #### data
118 |
119 | ##### object
120 |
121 | An object contains data that defines all the states in the state machine. This data is supplied to the component rendered by any state, to `test` functions in autoTransitions. If a render prop is used for the state, then the props are passed as the argument, along with a `transitionTo` function and `currentState` name.
122 |
123 | # Logo
124 |
125 | The awesome logo was designed by @ayushs08, who graciously provided under CC BY 3.0. Many thanks!
--------------------------------------------------------------------------------
/StateDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/francisrstokes/React-Machinery/acca228c2a70808b6f35e99eb977924a1f61755e/StateDiagram.png
--------------------------------------------------------------------------------
/examples/1.Normal-Component-State.js:
--------------------------------------------------------------------------------
1 | import {StateMachine} from 'react-machinery';
2 |
3 | /*
4 | This example uses a regular react component as the data store for our state machine.
5 | Updating the state is as simple as passing a function that calls setState on our component.
6 |
7 | At any time, we can be in one of four states:
8 | - theNumberOne
9 | - theNumberTwo
10 | - theNumberTen
11 | - lifeTheUniverseAndEverything
12 |
13 | We can only transition to some states if we are already in a certain state. For example,
14 | 'theNumberTen' can only be transitioned if we are already in 'theNumberTwo'.
15 | */
16 |
17 | const One = () =>
State #1
18 | const Two = () =>
State #2
19 | const Ten = () =>
State #10
20 | const HitchHikerComponent = () =>
Theres a frood who really knows where his towel is.
;
93 | }
94 | }
--------------------------------------------------------------------------------
/examples/2.With-Redux.js:
--------------------------------------------------------------------------------
1 | import {StateMachine} from 'react-machinery';
2 | import {connect} from 'react-redux';
3 |
4 | /*
5 | This example uses a redux connected component to provide data to the State Machine.
6 | Updating the state is done by dispatching an action so that the
7 | current state is also kept in the store.
8 |
9 | At any time, we can be in one of four states:
10 | - theNumberOne
11 | - theNumberTwo
12 | - theNumberTen
13 | - lifeTheUniverseAndEverything
14 |
15 | We can only transition to some states if we are already in a certain state. For example,
16 | 'theNumberTen' can only be transitioned if we are already in 'theNumberTwo'.
17 | */
18 |
19 | const One = () =>
State #1
20 | const Two = () =>
State #2
21 | const Ten = () =>
State #10
22 | const HitchHikerComponent = () =>
Theres a frood who really knows where his towel is.