Last active
May 23, 2016 04:15
-
-
Save fmontes/536a9856e7ae25e6f645e3061d77a441 to your computer and use it in GitHub Desktop.
Egghead.io redux course | https://jsbin.com/bixigo/edit?html,js,output
This file contains hidden or 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
<!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> |
This file contains hidden or 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
/************************************ | |
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