-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
Copy pathredux_helpers.js
129 lines (124 loc) · 3.97 KB
/
redux_helpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import $ from 'jquery'
import reduce from 'lodash.reduce'
import isObject from 'lodash.isobject'
import forIn from 'lodash.forin'
import { createStore as reduxCreateStore } from 'redux'
/**
* Create a redux store given the reducer. It also enables the Redux dev tools.
*/
export function createStore (reducer) {
// @ts-ignore
return reduxCreateStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
}
/**
* Connect elements with the redux store. It must receive an object with the following attributes:
*
* elements: It is an object with elements that are going to react to the redux state or add something
* to the initial state.
*
* ```javascript
* const elements = {
* // The JQuery selector for finding elements in the page.
* '[data-counter]': {
* // Useful to put things from the page to the redux state.
* load ($element) {...},
* // Check for state changes and manipulates the DOM accordingly.
* render ($el, state, oldState) {...}
* }
* }
* ```
*
* The load and render functions are optional, you can have both or just one of them. It depends
* on if you want to load something to the state in the first render and/or that the element should
* react to the redux state. Notice that you can include more elements if you want to since elements
* also is an object.
*
* store: It is the redux store that the elements should be connected with.
* ```javascript
* const store = createStore(reducer)
* ```
*
* action: The first action that the store is going to dispatch. Optional, by default 'ELEMENTS_LOAD'
* is going to be dispatched.
*
* ### Examples
*
* Given the markup:
* ```HTML
* <div data-counter>
* <span class="number">1</span>
* </div>
* ```
*
* The reducer:
* ```javascript
* function reducer (state = { number: null }, action) {
* switch (action.type) {
* case 'ELEMENTS_LOAD': {
* return Object.assign({}, state, { number: action.number })
* }
* case 'INCREMENT': {
* return Object.assign({}, state, { number: state.number + 1 })
* }
* default:
* return state
* }
* }
* ```
*
* The elements:
* ```javascript
* const elements = {
* // '[data-counter]' is the element that will be connected to the redux store.
* '[data-counter]': {
* // Find the number within data-counter and add to the state.
* load ($el) {
* return { number: $el.find('.number').val() }
* },
* // React to redux state. Case the number in the state changes, it is going to render the
* // new number.
* render ($el, state, oldState) {
* if (state.number === oldState.number) return
*
* $el.html(state.number)
* }
* }
* }
*
* All we need to do is connecting the store and the elements using this function.
* ```javascript
* connectElements({store, elements})
* ```
*
* Now, if we dispatch the `INCREMENT` action, the state is going to change and the [data-counter]
* element is going to re-render since they are connected.
* ```javascript
* store.dispatch({type: 'INCREMENT'})
* ```
*/
export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) {
function loadElements () {
return reduce(elements, (pageLoadParams, { load }, selector) => {
if (!load) return pageLoadParams
const $el = $(selector)
if (!$el.length) return pageLoadParams
const morePageLoadParams = load($el, store)
return isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams
}, {})
}
function renderElements (state, oldState) {
forIn(elements, ({ render }, selector) => {
if (!render) return
const $el = $(selector)
if (!$el.length) return
render($el, state, oldState)
})
}
let oldState = store.getState()
store.subscribe(() => {
const state = store.getState()
renderElements(state, oldState)
oldState = state
})
store.dispatch(Object.assign(loadElements(), { type: action }))
}