Skip to content

Instantly share code, notes, and snippets.

@saitoxu
Last active Apr 16, 2017
Embed
What would you like to do?
2017-04-16
export const REQUEST_REPOS = 'REQUEST_REPOS'
export const RECEIVE_REPOS = 'RECEIVE_REPOS'
export function fetchReposIfNeeded(username) {
return (dispatch, getState) => {
if (shouldFetchRepos(getState(), username)) {
return dispatch(fetchRepos(username))
}
}
}
function fetchRepos(username) {
return (dispatch) => {
dispatch(requestRepos(username))
return fetch(`https://api.github.com/users/${username}/repos`)
.then((response) => {
return response.json()
})
.then((json) => {
if (json.message) {
throw Error(json.message)
}
return dispatch(receiveRepos(username, json))
})
.catch((err) => {
return dispatch(receiveRepos(username, []))
})
}
}
function requestRepos(username) {
return {
type: REQUEST_REPOS,
username
}
}
function receiveRepos(username, json) {
return {
type: RECEIVE_REPOS,
username,
repos: json.map(child => {
return { name: child.name }
})
}
}
function shouldFetchRepos(state, username = '') {
const currentUsername = state.username
const isFetching = state.isFetching
if (username.length === 0 || currentUsername === username || isFetching) {
return false
}
return true
}
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchReposIfNeeded } from '../actions'
import Form from '../components/Form'
import Repos from '../components/Repos'
class AsyncApp extends Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit(nextUser) {
this.props.dispatch(fetchReposIfNeeded(nextUser))
}
render() {
const { username, isFetching, repos } = this.props
return (
<div>
<Form value={username} onSubmit={this.handleSubmit} />
{isFetching && repos.length === 0 &&
<h2>Loading...</h2>
}
{!isFetching && repos.length === 0 &&
<h2>Empty.</h2>
}
{repos.length > 0 &&
<div style={{ opacity: isFetching ? 0.5 : 1 }}>
<Repos repos={repos} />
</div>
}
</div>
)
}
}
function mapStateToProps(state) {
const { reposByUser } = state
const { username, isFetching, repos } = reposByUser
return {
username,
isFetching,
repos
}
}
export default connect(mapStateToProps)(AsyncApp)
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from './reducers'
export default function configureStore(preloadedState) {
return createStore(
rootReducer,
preloadedState,
applyMiddleware(
thunkMiddleware
)
)
}
import React, { Component } from 'react'
export default class Form extends Component {
render() {
const { value, onSubmit } = this.props
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
onSubmit(input.value)
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Fetch
</button>
</form>
<h2>{value}</h2>
</div>
)
}
}
import { combineReducers } from 'redux'
import {
REQUEST_REPOS,
RECEIVE_REPOS
} from './actions'
function reposByUser(state = { username: '', isFetching: false, repos: [] }, action) {
switch (action.type) {
case REQUEST_REPOS:
return Object.assign({}, state, {
username: action.username,
isFetching: true
})
case RECEIVE_REPOS:
return Object.assign({}, state, {
isFetching: false,
repos: action.repos
})
default:
return state
}
}
const rootReducer = combineReducers({
reposByUser
})
export default rootReducer
import React, { Component } from 'react'
export default class Repos extends Component {
render() {
return (
<ul>
{this.props.repos.map((repo, i) =>
<li key={i}>{repo.name}</li>
)}
</ul>
)
}
}
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import configureStore from '../configureStore'
import AsyncApp from './AsyncApp'
const store = configureStore()
export default class Root extends Component {
render() {
return (
<Provider store={store}>
<AsyncApp />
</Provider>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment