import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { BrowserRouter, Route } from 'react-router-dom'
import App from './components/app';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware()(createStore);
class Hello extends React.Component {
render() {
return <div>Hello</div>
}
}
class Goodbye extends React.Component {
render() {
return <div>Goodbye</div>
}
}
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<div>
<App />
<Route path='/hello' component={Hello} />
<Route path='/goodbye' component={Goodbye} />
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.container'));
const posts = [
{ id: 4, title: 'hi' },
{ id: 25, title: 'bye' },
{ id: 36, title: 'how it going?' },
]
const state = _.mapKeys(posts, 'id')
console.log(state) // {"4":{"id":4,"title":"hi"},"25":{"id":25,"title":"bye"},"36":{"id":36,"title":"how it going?"}}
console.log(state[4]) // prints: {"id": 4, "title": "hi"}
reducers/reducer_posts.js
import { FETCH_POSTS } from '../actions'
import _ from 'lodash'
export default function(state = {}, action) {
switch(action.type) {
case FETCH_POSTS:
return _.mapKeys(action.payload.data, 'id')
default:
return state
}
}
components/posts_index.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchPosts } from '../actions'
class PostsIndex extends Component {
render() {
return (
<div>Posts Index</div>
)
}
}
export default connect(null, { fetchPosts })(PostsIndex)
components/posts_index.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchPosts } from '../actions'
class PostsIndex extends Component {
componentDidMount() {
// perfect place to fetch data
this.props.fetchPosts()
}
render() {
return (
<div>
</div>
)
}
}
export default connect(null, { fetchPosts })(PostsIndex)
components/posts_index.js
import _ from 'lodash'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchPosts } from '../actions'
class PostsIndex extends Component {
componentDidMount() {
// perfect place to fetch data
this.props.fetchPosts()
}
renderPosts() {
// lodash function that returns array (from object)
return _.map(this.props.posts, post => {
return (
<li className='list-group-item' key={post.id}>
{post.title}
</li>
)
})
}
render() {
console.log(this.props.posts)
return (
<div>
<h3>Posts</h3>
<ul className='list-group'>
{this.renderPosts()}
</ul>
</div>
)
}
}
function mapStateToProps(state) {
return {
posts: state.posts
}
}
export default connect(mapStateToProps, { fetchPosts })(PostsIndex)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import promise from 'redux-promise'
import reducers from './reducers';
import PostsIndex from './components/posts_index'
import PostsNew from './components/posts_new'
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<div>
<Switch>
<Route path='/posts/new' component={PostsNew} />
<Route path='/' component={PostsIndex} />
</Switch>
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.container'));
// Switch takes collection of different routes
// most specific route at the top
components/posts_index.js
import _ from 'lodash'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import { fetchPosts } from '../actions'
class PostsIndex extends Component {
componentDidMount() {
// perfect place to fetch data
this.props.fetchPosts()
}
renderPosts() {
// lodash function that returns array (from object)
return _.map(this.props.posts, post => {
return (
<li className='list-group-item' key={post.id}>
{post.title}
</li>
)
})
}
render() {
console.log(this.props.posts)
return (
<div>
<div className='text-xs-right'>
<Link className='btn btn-primary' to='/posts/new'>
Add a Post
</Link>
</div>
<h3>Posts</h3>
<ul className='list-group'>
{this.renderPosts()}
</ul>
</div>
)
}
}
function mapStateToProps(state) {
return {
posts: state.posts
}
}
export default connect(mapStateToProps, { fetchPosts })(PostsIndex)
components/post_new.js
import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'
class PostsNew extends Component {
renderField(field) {
const { meta } = field
const className = `form-group ${meta.touched && meta.error ? 'has-danger' : ''}`
return (
<div className={className}>
<label>{field.label}</label>
<input
className='form-control'
type='text'
{...field.input}
/>
<div className='text-help'>
{ meta.touched ? meta.error : '' }
</div>
</div>
)
}
onSubmit(values) {
console.log(values)
}
render() {
const { handleSubmit } = this.props // reduxForm thing..
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label='Title'
name='title'
component={this.renderField}
/>
<Field
label='Categories'
name='categories'
component={this.renderField}
/>
<Field
label='Post Content'
name='content'
component={this.renderField}
/>
<button type='submit' className='btn btn-primary'>Submit</button>
</form>
)
}
}
function validate(values) {
// console.log(values => {title: 'sadasdsa', categories: 'dsaas', content: 'blablabla'})
const errors = {}
// validate the inputs from 'values'
if (!values.title || values.title.length < 3) {
errors.title = 'Enter a title that is at least 3 characters'
}
if (!values.categories) {
errors.categories = 'Enter some categories'
}
if (!values.content) {
errors.content = 'Enter some content'
}
// if errors is empty, the form is fine to submit
// if errors has any properties, redux form assumes form is invalid
return errors
}
export default reduxForm({
validate: validate,
form: 'PostNewForm' // a unique name for this form
})(PostsNew)
pristine: user not select input yet touched: user has selected input and focused out invalid: when error occured
actions/index.js
export function createPost(values) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, values)
return {
type: CREATE_POST,
payload: request
}
}
components/posts_new.js
onSubmit(values) {
this.props.createPost(values)
}
export default reduxForm({
validate: validate,
form: 'PostNewForm' // a unique name for this form
})(
connect(null, { createPost })(PostsNew)
)
actions/index.js
export function createPost(values, callback) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, values)
.then(() => callback())
return {
type: CREATE_POST,
payload: request
}
}
components/posts_new.js
onSubmit(values) {
console.log(values)
this.props.createPost(values, () => {
this.props.history.push('/')
})
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import promise from 'redux-promise'
import reducers from './reducers';
import PostsIndex from './components/posts_index'
import PostsNew from './components/posts_new'
import PostsShow from './components/posts_show'
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<div>
<Switch>
<Route path='/posts/new' component={PostsNew} />
<Route path='/posts/:id' component={PostsShow} />
<Route path='/' component={PostsIndex} />
</Switch>
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.container'));
// Switch takes collection of different routes
// most specific route at the top
actions/index.js
import axios from 'axios'
export const FETCH_POSTS = 'FETCH_POSTS'
export const CREATE_POST = 'CREATE_POST'
export const FETCH_POST = 'FETCH_POST'
const ROOT_URL = 'http://reduxblog.herokuapp.com/api'
const API_KEY = '?key=SAVA1234'
export function fetchPosts() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`)
return {
type: FETCH_POSTS,
payload: request
}
}
export function createPost(values, callback) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, values)
.then(() => callback())
return {
type: CREATE_POST,
payload: request
}
}
export function fetchPost(id) {
const request = axios.get(`${ROOT_URL}/posts/${id}${API_KEY}`)
return {
type: FETCH_POST,
payload: request
}
}
reducers/reducer_posts.js
import { FETCH_POSTS, FETCH_POST } from '../actions'
import _ from 'lodash'
export default function(state = {}, action) {
switch(action.type) {
case FETCH_POSTS:
return _.mapKeys(action.payload.data, 'id')
case FETCH_POST:
// ES5
// const post = action.payload.data
// const newState = { ...state }
// newState[post.id] = post
// return newState
// ES6
return { ...state, [action.payload.data.id]: action.payload.data }
default:
return state
}
}
components/posts_show.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchPost } from '../actions'
class PostsShow extends Component {
componentDidMount() {
const { id } = this.props.match.params
this.props.fetchPost(id)
}
render() {
const { post } = this.props
if (!post) {
return <div>Loading...</div>
}
return (
<div>
<h3>{post.title}</h3>
<h6>Categories: {post.categories}</h6>
<p>{post.content}</p>
</div>
)
}
}
function mapStateToProps({ posts }, ownProps) {
// this.props === ownProps
return {
post: posts[ownProps.match.params.id]
}
}
export default connect(mapStateToProps, { fetchPost })(PostsShow)