Skip to content

Commit

Permalink
Merge pull request #460 from gaearon/add-async-example
Browse files Browse the repository at this point in the history
Add “real world” example
  • Loading branch information
gaearon committed Aug 11, 2015
2 parents d594232 + df01a1f commit 41e609d
Show file tree
Hide file tree
Showing 23 changed files with 1,053 additions and 6 deletions.
2 changes: 1 addition & 1 deletion examples/counter/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<html>
<head>
<title>Redux Counter Example</title>
<title>Redux counter example</title>
</head>
<body>
<div id="root">
Expand Down
4 changes: 2 additions & 2 deletions examples/counter/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "counter-redux",
"name": "redux-counter-example",
"version": "0.0.0",
"description": "Counter example for redux",
"description": "Redux counter example",
"main": "server.js",
"scripts": {
"start": "node server.js",
Expand Down
3 changes: 3 additions & 0 deletions examples/real-world/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"stage": 2
}
148 changes: 148 additions & 0 deletions examples/real-world/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { CALL_API, Schemas } from '../middleware/api';

export const USER_REQUEST = 'USER_REQUEST';
export const USER_SUCCESS = 'USER_SUCCESS';
export const USER_FAILURE = 'USER_FAILURE';
/**
* Fetches a single user from Github API.
* Relies on the custom API middleware defined in ../middleware/api.js.
*/
function fetchUser(login) {
return {
[CALL_API]: {
types: [USER_REQUEST, USER_SUCCESS, USER_FAILURE],
endpoint: `users/${login}`,
schema: Schemas.USER
}
};
}
/**
* Fetches a single user from Github API unless it is cached.
* Relies on Redux Thunk middleware.
*/
export function loadUser(login, requiredFields = []) {
return (dispatch, getState) => {
const user = getState().entities.users[login];
if (user && requiredFields.every(key => user.hasOwnProperty(key))) {
return null;
}

return dispatch(fetchUser(login));
};
}

export const REPO_REQUEST = 'REPO_REQUEST';
export const REPO_SUCCESS = 'REPO_SUCCESS';
export const REPO_FAILURE = 'REPO_FAILURE';
/**
* Fetches a single repository from Github API.
* Relies on the custom API middleware defined in ../middleware/api.js.
*/
function fetchRepo(fullName) {
return {
[CALL_API]: {
types: [REPO_REQUEST, REPO_SUCCESS, REPO_FAILURE],
endpoint: `repos/${fullName}`,
schema: Schemas.REPO
}
};
}
/**
* Loads a single user from Github API unless it is cached.
* Relies on Redux Thunk middleware.
*/
export function loadRepo(fullName, requiredFields = []) {
return (dispatch, getState) => {
const repo = getState().entities.repos[fullName];
if (repo && requiredFields.every(key => repo.hasOwnProperty(key))) {
return null;
}

return dispatch(fetchRepo(fullName));
};
}

export const STARRED_REQUEST = 'STARRED_REQUEST';
export const STARRED_SUCCESS = 'STARRED_SUCCESS';
export const STARRED_FAILURE = 'STARRED_FAILURE';
/**
* Fetches a page of starred repos by a particular user.
* Relies on the custom API middleware defined in ../middleware/api.js.
*/
function fetchStarred(login, nextPageUrl) {
return {
login,
[CALL_API]: {
types: [STARRED_REQUEST, STARRED_SUCCESS, STARRED_FAILURE],
endpoint: nextPageUrl,
schema: Schemas.REPO_ARRAY
}
};
}
/**
* Loads a page of starred repos by a particular user.
* Bails out if page is cached and user didn’t specifically request next page.
* Relies on Redux Thunk middleware.
*/
export function loadStarred(login, nextPage) {
return (dispatch, getState) => {
const {
nextPageUrl = `users/${login}/starred`,
pageCount = 0
} = getState().pagination.starredByUser[login] || {};

if (pageCount > 0 && !nextPage) {
return null;
}

return dispatch(fetchStarred(login, nextPageUrl));
};
}


export const STARGAZERS_REQUEST = 'STARGAZERS_REQUEST';
export const STARGAZERS_SUCCESS = 'STARGAZERS_SUCCESS';
export const STARGAZERS_FAILURE = 'STARGAZERS_FAILURE';
/**
* Fetches a page of stargazers for a particular repo.
* Relies on the custom API middleware defined in ../middleware/api.js.
*/
function fetchStargazers(fullName, nextPageUrl) {
return {
fullName,
[CALL_API]: {
types: [STARGAZERS_REQUEST, STARGAZERS_SUCCESS, STARGAZERS_FAILURE],
endpoint: nextPageUrl,
schema: Schemas.USER_ARRAY
}
};
}
/**
* Loads a page of stargazers for a particular repo.
* Bails out if page is cached and user didn’t specifically request next page.
* Relies on Redux Thunk middleware.
*/
export function loadStargazers(fullName, nextPage) {
return (dispatch, getState) => {
const {
nextPageUrl = `repos/${fullName}/stargazers`,
pageCount = 0
} = getState().pagination.stargazersByRepo[fullName] || {};

if (pageCount > 0 && !nextPage) {
return null;
}

return dispatch(fetchStargazers(fullName, nextPageUrl));
};
}

