Last active
December 3, 2022 02:41
-
-
Save jcppman/7d82853ea6be03855bf8b961ea1a9770 to your computer and use it in GitHub Desktop.
Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* some helpers */ | |
import { batch } from 'redux-act'; | |
import { createStore, combineReducers } from 'redux'; | |
import { get, isFunction, forEach, mapValues } from 'lodash'; | |
import { Observable } from 'rxjs'; | |
// create a store, combine all submodules to the store | |
// and bind all actions and the batch method to the created store | |
export const createAndBindStore = (modules, highOrderReducer) => { | |
let reducer = combineReducers(mapValues(modules, m => m.reducer)); | |
if (highOrderReducer) { | |
reducer = highOrderReducer(reducer); | |
} | |
const store = createStore(reducer); | |
batch.assignTo(store); | |
forEach(modules, ({ actions, init }, moduleName) => { | |
forEach(actions, val => { | |
if (val.isMeta) { | |
// this assignTo is actually more like setGetState | |
// but name it assignTo to keep it consistent with regular | |
// redux-act actions | |
val.assignTo(() => get(store.getState(), moduleName)); | |
return; | |
} | |
val.assignTo(store); | |
}); | |
if (isFunction(init)) { | |
init(Observable.from(store).map(s => s[moduleName])); | |
} | |
}); | |
return store; | |
}; | |
// wrap the given function, provide it the ability to get current store state | |
// by `getState` | |
// createMataAction((getState, payload) => {}); | |
export const createMetaAction = actionBody => { | |
let getState; | |
const asyncAction = (...args) => actionBody(getState, ...args); | |
asyncAction.assignTo = _getState => { | |
getState = _getState; | |
}; | |
asyncAction.isMeta = true; | |
return asyncAction; | |
}; | |
export default { createAndBindStore, createMetaAction }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createAction, createReducer, batch } from 'redux-act'; | |
import produce from 'immer'; | |
import { createMetaAction } from './redux-utils'; | |
// a service getter/setter which uses observable, getItems$ returns a lazy executed observable, only fire the | |
// request when subscribed | |
import { getItems$, saveItems$ } from 'items-service'; | |
const addTodo = createAction(); | |
const removeTodo = createAction(); | |
const editTodo = createAction(); | |
const setItems = createAction(); | |
const setLoading = createAction(); | |
const importItems = createMetaAction(async (getState, url) => { | |
setLoading(false); | |
// get items from an url | |
const items = await fetch(url); | |
batch( | |
setLoading.raw(true), | |
setItems.raw(items), | |
); | |
}); | |
export const actions = { | |
setItems, | |
addTodo, | |
removeTodo, | |
editTodo, | |
setLoading, | |
importItems, | |
}; | |
export const reducer = createReducer({ | |
[setLoading]: (state, isLoading) => | |
produce(function () { | |
this.isLoading = isLoading; | |
}), | |
[addTodo]: (state, content) => | |
produce(state, function() { | |
this.items.push({ | |
content, | |
isDone: false, | |
timestamp: new Date().getTime(), | |
}); | |
}), | |
[removeTodo]: (state, index) => | |
produce(state, function() { | |
this.items.splice(index, 1); | |
}), | |
[editTodo]: (state, payload) => | |
produce(state, function() { | |
const { index } = payload; | |
this.items[index] = { | |
...this.items[index], | |
...payload, | |
}; | |
}), | |
[setItems]: (state, items) => | |
produce(state, function() { | |
this.items = items; | |
}), | |
}, { | |
items: [], | |
isLoading: false, | |
}); | |
export const init = (store$) => { | |
setLoading(true); | |
const loadItems = getItems$().do((items) => { | |
batch( | |
// batch actions, worth noting that because actions | |
// are binded to the store, we need the `raw` version | |
// for batch | |
setItems.raw(items), | |
setLoading.raw(false), | |
) | |
}); | |
// store$ is an observable so we can utilize all kind of its fany operators | |
store$ | |
// don't do anything before the initial items from the backend is loaded | |
// and set to the store, otherwise we'll end up feeding the items backend | |
// just gave us back to the server | |
.skipUntil(loadItems) | |
// in terms of saving to backend, all we care is the property `items` | |
.pluck('items') | |
// a little bit taste of the rx power | |
.debounceTime(1000) | |
.switchMap(items => | |
saveItems$(items).retry(3) | |
).subscribe(); | |
}; | |
export default { | |
actions, | |
reducer, | |
init, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component } from 'react'; | |
import { withState, compose } from 'recompose'; | |
import withTodos from './withTodos'; | |
// let's assume these components exist | |
import TodoItem from './components/TodoItem'; | |
import AddButton from './components/AddButton'; | |
import InputBox from './components/InputBox'; | |
// if in the future we need it elsewhere, maybe promote it to a global state | |
// but at this point it's totally ok to use local state in this scenario | |
// and here we use recompose to make our lifes easier | |
const enhance = compose( | |
withTodos(), | |
withState('textValue', 'setValue', ''), | |
); | |
class TodoList extends Component { | |
handleAddTodo() { | |
const { | |
textValue, | |
setValue, | |
addTodo, | |
} = this.props; | |
addTodo(textValue); | |
setValue(''); | |
} | |
handleContentChange(idx, content) { | |
const { editTodo } = this.props; | |
editTodo(idx, { content }); | |
} | |
handleDoneChanged(idx) { | |
const { items, editTodo } = this.props; | |
editTodo(idx, { isDone: !items[idx].isDone }); | |
} | |
render() { | |
const { | |
tasks, | |
addTodo, | |
removeTodo, | |
editTodo, | |
textValue, | |
setValue, | |
} = props; | |
const { | |
handleContentChange, | |
handleDoneChanged, | |
handleAddTodo, | |
} = this; | |
return ( | |
<div> | |
{tasks.map((t, idx) => | |
<TodoItem | |
item={t} | |
key={t.wtv} | |
onContentChanged={handleContentChange} | |
onDoneChanged={handleDoneChanged} | |
/> | |
)} | |
<InputBox onChange={setValue} value={textValue} /> | |
<AddButton onClick={handlAddTodo} /> | |
</div> | |
); | |
} | |
}; | |
export default enhance(TodoList); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// a Store Connector | |
import { connect } from 'react-redux'; | |
import { createSelector } from 'reselect'; | |
import { actions } from './state'; | |
const getUndone = createSelector( | |
state => state.items, | |
items => items.filter(i => !i.isDone), | |
); | |
const withTodos = () => connect( | |
state => ({ | |
undoneTasks: getUndone(state), | |
tasks: state.items, | |
}), | |
() => (actions) | |
); | |
export default withTodos; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment