Skip to content

Instantly share code, notes, and snippets.

@nicolasparada
Last active July 26, 2018 17:28
Show Gist options
  • Save nicolasparada/14ccc8a0c1ddd5438ab6539e8904d7db to your computer and use it in GitHub Desktop.
Save nicolasparada/14ccc8a0c1ddd5438ab6539e8904d7db to your computer and use it in GitHub Desktop.
Redux Clone (Preact)
const { Component, h, render } = preact
const createStore = (reducer, initialState) => {
let listeners = []
let currentState = initialState
const getState = () => currentState
const subscribe = listener => {
listeners.push(listener)
return () => {
listeners = listeners.filter(l => l !== listener)
}
}
const dispatch = action => {
const rawDispatch = action => {
currentState = reducer(currentState, action)
console.log(action, currentState)
listeners.forEach(listener => {
listener(currentState)
})
}
if (typeof action === 'function') {
return action(dispatch, getState)
} else if (typeof action.then === 'function') {
return action.then(rawDispatch)
} else {
return rawDispatch(action)
}
}
dispatch({ type: 'INIT' })
return { getState, subscribe, dispatch }
}
const combineReducers = reducers => (state = {}, action) =>
Object.keys(reducers).reduce((nextState, key) => (
nextState[key] = reducers[key](state[key], action), nextState
), {})
class Provider extends Component {
getChildContext() {
return { store: this.props.store }
}
render(props) {
return props
}
}
const shallowEqual = (objA, objB) => {
if (objA === objB) {
return true
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) {
return false
}
const bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB)
for (let i = 0; i < keysA.length; i++) {
if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false
}
}
return true
}
const connect = (
mapStateToProps = () => ({}),
mapDispatchToProps = () => ({})
) => Child => class extends Component {
constructor(props, context) {
super(props, context)
this.state = mapStateToProps(context.store.getState(), this.props)
this.actions = typeof mapDispatchToProps === 'function'
? mapDispatchToProps(context.store.dispatch, this.props)
: Object.keys(mapDispatchToProps).reduce((actionCreators, key) => {
actionCreators[key] = (...args) => context.store.dispatch(mapDispatchToProps[key](...args))
return actionCreators
}, {})
}
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(newState => {
this.setState(mapStateToProps(newState, this.props))
})
}
componentDidUnmount() {
if (this.unsubscribe)
this.unsubscribe()
}
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(nextProps, this.props) || !shallowEqual(nextState, this.state)
}
render(props, state) {
return h(Child, Object.assign({}, props, state, this.actions))
}
}
/**
* App code since here.
*/
const Display = connect(
state => ({ count: state.count })
)(({ count }) =>
h('span', null, count)
)
const Controls = connect(undefined, {
increment: () => ({ type: 'INCREMENT' }),
decrement: () => ({ type: 'DECREMENT' }),
})(({ increment, decrement }) =>
h('div', null, [
h('button', { onClick: increment }, '+'),
h('button', { onClick: decrement }, '-'),
])
)
const AsyncControls = connect(undefined, {
incrementAsync: () => new Promise(resolve => {
setTimeout(() => {
resolve({ type: 'INCREMENT' })
}, 500)
}),
decrementAsync: () => dispatch => {
setTimeout(() => {
dispatch({ type: 'DECREMENT' })
}, 500)
}
})(({ incrementAsync, decrementAsync }) =>
h('div', null, [
h('button', { onClick: incrementAsync }, '+ (async)'),
h('button', { onClick: decrementAsync }, '- (async)')
])
)
const countReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1
case 'DECREMENT': return state - 1
default: return state
}
}
const reducer = combineReducers({
count: countReducer
})
const store = createStore(reducer)
const Root = () =>
h(Provider, { store }, [
h('div', null, [
h(Display),
h(Controls),
h(AsyncControls)
])
])
render(h(Root), document.body)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment