Skip to content

Instantly share code, notes, and snippets.

@fmontes
Last active May 23, 2016 04:15
Show Gist options
  • Save fmontes/536a9856e7ae25e6f645e3061d77a441 to your computer and use it in GitHub Desktop.
Save fmontes/536a9856e7ae25e6f645e3061d77a441 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Redux examples">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://wzrd.in/standalone/expect@latest"></script>
<script src="https://wzrd.in/standalone/deep-freeze@latest"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.5.2/redux.js"></script>
<script src="https://fb.me/react-15.0.2.js"></script>
<script src="https://fb.me/react-dom-15.0.2.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
/************************************
UTILITY FUNCTIONS
************************************/
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);
}
};
/************************************
ACTIONS CREATORS
************************************/
var nextId = 0;
const addTodo = (text) => {
return {
type: 'ADD_TODO',
id: nextId++,
text: text
}
};
const setVisibilityFilter = (filter) => {
return {
type: 'SET_VISIBILITY_FILTER',
filter: filter
}
};
const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id
}
};
/************************************
REDUCERS
************************************/
// Reduce function example
// A reducer is pure function to calculate the next state
// given the old state and the action.
// Pure fuction will NOT mutate the state.
// Extracted reducer
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;
}
// NOT mutate the object, return a new one
return Object.assign({}, state, {completed: !state.completed});
// Object spreads operators ES7
// return {
// ...todo,
// completed: !todo.completed
// };
default:
return state;
}
};
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
// NOT mutate the array, return a new one
// return state.concat([todo(state, action)])
// Array spreads operators ES6
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;
}
};
const { combineReducers } = Redux;
const todoApp = combineReducers({
todos,
visibilityFilter
});
// Reducer composition pattern to combine reducers
// MANUALLY COMBINED REDUCERS (Redux provide combineReducers method)
// const todoApp = (state = {}, action) => {
// return {
// todos: todos(state.todos, action),
// visibilityFilter: visibilityFilter(state.visibilityFilter, action)
// }
// }
/************************************
COMPONENTS
-- Containers components
-- Presentarional components
************************************/
const { Component } = React;
/*****************************************************
-- Presentational components: how the component looks
*****************************************************/
const Link = ({
active,
children,
onClick
}) => {
if (active) {
return (
<span>{children}</span>
)
}
return (
<a href="#" onClick={onClick}>{children}</a>
)
};
const Todo = ({
onClick,
completed,
text
}) => {
return (
<li onClick={onClick} style={{textDecoration: + completed ? "line-through" : "none"}}>{text}</li>
)
};
const TodoList = ({todos, onTodoClick}) => {
return (
<ul>
{todos.map(todo =>
<Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} />
)}
</ul>
)
};
const Footer = () => {
return (
<p>
Show: {' '}
<FilterLink filter="SHOW_ALL">All</FilterLink>
{' '}
<FilterLink filter="SHOW_ACTIVE">Active</FilterLink>
{' '}
<FilterLink filter="SHOW_COMPLETED">Completed</FilterLink>
</p>
)
};
/*****************************************************
-- Functional components:
You can created hand made or using .connect() method
from ReactRedux to generate container components
*****************************************************/
const { connect } = ReactRedux;
const mapStateToLinkProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
};
const mapDispatchToLinkProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
}
};
const FilterLink = connect(mapStateToLinkProps, mapDispatchToLinkProps)(Link);
// Handmade container component
// class FilterLink extends Component {
// componentDidMount() {
// const { store } = this.context;
// // subscribe() method return the unsubcribe() method
// this.unsubscribe = store.subscribe(() =>
// this.forceUpdate()
// )
// }
// componentWillUnmount() {
// this.unsubscribe()
// }
// render() {
// const props = this.props;
// const { store } = this.context;
// const state = store.getState();
// return (
// <Link active={props.filter === state.visibilityFilter}
// onClick={() => {
// store.dispatch({
// type: 'SET_VISIBILITY_FILTER',
// filter: props.filter
// })
// }} >
// {props.children}
// </Link>
// )
// }
// }
// // In the child component we need to defined what context are we going to receive
// FilterLink.contextTypes = {
// store: React.PropTypes.object
// }
let AddTodo = ({ dispatch }) => {
let inputField;
return (
<div>
<input type="text" ref={node => {inputField = node}} />
<button onClick={() => {
dispatch(addTodo(inputField.value));
inputField.value = ''
}}>Add todo</button>
</div>
)
};
// The default behaviour of "connect" is not to subscribe to the store and pass the dispatch as a prop to the component that is wrapping (video 28)
AddTodo = connect()(AddTodo);
// Map the redux store state to the props of the TodoList component that is related to the data from the redux store
const mapStateToTodoListProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
};
// Maps the dispatch method of the store to the callback props of TodoList component specify the behaviour that is which callback props dispatch which actions
const mapDispatchToTodoListProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
};
// Generating the container component that render the presentational component
// Props will be passed to TodoList
const VisibleTodoList = connect(mapStateToTodoListProps, mapDispatchToTodoListProps)(TodoList);
// Creating the container component from scratch
// class VisibleTodoList extends Component {
// componentDidMount() {
// const { store } = this.context;
// // subscribe() method return the unsubcribe() method
// this.unsubscribe = store.subscribe(() =>
// this.forceUpdate()
// )
// }
// componentWillUnmount() {
// this.unsubscribe()
// }
// render() {
// const props = this.props;
// const { store } = this.context;
// const state = store.getState();
// return (
// <TodoList todos={getVisibleTodos(state.todos, state.visibilityFilter)}
// onTodoClick={id =>
// store.dispatch({
// type: 'TOGGLE_TODO',
// id
// })
// }
// />
// )
// }
// }
// In the child component we need to defined what context are we going to receive
// VisibleTodoList.contextTypes = {
// store: React.PropTypes.object
// }
// Container components
const TodoApp = () => (
<div>
<AddTodo />
<VisibleTodoList />
<Footer />
</div>
);
// Al containers components are pretty similar, they need to re-render when the store state changes, they need to unsubscribe from the store when they are unmount and they take the current state of the redux store and use it to render the presentational component with some props that they calculate from the state of the store and they also need to specify the context types to get the store from the context.
// class Provider extends Component {
// // Special method to make the store accesible in the context
// getChildContext() {
// return {
// store: this.props.store
// };
// }
// render() {
// return this.props.children;
// }
// }
// DON'T FORGET: This will "turn on" the context and we are defining the context we want to pass down to children and grandchildren
// Provider.childContextTypes = {
// store: React.PropTypes.object
// };
const { createStore } = Redux;
const { Provider } = ReactRedux;
ReactDOM.render(
<Provider store={createStore(todoApp)}>
<TodoApp />
</Provider>,
document.getElementById("root")
);
/************************************
UNIT TEST FOR REDUCERS
************************************/
const testaddTodo = () => {
const stateBefore = [];
const action = {
type: 'ADD_TODO',
id: 0,
text: 'This is a todo'
};
const stateAfter = [{
id: 0,
text: 'This is a todo',
completed: false
}];
deepFreeze(stateBefore);
deepFreeze(action);
expect(
todos(stateBefore, action)
).toEqual(stateAfter)
};
//testaddTodo();
//console.log('PASS: test add todo reducer');
const testToggleTodo = () => {
const stateBefore = [
{
id: 0,
text: 'This is a todo',
completed: false
},
{
id: 1,
text: 'This is other todo',
completed: false
}
];
const action = {
type: 'TOGGLE_TODO',
id: 1
};
const stateAfter = [
{
id: 0,
text: 'This is a todo',
completed: false
},
{
id: 1,
text: 'This is other todo',
completed: true
}
];
deepFreeze(stateBefore);
deepFreeze(action);
expect(
todos(stateBefore, action)
).toEqual(stateAfter)
};
//testToggleTodo();
//console.log('PASS: test toggle todo reducer');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment