Skip to content

Instantly share code, notes, and snippets.

@tongrhj
Last active August 30, 2016 05:17
Show Gist options
  • Save tongrhj/c92bae8914d21e2630164577b49d3a1d to your computer and use it in GitHub Desktop.
Save tongrhj/c92bae8914d21e2630164577b49d3a1d to your computer and use it in GitHub Desktop.
Getting Started With Redux
// Practice Exercise to Accompany Egghead Course by Dan Abramov Getting Started with Redux
const alphabets = Array.from('abcdefghijklmnopqrstuvwxyz')
const abcSnake = (state = ['a'], action) => {
deepFreeze(state)
switch (action.type) {
case 'APPEND':
return appendToSnake(state)
case 'REMOVE':
return removeFromSnake(state);
case 'MODIFY':
return modifySnake(state, action.newValue)
default:
return state;
}
}
const appendToSnake = (snake) => {
deepFreeze(snake)
newABCIndex = alphabets.findIndex((elm) => {
return elm == snake.slice(-1)
}) + 1
while (newABCIndex >= 26) { newABCIndex -= 26 }
// return snake.concat(alphabets[newABCIndex])
return [...snake, alphabets[newABCIndex]].join('')
}
const removeFromSnake = (snake) => {
deepFreeze(snake)
return snake.slice(0, -1)
}
const modifySnake = (snake, newValue) => {
return [...snake, ...newValue]
}
const AbcSnake = ({
value,
onAppend,
onRemove,
onModify
}) =>
(
<div>
<h1>{value}</h1>
<button onClick={onAppend}>+</button>
<button onClick={onRemove}>-</button>
<input onBlur={onModify} type="text" id='snake-input'></input>
</div>
)
const { createStore } = Redux;
const store = createStore(abcSnake);
const render = () => {
ReactDOM.render(
<AbcSnake
value = {store.getState()}
onAppend = {() => {
store.dispatch({ type: 'APPEND' })
}}
onRemove = {() => {
store.dispatch({ type: 'REMOVE' })
}}
onModify = {() => {
store.dispatch({ type: 'MODIFY',
newValue: document.getElementById('snake-input').value
})
}}
/>,
document.getElementById('app')
)
}
store.subscribe(render)
render()
// TESTS
expect(abcSnake('a', { type: 'APPEND' })).toEqual('ab')
expect(abcSnake('abcdefghijklmnopqrstuvwxyz', { type: 'APPEND' })).toEqual('abcdefghijklmnopqrstuvwxyza')
expect(abcSnake('ab', { type: 'REMOVE' })).toEqual('a')
expect(abcSnake('a', { type: 'REMOVE' })).toEqual('')
expect(abcSnake('', { type: 'APPEND' })).toEqual('a')
expect(abcSnake('', { type: 'REMOVE' })).toEqual('')
expect(abcSnake(undefined, {})).toEqual('a')
console.log('Tests passed!')
const processTodo = (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,
processTodo(undefined, action)
]
case 'TOGGLE_TODO':
return state.map(t => {
return processTodo(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 getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_ACTIVE':
return todos.filter((todo) => {
return !todo.completed
})
case 'SHOW_COMPLETED':
return todos.filter((todo) => {
return todo.completed
})
default:
return todos
}
}
// What Combine Reducer replaces
// const todoApp = (state = {}, action) => {
// return {
// todos: todos(state.todos, action),
// visibilityFilter: visibilityFilter(state.visibilityFilter, action)
// }
// }
// What Combine Reducer does
// const combineReducers = (reducers) => {
// return (state = {}, action) => {
// return Object.keys(reducers).reduce(
// (nextState, key) => {
// nextState[key] = reducers[key](state[key], action)
// return nextState
// }, {}
// )
// }
// }
const { combineReducers } = Redux;
const todoApp = combineReducers({
// todos: todos,
// visibilityFilter: visibilityFilter
// ES6 Object literal shorthand means the above is
todos,
visibilityFilter
})
const { Component } = React
const TodoListItem = (
{handleClick, completed, text}
) => {
return (
<li onClick={ handleClick }
style={{
textDecoration: completed ?
'line-through' :
'none'
}}
>
{ text }
</li>
)
}
const TodoList = ({ todos, handleTodoClick }) =>
(
<ul>
{todos.map(todo =>
<TodoListItem
key={todo.id}
{...todo}
handleClick={() => handleTodoClick(todo.id)}
/>
)}
</ul>
)
const mapStateToListProps = (state) => {
return {
todos: getVisibleTodos(
state.todos,
state.visibilityFilter
)
}
}
const mapDispatchToListProps = (dispatch) => {
return {
handleTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id
}
}
const { connect } = ReactRedux
const VisibleTodoList = connect(mapStateToListProps, mapDispatchToListProps)(TodoList)
// What connect ()() creates: (Also see Addtodo and FilterLink)
// class VisibleTodoList extends Component {
// componentDidMount() {
// const { store } = this.context
// 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) }
// />
// )
// }
// }
// VisibleTodoList.contextTypes = {
// store: React.PropTypes.object
// }
let nextTodoId = 0;
const addTodo = (text) => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}
const AddTodoWIP = ({ dispatch }) => {
let input;
return (
<div>
<input ref= { node => {
input = node
}} />
<button onClick={() => {
dispatch(addTodo(input.value))
input.value = ''
}}>
Add Todo
</button>
</div>
)
}
// AddTodo.contextTypes = {
// store: React.PropTypes.object
// }
let AddTodo = connect()(AddTodoWIP)
const Link = ({
active,
children,
handleClick
}) => {
if (active) {
return <span>{children}</span>;
}
return (
<a href='#'
onClick={e => {
e.preventDefault();
handleClick();
}}
>
{children}
</a>
)
}
// Extracting Container Components
const mapStatetoLinkProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
const mapDispatchToLinkProps = (dispatch, ownProps) => {
return {
handleClick: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
}
}
const setVisibilityFilter = (filter) => {
return {
type: 'SET_VISIBILITY_FILTER',
filter
}
}
const FilterLink = connect(
mapStatetoLinkProps,
mapDispatchToLinkProps)(Link)
// What connect()() creates:
// class FilterLink extends Component {
// componentDidMount() {
// const { store } = this.context
// 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 }
// handleClick={ (filter) => {
// store.dispatch({
// type: 'SET_VISIBILITY_FILTER',
// filter: props.filter
// })
// }}
// >
// { props.children }
// </Link>
// )
// }
// }
// FilterLink.contextTypes = {
// store: React.PropTypes.object
// }
const FilterLinks = () => {
return (
<p>
SHOW:
{' '}
<FilterLink filter='SHOW_ALL'>ALL</FilterLink>
{', '}
<FilterLink filter='SHOW_ACTIVE'>ACTIVE</FilterLink>
{', '}
<FilterLink filter='SHOW_COMPLETED'>COMPLETED</FilterLink>
{' '}
</p>
)
}
const TodoApp = () => (
<div>
<AddTodo />
<VisibleTodoList />
<FilterLinks />
</div>
)
const { createStore } = Redux
const { Provider } = ReactRedux
// We want to avoid doing this so
// 1) its easier to test our methods by passing mock stores
// 2) we can use different stores on our backend for each request
// const store = createStore(todoApp);
ReactDOM.render(
<Provider store={createStore(todoApp)}>
<TodoApp />
</Provider>,
document.getElementById('app')
)
const testAddTodo = () => {
const stateBefore = [];
const action = {
id: 0,
type: 'ADD_TODO',
text: 'Learn Redux',
completed: false
}
const stateAfter =[{
id: 0,
text: 'Learn Redux',
completed: false
}]
deepFreeze(stateBefore)
deepFreeze(action)
expect(
todos(stateBefore, action)
).toEqual(stateAfter)
}
const testToggleTodo = () => {
const stateBefore = [{
id: 0,
text: 'Learn Redux',
completed: false,
},{
id: 1,
text: 'Go pokemon hunting',
completed: false,
}]
const action = {
type: 'TOGGLE_TODO',
id: 1
}
deepFreeze(stateBefore)
deepFreeze(action)
const stateAfter = [stateBefore[0],
Object.assign({},
stateBefore[1],
{ completed: true }
)]
expect(
todos(stateBefore, action)
).toEqual(stateAfter)
}
const testVisibilityFilter = () => {
const mock_todo = [{
id: 0,
text: 'Learn Redux',
completed: false,
},{
id: 1,
text: 'Go pokemon hunting',
completed: false,
}]
const action = {
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
}
const stateBefore = {
todos: mock_todo,
visibilityFilter: 'SHOW_ALL'
}
deepFreeze(mock_todo)
deepFreeze(stateBefore)
deepFreeze(action)
const stateAfter = {
todos: mock_todo,
visibilityFilter: 'SHOW_COMPLETED'
}
expect(
todoApp(stateBefore, action)
).toEqual(stateAfter)
}
testAddTodo()
testToggleTodo()
testVisibilityFilter()
console.log('All tests passed.')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment