https://github.com/timwis/choo-workshop
choo: the good parts of react & redux without the boilerplate
https://github.com/timwis/choo-workshop
Last synced: 25 days ago
JSON representation
choo: the good parts of react & redux without the boilerplate
- Host: GitHub
- URL: https://github.com/timwis/choo-workshop
- Owner: timwis
- Created: 2016-11-12T05:00:41.000Z (over 8 years ago)
- Default Branch: gh-pages
- Last Pushed: 2016-11-12T15:42:45.000Z (over 8 years ago)
- Last Synced: 2025-03-25T08:42:50.653Z (about 1 month ago)
- Language: JavaScript
- Homepage: http://timwis.com/choo-workshop/
- Size: 6.84 KB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# choo: the good parts of react & redux without the boilerplate
Tim Wisniewski
Chief Data Officer, City of Philadelphia
@timwis -- [email protected]
Repo: github.com/yoshuawuyts/choo
---
## Outline1. Quick poll
2. What you'll get out of this session
3. Why choo?
4. Dependencies
5. Your first app---
## Quick poll1. Software devs?
2. Built an app in JS?
3. Used react or redux?---
## What you'll get out of this session- Build a basic todo app in a modern, functional front-end framework
- We'll cover 90% of the library's footprint
- The whole workshop is written down so you can pick it up later---
## Why choo?```javascript
const choo = require('choo')
const html = require('choo/html')
const app = choo()app.model({
state: { title: 'Not quite set yet' },
reducers: {
update: (data, state) => ({ title: data })
}
})const mainView = (state, prev, send) => html`
Title: ${state.title}
send('update', e.target.value)}>
`app.router((route) => [
route('/', mainView)
])const tree = app.start()
document.body.appendChild(tree)
```???
- 5kb
- FUNctional (transparent side effects, immutable, uni-directional data flow)
- Small api footprint (easy to remember)
- Minimal tooling
- Just JavaScript---
## Assumptions- You have NodeJS w/npm
- Concept of **models** and **views** (as in MVC)
- Okay with a couple new ES6 features (const, arrow functions, template strings)### Backup plan
http://c9.io
---
## Your first app1. github.com/yoshuawuyts/choo
2. Click **Handbook**
3. Click **02 your first app**https://yoshuawuyts.gitbooks.io/choo/content/02_your_first_app.html
---
### Boilerplate```bash
npm init --yes
``````bash
npm install --save choo
```---
### Rendering data```javascript
const choo = require('choo')
const html = require('choo/html')
const app = choo()app.model({
state: {
todos: [
{ title: 'Buy milk' },
{ title: 'Call mum' }
]
}
})const view = (state, prev, send) => {
return html`
`
Todos
${state.todos.map((todo) => html`- ${todo.title}
`)}
}app.router((route) => [
route('/', view)
])const tree = app.start()
document.body.appendChild(tree)
```???
- Models are where **state** is contained and where methods for
updating the state are defined
- Views are just functions that return a DOM tree of elements.
They are passed the current state, the previous state, and a
callback function that can be used to change the state.
- `1.js`---
### Running the app```bash
npm install --global budo
``````bash
budo index.js --live --open
```
or on **c9.io**:
```bash
budo index.js --port $PORT
``````
http://workspace-username.c9users.io
```---
### Adding items```javascript
app.model({
state: {
todos: []
},
reducers: {
addTodo: (data, state) => {
const newTodos = state.todos.slice()
newTodos.push(data)
return { todos: newTodos }
}
}
})
``````javascript
const view = (state, prev, send) => {
return html`
`
{
send('addTodo', { title: e.target.children[0].value })
e.preventDefault()
}}>
${state.todos.map((todo) => html`- ${todo.title}
`)}
}
```???
- First thought might be to `.push()` into `state.todos`
- Immutability makes a copy, alters it, returns the copy
- Allows us to compare state over time
- Try it out! Woah!
- `2.js`---
### Adding items```javascript
const view = (state, prev, send) => {
return html`
`
{
const input = e.target.children[0]
send('addTodo', { title: input.value })
input.value = ''
e.preventDefault()
}}>
${state.todos.map((todo) => html`- ${todo.title}
`)}
}
```Yikes...
---
### Adding items```javascript
const view = (state, prev, send) => {
return html`
`
${state.todos.map((todo) => html`- ${todo.title}
`)}
function onSubmit (e) {
const input = e.target.children[0]
send('addTodo', { title: input.value })
input.value = ''
e.preventDefault()
}
}
```Ah, that's better.
???
- Notice it doesn't reset the text in your input
- `3.js`---
### Completion status```bash
npm install xtend
``````javascript
const extend = require('xtend')
const choo = require('choo')
const html = require('choo/html')
const app = choo()
```---
### Completion status```javascript
app.model({
state: {
todos: []
},
reducers: {
addTodo: (data, state) => {
const todo = extend(data, {
completed: false
})
const newTodos = state.todos.slice()
newTodos.push(todo)
return { todos: newTodos }
}
}
})
```???
- Now new items will be stored as `{ title: 'Our title', complete: false }`
---
### Completion status```javascript
const view = (state, prev, send) => {
return html`
`
${state.todos.map((todo) => html`
- `)}
${todo.title}
function onSubmit (e) {
. . .
}
```???
- You'll notice, though, that adding new items resets the checkboxes
because checking them doesn't do anything
- `4.js`---
### Completion status```javascript
const view = (state, prev, send) => {
return html`
`
${state.todos.map((todo, index) => html`
- `)}
{
const updates = { completed: e.target.checked }
send('updateTodo', { index: index, updates: updates })
}} />
${todo.title}
function onSubmit (e) {
. . .
}
```???
- State stores todos as an array
- To update, we need to know index
- Here we pass `index` and `updates` to reducer---
### Completion status```javascript
app.model({
state: {
todos: []
},
reducers: {
addTodo: (data, state) => {
// ...
},
updateTodo: (data, state) => {
const newTodos = state.todos.slice()
const oldItem = newTodos[data.index]
const newItem = extend(oldItem, data.updates)
newTodos[data.index] = newItem
return { todos: newTodos }
}
}
})
```???
- Here we create the reducer to update the state,
making a copy and returning it
- Now your app maintains completed state, but refreshing
will lose all items
- `5.js`---
### Effects- Effects are similar to reducers except instead of modifying
the state they cause side effects by interacting servers,
databases, DOM APIs, etc. Often they'll call a reducer when
they're done to update the state.#### Example
```javascript
{
state: {
users: []
},
reducers: {
receiveUsers: (data, state) => {
return { users: data }
}
},
effects: {
fetchUsers: (data, state, send, done) => {
http('api.com/users', (err, response, body) => {
send('receiveUsers', body.users, done)
})
}
}
}
```---
### Effects```javascript
// localStorage wrapper
const store = {
getAll: (storeName, cb) => {
try {
cb(JSON.parse(window.localStorage[storeName]))
} catch (e) {
cb([])
}
},
add: (storeName, item, cb) => {
store.getAll(storeName, (items) => {
items.push(item)
window.localStorage[storeName] = JSON.stringify(items)
cb()
})
},
. . .
```https://yoshuawuyts.gitbooks.io/choo/content/02_your_first_app.html
---
### Effects```javascript
app.model({
state: {
todos: []
},
reducers: {
receiveTodos: (data, state) => {
return { todos: data }
}
// ...
},
effects: {
getTodos: (data, state, send, done) => {
store.getAll('todos', (todos) => {
send('receiveTodos', todos, done)
})
}
}
})
```???
- We'll use a method from the snippet called `getAll`
- Once it completes, we'll use `send()` to pass the data to a reducer
- Note third param, `done`, which allows effects to be chained together---
### Effects```javascript
const view = (state, prev, send) => {
return html`
send('getTodos')}>`
// ...
function onSubmit (e) {
// ...
}
``````javascript
localStorage.todos = '[{"title": "Test", "completed": false}]'
```???
- Now we'll trigger `getTodos` when our view is rendered
- `6.js`---
### Effects```javascript
app.model({
state: {
todos: []
},
reducers: {
receiveTodos: (data, state) => {...},
receiveNewTodo: (data, state) => {
const newTodos = state.todos.slice()
newTodos.push(data)
return { todos: newTodos }
}
},
effects: {
getTodos: (data, state, send, done) => {...},
addTodo: (data, state, send, done) => {
const todo = extend(data, {
completed: false
})store.add('todos', todo, () => {
send('receiveNewTodo', todo, done)
})
}
}
})
```???
- We want `addTodo` to interact w/localStorage as well, so we'll
replace it with an effect and add a reducer to receive its data
- Similar to before: we split functionality and added a side effect---
### Effects```javascript
app.model({
state: {
todos: []
},
reducers: {
receiveTodos: (data, state) => {...},
receiveNewTodo: (data, state) => {...},
replaceTodo: (data, state) => {
const newTodos = state.todos.slice()
newTodos[data.index] = data.todo
return { todos: newTodos }
}
},
effects: {
getTodos: (data, state, send, done) => {...},
addTodo: (data, state, send, done) => {...},
updateTodo: (data, state, send, done) => {
const oldTodo = state.todos[data.index]
const newTodo = extend(oldTodo, data.updates)store.replace('todos', data.index, newTodo, () => {
send('replaceTodo', { index: data.index, todo: newTodo }, done)
})
}
}
})
```???
- Let's do the same for `updateTodo`
- When you call `send` it looks for reducers _and_ effects by that name,
so our view should already be wired up.
- You should not be able to add items, mark them complete, and refresh
- `7.js`---
### This presentationtimwis.com/choo-workshop
Oh, and we're hiring!
* Data Engineer
* Front-end / WordPress Developer
* Product Manager for beta.phila.govEmail me: [email protected]