Skip to content

Instantly share code, notes, and snippets.

@gustavoguichard
Last active September 18, 2018 03:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gustavoguichard/2f1d97aea7829a506e1a to your computer and use it in GitHub Desktop.
Save gustavoguichard/2f1d97aea7829a506e1a to your computer and use it in GitHub Desktop.
Minimal todo app with Redux from scratch
const { Component } = React
// Model
let nextTodoID = 0
// Update
const setVisibilityFilter = filter => ({
type: 'SET_VISIBILITY_FILTER',
filter,
})
const toggleTodo = id => ({
type: 'TOGGLE_TODO',
id,
})
const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoID++,
text,
})
const todo = (state, action) => {
switch(action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false,
}
case 'TOGGLE_TODO':
return action.id === state.id ?
{ ...state, completed: !state.completed } :
state
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) => {
return action.type === 'SET_VISIBILITY_FILTER' ?
action.filter :
state
}
// View
const Todos = () => (
<div>
<AddTodo />
<VisibleTodoList />
<Footer />
</div>
)
const Todo = ({ text, completed, onClick }) => (
<li
onClick={onClick}
style={{ textDecoration: completed ?
'line-through' : 'none'
}}
>{text}</li>
)
const TodosList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
const AddTodo = (props, { store }) => {
let input
return (
<div>
<input ref={node => { input = node }} />
<button
onClick={() => {
store.dispatch(addTodo(input.value))
input.value = ""
}}>
Add
</button>
</div>
)
}
AddTodo.contextTypes = {
store: React.PropTypes.object,
}
const Link = ({ active, onClick, children }) => active ? <span>{children}</span> : (
<a
href="#"
onClick={onClick}
>{children}</a>
)
class FilterLink extends Component {
static contextTypes = {
store: React.PropTypes.object,
}
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => this.forceUpdate())
}
componentWillUnMount() {
this.unsubscribe()
}
render() {
const { children, filter } = this.props
const { visibilityFilter } = this.context.store.getState()
return (
<Link
active={visibilityFilter === filter}
onClick={e => {
e.preventDefault()
this.context.store.dispatch(setVisibilityFilter(filter))
}}
>{children}</Link>
)
}
}
class VisibleTodoList extends Component {
static contextTypes = {
store: React.PropTypes.object,
}
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => this.forceUpdate())
}
componentWillUnMount() {
this.unsubscribe()
}
filterTodos(todos, filter) {
switch(filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
}
}
render() {
const { todos, visibilityFilter } = this.context.store.getState()
return (
<TodosList
todos={this.filterTodos(todos, visibilityFilter)}
onTodoClick={id => {
this.context.store.dispatch(toggleTodo(id))
}} />
)
}
}
const Footer = () => (
<p>
Filters:
{' '}
<FilterLink
filter='SHOW_ALL'
>Show All</FilterLink>
{' '}
<FilterLink
filter='SHOW_ACTIVE'
>Active</FilterLink>
{' '}
<FilterLink
filter='SHOW_COMPLETED'
>Completed</FilterLink>
</p>
)
// Set Up
class Provider extends Component {
static childContextTypes = {
store: React.PropTypes.object
}
getChildContext() {
return {
store: this.props.store
}
}
render() {
return this.props.children
}
}
const combineReducers = reducers => (
(state, action) => (
Object.keys(reducers).reduce(
(nextState, key) => {
nextState[key] = reducers[key](
state[key], action
)
return nextState
}, {}
)
)
)
const createStore = reducer => {
let state
let listeners = []
try {
state = JSON.parse(localStorage.getItem('todos'))
} catch(e){
state = {}
}
const getState = () => state
const dispatch = action => {
state = reducer(state, action)
console.log(state)
localStorage.setItem('todos', JSON.stringify(state))
listeners.forEach(f => f())
}
const subscribe = listener => {
listeners = [ ...listeners, listener ]
return () => {
listeners = listeners.filter(l => l !== listener)
}
}
dispatch({})
return { getState, dispatch, subscribe }
}
const todoApp = combineReducers({ todos, visibilityFilter })
ReactDOM.render(<Provider store={createStore(todoApp)}><Todos /></Provider>, document.getElementById('root'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment