Reducers
Reducers: is a function that return a piece of the application state. Our application can have many pieces of state, we can have many different reducers.
e.g
list of books -- state, reducer #1
currently selected book -- state reducer #2.
our application state is a plain js object.
// application state = generated by reducers
{
books: [{title:'Harry Potter'},{title:'Javascript'}], // generated by book reducer
activeBook:{title: 'Javascript: The Good parts'} // generated by activeBook reducer
}
reducers produce value of our state. keys can be whatever we want. Reducer is concerned only with value of state.
Will write a function that produce a value of the state. For books state, will produce an array of object.
Create new folder in src, 'reducers'
Create new file reducer_books.js
// other files can import this file and receive the reducer_books (note naming convention).
export default function (){
return [
{title: 'javascript: the good parts'},
{title: 'Harry Potter'},
{title: 'Pokemon'},
{title: 'React with Redux'}
]
}
Next, need to wire up reducer to application. Create new file index.js in reducer folder
import {combineReducers} from 'redux';
import BooksReducer from './reducer_books';
const rootReducer = combineReducers({
books: BooksReducer
});
export default rootReducer;
key is the piece of state. value is reducer itself. When pass object to combineReducers
we tell redux how to create our applicaiton state.
If we ran it now, we have a single piece of state called books and value is the list of book objects.
Containers - connecting Redux to React
create new folder, containers. create new file book_list.js
import React, {Component} from 'react';
export default class BookList extends Component {
renderList(){
return this.props.books.map((book) => {
return (
<li key={book.title} className="list-group-item">{book.title}</li>
);
});
}
render(){
// bootstrap classes
return (
<ul className="list-group col-sm-4">
{this.renderList()}
</ul>
)
}
}
a container is a promoted react component that has a direction connection to a state
managed by redux
React and redux are two separate libraries. Through 'react-redux' library bridges the two.
How to decide which components become containers?
'Booklist' cares about when the list of books changes
'Book details' cares about when the active book changes.
Therefore both components should have direction connection to redux.
Which components should be dumb component?
Implementation of a Container class.
When we forge a component to redux, we change it into a container or a 'smart component'.
app.js
import React, { Component } from 'react';
import BookList from '../containers/book_list.js'
export default class App extends Component {
render() {
return (
<div>
<BookList />
</div>
);
}
}
book_list.js
import React, {Component} from 'react';
import { connect } from 'react-redux';
class BookList extends Component {
renderList(){
return this.props.books.map((book) => {
return (
<li key={book.title} className="list-group-item">{book.title}</li>
);
});
}
render(){
// bootstrap classes
return (
<ul className="list-group col-sm-4">
{this.renderList()}
</ul>
)
}
}
// take application state as argument, list of books, selected book
// if state changes, this container will instantly re-render with new list of books
function mapStateToProps(state){
// whatever is returned will show up as props inside of booklist
return{
books: state.books
};
}
export default connect(mapStateToProps)(BookList);
Containers and Reducers Review
Redux constructs app state, and react provides view to presents the state.
Application state is generated by reducer functions. We created books_reducer. always return an array of books.
combineReducers: add a key to our global application state where key is books and value is bookreducer(array of books).
We created component booklist. Promoted to a container since it has to be aware of state. We promote it by importing connect,
function mapstatetoprops, and export connect.
Actions and Action Creators.
Our books reducer always listen tot the same books. State always the change.
Changing state: action and action creator purpose.
user clicks a button, calls action creator. Action creator returns an action object (action has action type, e.g. book_selected. can also contain data, like title of book.). That action automatically sent to all reducers. Reducers can choose to return a different piece of state depending on what the action is (normally implemented using a switch statement. if action type is book_selected, return action.book (new value of state), or default, don't care about action. return current state.).
Newly returned piece of state is then passed into the app state which is passed into our react app which causes all containers/components to re-render with new props(data).
Binding Action Creators
action creator is just a function that returns an action. An action is just an object that flows through all our reducers. reducers can then use that action to produce a diff value for its particular piece of state.
Click on book and see details of book.
in actions folder, create index.js
// action creator
export function selectBook(book){
console.log('book has been selected', book.title);
}
// let this action creator be wired up to redux, run thru all reducers.
book-list.js
import {selectBook} from '../actions/index';
import {bindActionCreators} from 'redux';
// anything returned from this function will end up as props on the // booklist container
function mapDispatchToProps(dispatch){
// whenever selectBook is called, result should be passed to all our reducers.
return bindActionCreators({selectBook: selectBook},dispatch)
}
// promote booklist from a component to container, need to know new dispatch method selectbook. make it available as a prop.
export default connect(mapStateToProps, mapDispatchToProps)
Creating an Action
in book_list.js, renderList
onClick={() => this.props.selectBook(book)}
//calling action creator
in action/index.js
// action creator
export function selectBook(book){
// need to return an action
// an object with a type property
return {
type: 'BOOK_SELECTED', // always uppercase string
payload: book
};
}
Consuming Actions in Reducers
Create reducer_active_book.js in reducers folder.
// state is not application state but only the state this reducer is responsible for.
e.g.
// if state is undefined, set to null
export default function(state = null, action){
switch(action.type){
case 'BOOK_SELECTED':
return action.payload;
}
return state;
}
in reducers/index.js combineReducer
import {combineReducers} from 'redux';
import BooksReducer from './reducer_books';
import ActiveBook from './reducer_active_book';
const rootReducer = combineReducers({
books: BooksReducer,
activeBook: ActiveBook
});
// action creator
export default rootReducer;
In containers, create book_detail.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
class BookDetail extends Component {
render(){
if(!this.props.book){
return (
<div>Select a book to get Started.</div>
);
}
return (
<div>
<h3>Details for:</h3>
<div>{this.props.book.title}</div>
</div>
);
}
}
function mapStateToProps(state){
return {
book: state.activeBook
}
}
export default connect(mapStateToProps)(BookDetail);
In app.js
import React, { Component } from 'react';
import BookList from '../containers/book_list'
import BookDetail from '../containers/book_detail';
export default class App extends Component {
render() {
return (
<div>
<BookList />
<BookDetail />
</div>
);
}
}
No comments:
Post a Comment