export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE';
/**
* Resets the currently visible error message.
*/
export function resetErrorMessage() {
return {
type: RESET_ERROR_MESSAGE
};
}
61 changes: 61 additions & 0 deletions examples/real-world/components/Explore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { Component, PropTypes, findDOMNode } from 'react';

const GITHUB_REPO = 'https://github.com/gaearon/redux';

export default class Explore extends Component {
constructor(props) {
super(props);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handleGoClick = this.handleGoClick.bind(this);
}

getInputValue() {
return findDOMNode(this.refs.input).value;
}

setInputValue(val) {
// Generally mutating DOM is a bad idea in React components,
// but doing this for a single uncontrolled field is less fuss
// than making it controlled and maintaining a state for it.
findDOMNode(this.refs.input).value = val;
}

componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value) {
this.setInputValue(nextProps.value);
}
}

render() {
return (
<div>
<p>Type a username or repo full name and hit 'Go':</p>
<input size='45'
ref='input'
defaultValue={this.props.value}
onKeyUp={this.handleKeyUp} />
<button onClick={this.handleGoClick}>
Go!
</button>
<p>
Code on <a href={GITHUB_REPO} target='_blank'>Github</a>.
</p>
</div>
);
}

handleKeyUp(e) {
if (e.keyCode === 13) {
this.handleGoClick();
}
}

handleGoClick() {
this.props.onChange(this.getInputValue())
}
}

Explore.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
50 changes: 50 additions & 0 deletions examples/real-world/components/List.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { Component, PropTypes } from 'react';

export default class List extends Component {
render() {
const {
isFetching, nextPageUrl, pageCount,
items, renderItem, loadingLabel
} = this.props;

const isEmpty = items.length === 0;
if (isEmpty && isFetching) {
return <h2><i>{loadingLabel}</i></h2>;
}

const isLastPage = !nextPageUrl;
if (isEmpty && isLastPage) {
return <h1><i>Nothing here!</i></h1>;
}

return (
<div>
{items.map(renderItem)}
{pageCount > 0 && !isLastPage && this.renderLoadMore()}
</div>
);
}

renderLoadMore() {
const { isFetching, onLoadMoreClick } = this.props;
return (
<button style={{ fontSize: '150%' }}
onClick={onLoadMoreClick}
disabled={isFetching}>
{isFetching ? 'Loading...' : 'Load More'}
</button>
);
}
}

List.propTypes = {
loadingLabel: PropTypes.string.isRequired,
isFetching: PropTypes.bool.isRequired,
onLoadMoreClick: PropTypes.func.isRequired,
nextPageUrl: PropTypes.string
};

List.defaultProps = {
isFetching: true,
loadingLabel: 'Loading...'
};
37 changes: 37 additions & 0 deletions examples/real-world/components/Repo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { PropTypes } from 'react';
import { Link } from 'react-router';

export default class Repo {
static propTypes = {
repo: PropTypes.shape({
name: PropTypes.string.isRequired,
description: PropTypes.string
}).isRequired,
owner: PropTypes.shape({
login: PropTypes.string.isRequired
}).isRequired
}

render() {
const { repo, owner } = this.props;
const { login } = owner;
const { name, description } = repo;

return (
<div className='Repo'>
<h3>
<Link to={`/${login}/${name}`}>
{name}
</Link>
{' by '}
<Link to={`/${login}`}>
{login}
</Link>
</h3>
{description &&
<p>{description}</p>
}
</div>
);
}
}
27 changes: 27 additions & 0 deletions examples/real-world/components/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';

export default class User extends Component {
render() {
const { login, avatarUrl, name } = this.props.user;

return (
<div className='User'>
<Link to={`/${login}`}>
<img src={avatarUrl} width='72' height='72' />
<h3>
{login} {name && <span>({name})</span>}
</h3>
</Link>
</div>
);
}
}

User.propTypes = {
user: PropTypes.shape({
login: PropTypes.string.isRequired,
avatarUrl: PropTypes.string.isRequired,
name: PropTypes.string
}).isRequired
};
Loading

0 comments on commit 41e609d

Please sign in to comment.