├── .editorconfig
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── TODO.md
├── docs
├── actions.md
├── reducers.md
└── redux.md
├── examples
└── async-redux
│ ├── actions
│ ├── PostsActions.js
│ ├── SelectionActions.js
│ └── index.js
│ ├── components
│ ├── Picker.js
│ └── Posts.js
│ ├── containers
│ ├── AsyncApp.js
│ └── Root.js
│ ├── index.html
│ ├── index.js
│ ├── package.json
│ ├── reducers
│ ├── index.js
│ ├── posts.js
│ ├── postsByReddit.js
│ └── selectedReddit.js
│ ├── server.js
│ ├── store
│ ├── configureStore.js
│ └── index.js
│ └── webpack.config.js
├── index.js
├── package.json
├── redux.js
└── src
├── callAction.js
├── combineReducers.js
├── connectActionSets.js
├── getConsensus.js
├── index.js
├── isFunction.js
├── notFoundValue.js
├── redux.js
├── sequence.js
└── types.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | indent_size = 2
6 | tab_width = 2
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 | lib
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flambeau
2 | Lightweight Redux enhancements with opinions:
3 |
4 | ### Declarative action creators
5 | - **No UPPERCASE_CONSTANTS**. Just use an exported function to name the action, and a destructured object to document the payload.
6 | ```javascript
7 | export function addTodo({ text }) {} // No constants
8 |
9 | export function editTodo({ index, text }) {} // Payload is self-documenting
10 |
11 | export function completeTodo({ index }) { // Payload can be altered, otherwise defaults to the input.
12 | return { index, dateCompleted: new Date() };
13 | }
14 | ```
15 | - Namespacing of actions into sets for better organization.
16 | - **Async action support built-in**, with convenient dispatching of other actions.
17 |
18 | ### Reusable reducers
19 | - **No switch statements** to handle actions, just declare functions matching those exported by the action set.
20 | ```javascript
21 | export const TodoListActions = {
22 | addTodo(state, { text }) {
23 | return state.concat({ text });
24 | },
25 |
26 | editTodo(state, { index, text }) {
27 | let newState = state.slice();
28 | newState[index] = { ...newState[index], text };
29 | return newState;
30 | },
31 |
32 | completeTodo(state, { index, dateCompleted }) {
33 | let newState = state.slice();
34 | newState[index] = { ...newState[index], dateCompleted };
35 | return newState;
36 | }
37 | }
38 | ```
39 | - **Reusable reducers, using props to customize** the response to actions or initial state.
40 | ```javascript
41 | // Props are passed in as first argument:
42 | export function getInitialState({ initialItems = [] }) {
43 | return {
44 | items: initialItems
45 | };
46 | }
47 | ```
48 | - **Forward action sets in bulk** within reducers to allow easy composition of reducers, such as in collections or other hierarchies.
49 |
50 | ### Reducer state encapsulation
51 | - **Introspection methods encapsulate** reducers’ internal state. This removes action creators’ knowledge of the store’s structure, allowing greater code reuse.
52 | - Get a **consensus for async actions**, such as whether something needs loading, by polling reducers using their introspection methods.
53 |
54 | ## Documentation
55 |
56 | - [Actions](docs/actions.md)
57 | - [Reducers](docs/reducers.md)
58 | - [Using with Redux](docs/redux.md)
59 |
60 | ## Installation
61 |
62 | `npm install flambeau --save`
63 |
64 | ## Examples
65 |
66 | - See the [async redux demo example](examples/async-redux) for a full example of introspection and the features of Flambeau.
67 | - The great [flux-comparison](https://github.com/voronianski/flux-comparison) project has a [Redux + Flambeau example](https://github.com/voronianski/flux-comparison/tree/master/redux-flambeau).
68 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | ## Allow reducers to emit custom events
2 |
3 | Events are global, are not stored anywhere only transmitted, and listened to by
4 | those who wish to subscribe.
5 |
6 | ```javascript
7 | export const events = {
8 | blockCannotJoinWithPrevious({ index })
9 | };
10 |
11 |
12 | export function joinBlockWithPrevious(state, { index }, { emit }) {
13 | if (index === 0) {
14 | emit.blockCannotJoinWithPrevious({ index });
15 | }
16 |
17 | // ...
18 | }
19 | ```
20 |
21 | ## Declarative way to combine reducers?
22 |
23 | ```javascript
24 | export default {
25 | combine: {
26 | byId,
27 | visibleIds
28 | },
29 | alsoAdd: (combined) => ({
30 |
31 | })
32 | };
33 | ```
34 |
--------------------------------------------------------------------------------
/docs/actions.md:
--------------------------------------------------------------------------------
1 | # Actions
2 |
3 | ## Action Sets
4 |
5 | Action sets group multiple action creators into one namespace.
6 |
7 | The recommended way to declare an action set is to create a file with the name
8 | of action set, e.g. *TodoListActions.js*. Then declare action creators and
9 | introspection methods, which are detailed below.
10 |
11 | ## Action Creators
12 |
13 | Flambeau’s action creators are declarative. There is no need for
14 | UPPERCASE_CONSTANTS. Simply export a function, with its name identifying the
15 | action creator, and a destructured object (`{ anything, you, like }`) for the
16 | first argument as the payload.
17 |
18 | If the payload is to be used as-is, then the body of the function can be left
19 | empty.
20 | If you would like to add other properties or transform the payload in some
21 | way, then return the customized payload you want.
22 |
23 | e.g.
24 | ```javascript
25 | export function addTodo({ text }) {} // Payload as-is
26 |
27 | export function addTodoWithCurrentDate({ text }) { // Payload transformed
28 | return {
29 | dateCreated: new Date(),
30 | text
31 | };
32 | }
33 | ```
34 |
35 | ## Async Action Creators
36 |
37 | Asynchronous action creators allow operations such as loading or saving to be
38 | encapsulated.
39 | These action creators forward to other actions that reducers can listen to.
40 |
41 | To make your action creator asynchronous, simple add a second argument to your action creator with a destructured object
42 | of the following optional properties:
43 | - `currentActionSet`: The action set you are currently declaring within,
44 | allowing you to dispatch sibling actions. Maps action identifiers to each action’s
45 | dispatcher function. e.g. `currentActionSet.otherAction(payload)`
46 | - `allActionSets`: All the action sets, mapping action set identifiers to their dispatcher functions. e.g. `allActionSets.OtherActionSet.someOtherAction(payload)`
47 |
48 | ```javascript
49 | import fetch from 'isomorphic-fetch';
50 |
51 | export function addTodo({ text }) {}
52 | export function addTodosFromURL({ items, URL }) {}
53 |
54 | export function importTodosFromURL({ URL }, { currentActionSet, allActionSets }) {
55 | fetch(URL)
56 | .then(response => response.json())
57 | .then(items => currentActionSet.addTodosFromURL({ items, URL }));
58 | }
59 | ```
60 |
61 | ## Introspection
62 |
63 | Flambeau allows action creators to query reducers with the concept of
64 | 'introspection', which is similar to interfaces or protocols in other
65 | programming languages.
66 |
67 | Reducers implement introspection methods, returning results from within its
68 | state. This completely encapsulates the specifics of the state’s structure.
69 | Unlike the use of Redux’s `getState()`, action creators have no knowledge
70 | of the state tree.
71 |
72 | Declare introspection methods underneath your exported actions:
73 | ```javascript
74 | export const introspection = {
75 | hasImportedFromURL({ URL }) {},
76 | hasTodoContainingText({ text }) {}
77 | };
78 | ```
79 |
80 | Introspection is explained in more detail and with examples in the
81 | [reducers](reducers.md#introspection)
82 | section.
83 |
84 | ---
85 |
86 | **[Actions](actions.md)**
87 | ·
88 | [Reducers](reducers.md)
89 | ·
90 | [Using with Redux](redux.md)
91 |
--------------------------------------------------------------------------------
/docs/reducers.md:
--------------------------------------------------------------------------------
1 | # Reducers
2 |
3 | ## Props
4 |
5 | Reducers in Flambeau are differentiated from the standard Redux reducer by a
6 | number of modest features. One of these is the addition of props for reducers.
7 |
8 | These props allow both the initial state and the state transformation during an
9 | action to be customized. Like React’s props, their use is encouraged to enable
10 | reducers to be less hard-coded and more reusable.
11 |
12 | ## Initial State
13 |
14 | The initial state in Flambeau is determined by a declaration of the exported
15 | function `getInitialState`. The props are passed as the first argument.
16 |
17 | ```javascript
18 | export function getInitialState({ initialItems = [] }) {
19 | return {
20 | items: initialItems
21 | };
22 | }
23 | ```
24 |
25 | ## Implementing Action Responders
26 |
27 | Responding to actions is done in a similar manner to action creators: by
28 | declaring functions. The name of the function mirrors that of the action
29 | creator. The functions are declared in a vanilla JavaScript object, named the
30 | same as the action set.
31 |
32 | The only difference from action creators is the current state is passed as the
33 | first argument.
34 |
35 | ```javascript
36 | export const TodoListActions = {
37 | addTodo(state, { text }) {
38 | return state.concat({ text });
39 | }
40 | }
41 | ```
42 |
43 | ## Composing
44 |
45 | Reducers can be easily composed within each other. This allows you to break your
46 | reducers into multiple pieces.
47 |
48 | Forwarding actions is possible, especially easily done in bulk per action set.
49 | To forward an action, declare an action set as a function instead of an object.
50 | When an action from this set is dispatched, this function will be called with
51 | the following parameters.
52 | - `isAction`: when the payload being dispatched is a standard action.
53 | - `isIntrospection`: when introspection into reducers is being requested.
54 | - `actionID`: the identifier of the action or introspection method.
55 | - `payload`: the payload being dispatched.
56 | - `props`: the props of this particular reducer.
57 | - `forwardTo()`: Call this to use another reducer on a subset of your state.
58 |
59 | ```javascript
60 | // TodoItemActions.js
61 |
62 | export function changeText({ text, index }) {}
63 | export function changeCompleted({ isCompleted, index }) {}
64 | ```
65 |
66 | ```javascript
67 | // TodoListReducer.js
68 | import TodoItemReducer from './TodoItemReducer';
69 |
70 | export function getInitialState() {
71 | return {
72 | items: []
73 | };
74 | }
75 |
76 | export function TodoItemActions(state, { isAction, isIntrospection, payload, props, forwardTo }) {
77 | if (isAction) {
78 | const { index } = payload;
79 | state = Object.assign({}, state, {
80 | items: state.items.slice() // Make a copy of the entire array
81 | });
82 | state.items[index] = forwardTo({ responder: TodoItemReducer, initialState: state.items[index] });
83 |
84 | return state;
85 | }
86 | }
87 | ```
88 |
89 | ```javascript
90 | // TodoItemReducer.js
91 |
92 | export const TodoItemActions = {
93 | changeText(item, { text, index }) {
94 | return Object.assign({}, item, { text });
95 | },
96 |
97 | changeCompleted(item, { isCompleted, index }) {
98 | return Object.assign({}, item, { isCompleted });
99 | }
100 | }
101 | ```
102 |
103 | ## Introspection
104 |
105 | An application will often need different actions to be dispatched depending on the
106 | store’s state. Differing from the normal method of directly checking the store
107 | (`getState()` in Redux), Flambeau introduces *introspection* methods, which
108 | allow reducers to completely encapsulate its state from the outside world.
109 |
110 | Say a todo list allows importing items from a URL online. The import action
111 | creator may want to only load data if it hasn’t been done already. Because
112 | action creators are stateless, this bit of information will be stored by a
113 | reducer somewhere.
114 |
115 | Introspection methods allow a reducer to declare its preference, say whether to
116 | load a URL or not, whilst leaving the implementation details of the store’s
117 | state hidden from the action creator.
118 |
119 | ```javascript
120 | // TodoListActions.js
121 | import fetch from 'isomorphic-fetch';
122 |
123 | export function addTodosFromURL({ items, URL }) {}
124 |
125 | function importTodosFromURL({ URL }, { currentActionSet }) {
126 | fetch(URL)
127 | .then(response => response.json())
128 | .then(items => currentActionSet.addTodosFromURL({ items, URL }));
129 | }
130 |
131 | export function importTodosFromURLIfNeeded({ URL }, { currentActionSet }) {
132 | if (!currentActionSet.consensus.hasImportedFromURL({ URL }).every()) {
133 | // This function is not exported as a public action, instead used directly.
134 | importTodosFromURL({ URL }, { currentActionSet });
135 | }
136 | }
137 |
138 | export const introspection = {
139 | hasImportedFromURL({ URL }) {}
140 | };
141 | ```
142 |
143 | ```javascript
144 | // TodoListReducer.js
145 | export function getInitialState({ initialItems = [] }) {
146 | return {
147 | items: initialItems,
148 | importedURLs: {}
149 | };
150 | }
151 |
152 | export const TodoListActions = {
153 | addTodosFromURL(state, { items, URL }) {
154 | // Other reducers might have returned false from hasImportedFromURL()
155 | if (state.importedURLs[URL]) {
156 | return;
157 | }
158 |
159 | return Object.assign({}, state, {
160 | importedURLs: Object.assign({}, state.importedURLs, { [URL]: true }),
161 | items: state.items.concat(items)
162 | });
163 | },
164 |
165 | introspection: {
166 | hasImportedFromURL(state, { URL }) {
167 | return Boolean(
168 | state.importedURLs[URL]
169 | );
170 | }
171 | }
172 | };
173 | ```
174 |
175 | ## Introspection Consensus
176 |
177 | The `consensus` property is part of every connected action set, including
178 | those passed to action creators (`currentActionSet` and `allActionSets`), that
179 | have introspection methods.
180 |
181 | To use it, append your introspection identifier, and call it with the payload to
182 | pass to each reducer’s introspection method. Then call one of the following
183 | methods:
184 |
185 | - `some([callback])`: like `Array.some`, returns true if callback returns true for any reducer’s
186 | result.
187 | If a callback is not passed, then the result is treated as a boolean.
188 | - `every([callback])`: like `Array.every`, returns true if callback returns true for every reducer’s
189 | result.
190 | If a callback is not passed, then the result is treated as a boolean.
191 | - `singleton()`: expects there to be only one reducer, returning its result.
192 | Throws an exception if zero or more than one reducer responded.
193 | - `reduce(callback[, initialValue])`: like `Array.reduce`, combines every
194 | reducer’s result using a callback passed the combined result so far, and the
195 | currently iterated reducer’s result for the introspection method.
196 | - `toArray()`: returns an array of results of every reducer for this
197 | introspection method.
198 |
199 | ```javascript
200 | if (currentActionSet.consensus.yourIntrospectionID({
201 | yourPayloadProperties: true
202 | }).some()) {
203 | // If any reducer returned true.
204 | }
205 | ```
206 |
207 | ```javascript
208 | if (currentActionSet.consensus.yourIntrospectionID({
209 | yourPayloadProperties: true
210 | }).every()) {
211 | // If all reducers returned true.
212 | }
213 | ```
214 |
215 | ```javascript
216 | const currentActionSet.consensus.yourIntrospectionID({
217 | yourPayloadProperties: true
218 | }).singleton();
219 | // A single chosen reducer returned a value.
220 | // Throws if no or multiple reducers returned a result.
221 | // Great for configuration variables.
222 | ```
223 |
224 | ```javascript
225 | const combinedResult = currentActionSet.consensus.yourIntrospectionID({
226 | yourPayloadProperties: true
227 | }).reduce((combined, current) => {
228 | // Reduce `combined` and `current`
229 | return combined + current;
230 | }, /* optional initialValue */ 0);
231 | ```
232 |
233 | ---
234 |
235 | [Actions](actions.md)
236 | ·
237 | **[Reducers](reducers.md)**
238 | ·
239 | [Using with Redux](redux.md)
240 |
--------------------------------------------------------------------------------
/docs/redux.md:
--------------------------------------------------------------------------------
1 | # Redux Support
2 |
3 | Flambeau is built to be composable, in a style inspired from Redux.
4 |
5 | Because Flambeau’s action creators and reducers are declarative, you don’t need
6 | to import Flambeau into their files. The only thing you need to import is
7 | 'flambeau/redux' into your Redux store.
8 |
9 | Two functions are provided:
10 | - `createRootReducer({ reducers, idToProps })`: Creates a Redux compatible
11 | reducer. Pass your props, mapping the reducer ID to specific props.
12 | - `connectActionSetsToStore()`: Connects your action sets to the Redux store. It
13 | is recommended that you export this object, to be used within your controller
14 | components (in React).
15 |
16 | Here is an example of integrating Redux with Flambeau:
17 |
18 | ```javascript
19 | import { createStore, applyMiddleware } from 'redux';
20 | import { createRootReducer, connectActionSetsToStore } from 'flambeau/redux';
21 | import actionSets from '../actions';
22 | import reducers, { idToProps } from '../reducers';
23 |
24 | const createStoreWithMiddleware = applyMiddleware(
25 | // All your favorite Redux middleware
26 | )(createStore);
27 |
28 | const rootReducer = createRootReducer({ reducers, idToProps });
29 | export const store = createStoreWithMiddleware(rootReducer, initialState);
30 | export const connectedActionSets = connectActionSetsToStore({ actionSets, store });
31 | ```
32 |
33 | It is expected that `actions` and `reducers` would be directories exporting all
34 | action sets and reducers, like the following:
35 |
36 | ```javascript
37 | // actions/index.js
38 |
39 | import * as TodoItemActions from './TodoItemActions';
40 | import * as TodoListActions from './TodoListActions';
41 |
42 | export default {
43 | TodoItemActions,
44 | TodoListActions
45 | };
46 | ```
47 |
48 | ```javascript
49 | // reducers/index.js
50 |
51 | import * as TodoListReducer from './TodoListReducer';
52 |
53 | export default {
54 | list: TodoListReducer
55 | };
56 |
57 | export const idToProps = {
58 | list: {
59 | initialItems: [
60 | {
61 | text: 'Read Flambeau documentation',
62 | isCompleted: true
63 | }
64 | ]
65 | }
66 | }
67 | ```
68 |
69 | ---
70 |
71 | [Actions](actions.md)
72 | ·
73 | [Reducers](reducers.md)
74 | ·
75 | **[Using with Redux](redux.md)**
76 |
--------------------------------------------------------------------------------
/examples/async-redux/actions/PostsActions.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 |
3 |
4 | export function invalidateReddit({ reddit }) {}
5 |
6 | export function requestPosts({ reddit }) {}
7 |
8 | export function receivePosts({ reddit, json }) {
9 | return {
10 | reddit,
11 | posts: json.data.children.map(child => child.data),
12 | receivedAt: Date.now()
13 | };
14 | }
15 |
16 | function fetchPosts({ reddit }, { currentActionSet }) {
17 | currentActionSet.requestPosts({ reddit });
18 |
19 | fetch(`http://www.reddit.com/r/${reddit}.json`)
20 | .then(response => response.json())
21 | .then(json => currentActionSet.receivePosts({ reddit, json }))
22 | ;
23 | }
24 |
25 | export const introspection = {
26 | shouldFetchPosts({ reddit }) {}
27 | }
28 |
29 | export function fetchPostsIfNeeded({ reddit }, { currentActionSet }) {
30 | if (currentActionSet.consensus.shouldFetchPosts({ reddit }).some()) {
31 | fetchPosts({ reddit }, { currentActionSet });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/examples/async-redux/actions/SelectionActions.js:
--------------------------------------------------------------------------------
1 | export function selectReddit({ reddit }) {
2 | return arguments[0];
3 | }
4 |
--------------------------------------------------------------------------------
/examples/async-redux/actions/index.js:
--------------------------------------------------------------------------------
1 | import * as PostsActions from './PostsActions';
2 | import * as SelectionActions from './SelectionActions';
3 |
4 | export default {
5 | PostsActions,
6 | SelectionActions
7 | };
8 |
--------------------------------------------------------------------------------
/examples/async-redux/components/Picker.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | export default class Picker extends Component {
4 | render () {
5 | const { value, onChange, options } = this.props;
6 |
7 | return (
8 |
9 | {value}
10 |
18 |
19 | );
20 | }
21 | }
22 |
23 | Picker.propTypes = {
24 | options: PropTypes.arrayOf(
25 | PropTypes.string.isRequired
26 | ).isRequired,
27 | value: PropTypes.string.isRequired,
28 | onChange: PropTypes.func.isRequired
29 | };
--------------------------------------------------------------------------------
/examples/async-redux/components/Posts.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 |
3 | export default class Posts extends Component {
4 | render () {
5 | return (
6 |
48 | {lastUpdated && 49 | 50 | Last updated at {new Date(lastUpdated).toLocaleTimeString()}. 51 | {' '} 52 | 53 | } 54 | {!isFetching && 55 | 57 | Refresh 58 | 59 | } 60 |
61 | {isFetching && posts.length === 0 && 62 |