Skip to content

Instantly share code, notes, and snippets.

@davidbarral
Created November 12, 2017 15:56
Show Gist options
  • Save davidbarral/321764edb2b1aa32da290c1151ad0c48 to your computer and use it in GitHub Desktop.
Save davidbarral/321764edb2b1aa32da290c1151ad0c48 to your computer and use it in GitHub Desktop.
Toy redux and react-redux
import React, { Component } from "react";
import PropTypes from "prop-types";
import ReactDOM from 'react-dom';
// Toy redux ----------------------------------------------------------
const createStore = (reducer, initialState) => {
let state = initialState;
let subscribers = [];
return {
getState() {
return state;
},
dispatch(action) {
state = reducer(state, action);
subscribers.forEach(subscriber => subscriber());
},
subscribe(newSubscriber) {
subscribers = [...subscribers, newSubscriber];
return () => {
subscribers = subscribers.filter(subscriber => subscriber !== newSubscriber);
};
}
};
};
const combineReducers = (reducers) => (state, action) =>
Object.keys(reducers).reduce((combinedReducer, reducerKey) => {
const reducer = reducers[reducerKey];
return {
...combinedReducer,
[reducerKey]: reducer(state && state[reducerKey], action),
};
}, {});
// Toy react-redux -----------------------------------------------------
class Provider extends Component {
static propTypes = {
store: PropTypes.object,
}
static childContextTypes = {
store: PropTypes.object,
}
getChildContext() {
return {
store: this.props.store,
};
}
render() {
return this.props.children;
}
}
const connect = (
mapStateToProps = () => null,
mapDispatchToProps = () => null,
) => WrappedComponent => {
class Wrapper extends Component {
state = {
storeState: this.context.store.getState(),
}
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => {
this.setState({
storeState: this.context.store.getState(),
})
});
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
render() {
const ownProps = this.props;
const stateProps = mapStateToProps(this.state.storeState, ownProps);
const dispatchProps = mapDispatchToProps(this.context.store.dispatch, ownProps);
return <WrappedComponent {...ownProps} {...stateProps} {...dispatchProps} />
}
}
Wrapper.contextTypes = Provider.childContextTypes;
Wrapper.displayName = `connected(${WrappedComponent.displayName})`;
return Wrapper;
};
// Sample Reminders/Todo App --------------------------------------
export const TODO_ADDED = "TODO_ADDED";
const todoAdded = text => ({
type: TODO_ADDED,
payload: {
text,
},
});
export const TODO_DELETED = "TODO_DELETED";
const todoDeleted = id => ({
type: TODO_DELETED,
payload: {
id,
},
});
export const REMINDER_ADDED = "REMINDER_ADDED";
const reminderAdded = text => ({
type: REMINDER_ADDED,
payload: {
text,
},
});
export const REMINDER_DELETED = "REMINDER_DELETED";
const reminderDeleted = id => ({
type: REMINDER_DELETED,
payload: {
id,
},
});
const notSoUuid = () => new Date().getTime();
const initialTodos = [];
export const todos = (state = initialTodos, { type, payload }) => {
switch(type) {
case TODO_ADDED:
return [...state, { id: notSoUuid(), text: payload.text }];
case TODO_DELETED:
return state.filter(({ id }) => id !== payload.id);
default:
return state;
}
};
const getTodos = state => state;
const initialReminders = [];
export const reminders = (state = initialReminders, { type, payload }) => {
switch(type) {
case REMINDER_ADDED:
return [...state, { id: notSoUuid(), text: payload.text }];
case REMINDER_DELETED:
return state.filter(({ id }) => id !== payload.id);
default:
return state;
}
};
const getReminders = state => state;
const reducer = combineReducers({
todos,
reminders,
});
const getAppTodos = state => getTodos(state.todos);
const getAppReminders = state => getReminders(state.reminders);
const store = createStore(reducer);
store.subscribe(() => console.log(store.getState()));
store.dispatch(todoAdded("A todo"));
store.dispatch(todoAdded("Another todo"));
store.dispatch(reminderAdded("A reminder"));
const Form = ({ onElementAdded }) => {
let input;
const handleSubmit = e => {
e.preventDefault();
if (input.value.trim().length) {
onElementAdded(input.value.trim());
input.value = "";
}
};
return (
<form onSubmit={handleSubmit}>
<input ref={i => input = i} defaultValue="" />
</form>
)
};
Form.propTypes = {
onElementAdded: PropTypes.func,
}
const TodoForm = connect(
undefined,
dispatch => ({
onElementAdded: text => dispatch(todoAdded(text)),
}),
)(Form);
const ReminderForm = connect(
undefined,
dispatch => ({
onElementAdded: text => dispatch(reminderAdded(text)),
}),
)(Form);
const List = ({ elements, onElementDelete }) => {
const handleClick = id => event => {
event.preventDefault();
onElementDelete(id);
};
return (
<ul>
{
elements.map(({ id, text}) => (
<li key={id}>
{text} [<a href="#" onClick={handleClick(id)}>x</a>]
</li>
))
}
</ul>
);
};
const Todos = connect(
state => ({
elements: getAppTodos(state),
}),
dispatch => ({
onElementDelete: id => dispatch(todoDeleted(id)),
}),
)(List);
const Reminders = connect(
state => ({
elements: getAppReminders(state),
}),
dispatch => ({
onElementDelete: id => dispatch(reminderDeleted(id)),
}),
)(List);
const App = () => (
<Provider store={store}>
<div className="app-wrapper">
<h1>Todos</h1>
<TodoForm />
<Todos />
<h1>Reminders</h1>
<ReminderForm />
<Reminders />
</div>
</Provider>
);
ReactDOM.render(<App />, document.getElementById('root'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment