Skip to content

Commit

Permalink
realworld-universal
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelkornev committed Feb 23, 2016
2 parents 826c79c + 0cd86c1 commit 3e8321c
Show file tree
Hide file tree
Showing 30 changed files with 1,204 additions and 0 deletions.
3 changes: 3 additions & 0 deletions examples/real-world-universal/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["es2015", "react", "stage-2"]
}
57 changes: 57 additions & 0 deletions examples/real-world-universal/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

const REQUEST = 'REQUEST'
const SUCCESS = 'SUCCESS'
const FAILURE = 'FAILURE'

function createRequestTypes(base) {
const res = {};
[REQUEST, SUCCESS, FAILURE].forEach(type => res[type] = `${base}_${type}`)
return res;
}

export const USER = createRequestTypes('USER')
export const REPO = createRequestTypes('REPO')
export const STARRED = createRequestTypes('STARRED')
export const STARGAZERS = createRequestTypes('STARGAZERS')

export const LOAD_USER_PAGE = 'LOAD_USER_PAGE'
export const LOAD_REPO_PAGE = 'LOAD_REPO_PAGE'
export const LOAD_MORE_STARRED = 'LOAD_MORE_STARRED'
export const LOAD_MORE_STARGAZERS = 'LOAD_MORE_STARGAZERS'
export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE'


function action(type, payload = {}) {
return {type, ...payload}
}

export const user = {
request: login => action(USER.REQUEST, {login}),
success: (login, response) => action(USER.SUCCESS, {login, response}),
failure: (login, error) => action(USER.FAILURE, {login, error}),
}

export const repo = {
request: fullName => action(REPO.REQUEST, {fullName}),
success: (fullName, response) => action(REPO.SUCCESS, {fullName, response}),
failure: (fullName, error) => action(REPO.FAILURE, {fullName, error}),
}

export const starred = {
request: login => action(STARRED.REQUEST, {login}),
success: (login, response) => action(STARRED.SUCCESS, {login, response}),
failure: (login, error) => action(STARRED.FAILURE, {login, error}),
}

export const stargazers = {
request: fullName => action(STARGAZERS.REQUEST, {fullName}),
success: (fullName, response) => action(STARGAZERS.SUCCESS, {fullName, response}),
failure: (fullName, error) => action(STARGAZERS.FAILURE, {fullName, error}),
}

export const loadUserPage = (login, requiredFields = []) => action(LOAD_USER_PAGE, {login, requiredFields})
export const loadRepoPage = (fullName, requiredFields = []) => action(LOAD_REPO_PAGE, {fullName, requiredFields})
export const loadMoreStarred = login => action(LOAD_MORE_STARRED, {login})
export const loadMoreStargazers = fullName => action(LOAD_MORE_STARGAZERS, {fullName})

export const resetErrorMessage = () => action(RESET_ERROR_MESSAGE)
64 changes: 64 additions & 0 deletions examples/real-world-universal/components/Explore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { Component, PropTypes } from 'react'

const GITHUB_REPO = 'https://github.com/yelouafi/redux-saga'

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

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

getInputValue() {
return 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.
this.refs.input.value = val
}

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

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

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>
<p>
Move the DevTools with Ctrl+W or hide them with Ctrl+H.
</p>
</div>
)
}
}

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

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

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>
)
}
}

List.propTypes = {
loadingLabel: PropTypes.string.isRequired,
pageCount: PropTypes.number,
renderItem: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
onLoadMoreClick: PropTypes.func.isRequired,
nextPageUrl: PropTypes.string
}

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

export default class Repo extends Component {

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>
)
}
}

Repo.propTypes = {
repo: PropTypes.shape({
name: PropTypes.string.isRequired,
description: PropTypes.string
}).isRequired,
owner: PropTypes.shape({
login: PropTypes.string.isRequired
}).isRequired
}
27 changes: 27 additions & 0 deletions examples/real-world-universal/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
}
74 changes: 74 additions & 0 deletions examples/real-world-universal/containers/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { browserHistory } from 'react-router'
import { resetErrorMessage } from '../actions'
import Explore from '../components/Explore'


class App extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleDismissClick = this.handleDismissClick.bind(this)
}

handleDismissClick(e) {
this.props.resetErrorMessage()
e.preventDefault()
}

handleChange(nextValue) {
browserHistory.push(`/${nextValue}`)
}

renderErrorMessage() {
const { errorMessage } = this.props
if (!errorMessage) {
return null
}

return (
<p style={{ backgroundColor: '#e99', padding: 10 }}>
<b>{errorMessage}</b>
{' '}
(<a href="#"
onClick={this.handleDismissClick}>
Dismiss
</a>)
</p>
)
}

render() {
const { children, inputValue } = this.props
return (
<div>
<Explore value={inputValue}
onChange={this.handleChange} />
<hr />
{this.renderErrorMessage()}
{children}
</div>
)
}
}

App.propTypes = {
// Injected by React Redux
errorMessage: PropTypes.string,
inputValue: PropTypes.string.isRequired,
resetErrorMessage: PropTypes.func.isRequired,
// Injected by React Router
children: PropTypes.node
}

function mapStateToProps(state, ownProps) {
return {
errorMessage: state.errorMessage,
inputValue: ownProps.location.pathname.substring(1)
}
}

export default connect(mapStateToProps, {
resetErrorMessage
})(App)
11 changes: 11 additions & 0 deletions examples/real-world-universal/containers/DevTools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'

export default createDevTools(
<DockMonitor toggleVisibilityKey="ctrl-h"
changePositionKey="ctrl-w">
<LogMonitor />
</DockMonitor>
)
Loading

0 comments on commit 3e8321c

Please sign in to comment.