Skip to content

Instantly share code, notes, and snippets.

@jsteenkamp
Last active March 11, 2016 01:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsteenkamp/b7a75421c953f8b8c933 to your computer and use it in GitHub Desktop.
Save jsteenkamp/b7a75421c953f8b8c933 to your computer and use it in GitHub Desktop.
Commented EggHead Redux Example
// Reducers are prue functions that implement the update logic of the applications
// Reducers define how to calculate the next State from the current State and the dispatched Action
// Reducer function returns the current State if not Action is performed.
// Redux passes the new State to each component and React re-renders them.
// Reducers are typically called by Container components. Container components do not emit DOM (so they
// do not have any associated CSS)
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
};
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
...state,
completed: !state.completed
};
default:
return state;
}
};
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
case 'TOGGLE_TODO':
return state.map(t =>
todo(t, action)
);
default:
return state;
}
};
const visibilityFilter = (
state = 'SHOW_ALL',
action
) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
};
// Reducer compositon - create smaller reducers focussed on a specifci concern
// Each reducer specifies how different parts of the state tree are updated in response to actions
// Use the convention of using the same name for the reducer and it's corresponding state key
// then you can use ES6 object literal shorthand notation
const { combineReducers } = Redux;
const todoApp = combineReducers({
todos,
visibilityFilter
});
// Actions describe what happens in the application.
// Action Creators formalize Actions into reusable JSON action objects that can be dispatched
// by other components if required. The Action Creator must return an object with type key/value.
// An action is the minimal representation of the change to the application state (data).
// Any data that gets into a Redux application gets there by actions.
// The structure of the action object is up to you. The only requirement is that it has a type property,
// which is not undefined. We suggest using strings, because they are serializable.
// You can also use ES6 Symbols:
// const ADD_TODO = Symbol('ADD_TODO');
// and then use type: ADD_TODO
let nextTodoId = 0;
const addTodo = (text) => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
};
};
const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id
};
};
const setVisibilityFilter = (filter) => {
return {
type: 'SET_VISIBILITY_FILTER',
filter
};
};
// Components
// Provider and connect are used to bind Redux and React, Provider sets up the store
// and connect is used to subscribe to the store state and dispatch update actions
const { Provider, connect } = ReactRedux;
const Link = ({
active,
children,
onClick
}) => {
if (active) {
return <span>{children}</span>;
}
return (
<a href='#'
onClick={e => {
e.preventDefault();
onClick();
}}
>
{children}
</a>
);
};
const mapStateToLinkProps = (
state,
ownProps
) => {
return {
active:
ownProps.filter ===
state.visibilityFilter
};
};
const mapDispatchToLinkProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch(
setVisibilityFilter(ownProps.filter)
);
}
};
}
// connect() connects a React component to a Redux store.
// It does not modify the component class passed to it.
// Instead, it returns a new, connected component class, for you to use.
// connect() generates a container component that wraps the component we pass in
// and injects subscribe and dispatch objects. If we do not pass properties (null)
// the connect(null, null) default is not to subscribe to store and to pass dispatch as a prop
// By using connect() we do not have to manually subscribe/unsubscribe/dispatch and use the
// React componentDidMount() and componentWillUnmount() methods
const FilterLink = connect(
mapStateToLinkProps,
mapDispatchToLinkProps
)(Link);
const Footer = () => (
<p>
Show:
{' '}
<FilterLink filter='SHOW_ALL'>
All
</FilterLink>
{', '}
<FilterLink filter='SHOW_ACTIVE'>
Active
</FilterLink>
{', '}
<FilterLink filter='SHOW_COMPLETED'>
Completed
</FilterLink>
</p>
);
const Todo = ({
onClick,
completed,
text
}) => (
<li
onClick={onClick}
style={{
textDecoration:
completed ?
'line-through' :
'none'
}}
>
{text}
</li>
);
// Note that here we use let since we reuse this component after wrapping
// it in a container with connect() and reuse the component. We can do this as we
// do not need to map state and just need access to dispatch.
// Using connect() = connect(null, null) = connect(null, dispatch)
// so default is not to subscribe to store and pass dispatch as property
let AddTodo = ({ dispatch }) => {
let input;
return (
<div>
<input ref={node => {
input = node;
}} />
<button onClick={() => {
dispatch(addTodo(input.value));
input.value = '';
}}>
Add Todo
</button>
</div>
);
};
AddTodo = connect()(AddTodo);
const getVisibleTodos = (
todos,
filter
) => {
switch (filter) {
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(
t => t.completed
);
case 'SHOW_ACTIVE':
return todos.filter(
t => !t.completed
);
}
}
const TodoList = ({
todos,
onTodoClick
}) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
);
const mapStateToTodoListProps = (
state
) => {
return {
todos: getVisibleTodos(
state.todos,
state.visibilityFilter
)
};
};
const mapDispatchToTodoListProps = (
dispatch
) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id));
}
};
};
const VisibleTodoList = connect(
mapStateToTodoListProps,
mapDispatchToTodoListProps
)(TodoList);
// main app component that is inserted into DOM
const TodoApp = () => (
<div>
<AddTodo />
<VisibleTodoList />
<Footer />
</div>
);
const { createStore } = Redux;
ReactDOM.render(
<Provider store={createStore(todoApp)}>
<TodoApp />
</Provider>,
document.getElementById('root')
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment