Book List
React-Redux
- Redux's key ideas
- All of your application’s data is in a single data structure called the state which is held in the store
- Your app reads the state from this store. (The app could be React-based, or Angular-based, or Vue-based, or anything.)
- The state is never mutated directly outside the store
- The views emit actions that describe what happened
- A new state is created by combining the old state and the action by a function called the reducer
- Redux's key benefits
- Redux relieves the top-level React component of state management responsibility and allows you to break up state management into isolated, smaller, and testable parts
- Certain local and component-managed state is fine, like activating certain buttons on mouse hover. But by managing all other state externally, React components become simple HTML rendering functions. This makes them smaller, easier to understand, and more composable
- It is good and flexible to mis-match between the state tree (in Redux store) and the DOM tree (for React). Oftentimes, we want to store our state with a different representation than how we want to display it. Instead of having components hold all the computational logic for derived data, Redux enables us to perform these computations before providing state to React components
Pure React
React-Redux
Project Appearance
Code Organization
Install react-redux
npm install --save redux
npm install --save react-redux
npm install --save-dev redux-devtools-extension
Source Code
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import "./index.css";
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { Provider } from 'react-redux';
import App from './components/App';
import rootReducer from './reducers';
import 'bootstrap/dist/css/bootstrap.min.css';
const store = createStore(rootReducer, {}, composeWithDevTools(applyMiddleware()));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
//index.css
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
.row {
margin: 20px;
}
.class-item-mine:hover {
border-color: blue;
}
.selected-item-mine {
background-color: lightgray;
}
//actions/index.js
export function selectBook(book){
return {
type: 'BOOK_SELECTED',
payload: book
}
}
// If you had other action creators, just list them all
// one by one here.
//reducers/index.js
//combines all reducers into one rootReducer
import { combineReducers } from "redux";
import BooksReducer from './reducer_books';
import ActiveBookReducer from './reducer_active_book';
const rootReducer = combineReducers({
// our first state in Redux store - books
books: BooksReducer,
activeBook: ActiveBookReducer
});
export default rootReducer;
//reducers/reducer_books.js
export default function() {
return [
{title: "JavaScript: The Good Parts", pages: 101},
{title: "The Dark Tower", pages: 20},
{title: "Eloquent NodeJS", pages: 45},
];
}
//reducers/reducer_active_book.js
export default function(state=null, action) {
switch(action.type){
case 'BOOK_SELECTED':
return action.payload;
default:
return state;
}
return state;
}
//components/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 className="container">
<div className="row">
<div className="col-md-8">
<BookList />
</div>
<div className="col-md-4">
<BookDetail />
</div>
</div>
</div>
);
}
}
//containers/book-list.js
import React, { Component } from "react";
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { selectBook } from '../actions/index';
class BookList extends Component {
renderList() {
// this.props.books works here thanks to the mapStateToProps function below
return this.props.books.map(book => {
const cn =
this.props.activeBook &&
this.props.activeBook.title === book.title
? "list-group-item class-item-mine selected-item-mine"
: "list-group-item class-item-mine";
return (
<li
onClick={() => this.props.selectBook(book)}
key={book.title}
className={cn}
>
{book.title}
</li>
);
});
}
render() {
return <ul className="list-group">{this.renderList()}</ul>;
}
}
function mapStateToProps(state) {
return {
books: state.books,
activeBook: state.activeBook
};
}
// Anything returned from this function will end up as props on the
// BookList container
function mapDispatchToProps(dispatch){
return bindActionCreators({ selectBook: selectBook }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(BookList);
//containers/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> Title: { this.props.book.title } </div>
<div> Pages: { this.props.book.pages } </div>
</div>
)
}
}
function mapStateToProps(state){
return {
book: state.activeBook
};
}
export default connect(mapStateToProps)(BookDetail);
Project Domain
https://book-list-lin.herokuapp.com/
Reference
CS4840 by Dr. Zhiguang Xu at Valdosta State University