From 0ce9d35d82fad8b86b791d56cdf01adff7a7c930 Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Sun, 9 Sep 2018 00:27:37 +0800 Subject: [PATCH 1/3] Add Getting Started section to the new docs --- docs/GettingStarted.md | 740 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 740 insertions(+) create mode 100644 docs/GettingStarted.md diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md new file mode 100644 index 000000000..0db24c819 --- /dev/null +++ b/docs/GettingStarted.md @@ -0,0 +1,740 @@ +# Getting Started + +[Redux](https://github.com/reduxjs/redux) has no knowledge of [React](https://reactjs.org/). +You may create your [Redux](https://github.com/reduxjs/redux) app using other UI rendering frameworks, or not using any frameworks at all. +`react-redux` is the official [React](https://reactjs.org/) binding for [Redux](https://github.com/reduxjs/redux). +It lets your [React](https://reactjs.org/) components read data and dispatch actions from and to a [Redux](https://github.com/reduxjs/redux) store. + +## Installation + +To use `react-redux` with your [React](https://reactjs.org/) app: + +``` +npm install --save react-redux +``` + + + +## … In A Few Words + +`react-redux` provides the `connect()`function to help you connect a [React](https://reactjs.org/) component to the [Redux](https://github.com/reduxjs/redux) `store`. + +It enables you to: + +1. Read data from the [Redux](https://github.com/reduxjs/redux) `store` into your app’s connected components as props +2. Dispatch actions to your `store` from any of your app’s connected components + +You can provide a function to specify which data you want to read from the store. By convention, it’s called `mapStateToProps`. To specify the actions you plan to dispatch, you provide the second parameter. By convention, we call it `mapDispatchToProps` . While it’s normally an object containing all the actions, it can also be a function. + +Normally, you’ll call `connect` in this fashion: + +Calling `connect` will return a function that waits for you to pass in your unconnected component. +To obtain your connected component, you then call that function and pass in the component you’d like to subscribe to the store. + +```jsx +const connectToStore = connect( + mapStateToProps, + mapDispatchToProps +); +const connectedComponent = connectToStore(Component); + +// We normally do both in one step, like this: +connect( + mapStateToProps, + mapDispatchToProps +)(Component); +``` + +## A Todo List Example + +Jump to + +- 🤞 [Just show me the code](https://codesandbox.io/s/5w8l097704) +- 👆 [Providing the store](###providing-the-store) +- ✌️ [Common Ways of Calling Connect](###common-ways-of-calling-connect) + +**The React UI Components** + +As an example, we will create a Todo List app using [React](https://reactjs.org/) and [Redux](https://github.com/reduxjs/redux). Suppose we have our [React](https://reactjs.org/) UI components implemented as follows: + +- `TodoApp` is the entry component for our app. It renders the header, the `AddTodo`, `TodoList`, and `VisibilityFilters` components +- `AddTodo` is the component that allows a user to input a todo item and add to the list upon clicking its “Add Todo” button + - It sets state with the user’s input upon `onBlur`, and uses that value for adding a todo when the user clicks on the “Add Todo” button + - It is waiting for an `addTodo` handler function passed down as props to handle the user’s add todo interaction +- `TodoList` is the component that renders the list of todos +- `Todo` is the component that renders a single todo item + - It waits for a `toggleTodo` handler function, with which we toggle the todo as upon `onClick` of the todo +- `VisibilityFilters` renders a simple set of filters: _all_, _completed_, and _incomplete._ Clicking on each one of them filters the todos + - It accepts an `activeFilter` prop from the parent that indicates which filter is currently selected by the user. An active filter is rendered with an underscore + - It waits for a `setFilter` handler function to handle setting filters +- `constants` holds the constants data for our app, mainly visibility filters +- And finally `style.css` is the stylesheet, and `index` renders our app to the DOM + +We’ll start by briefly describing our UI components. For the full code source, check out [this CodeSandbox](https://codesandbox.io/s/mo7p88po0j). + +
+Expand Code + +``` +// tree structure +. +├── components +│ ├── AddTodo.js +│ ├── TodoList.js +│ ├── Todo.js +│ └── VisibilityFilters.js +├── index.js +├── TodoApp.js +├── constants.js +└── style.css +``` + +```jsx +// TodoApp.js +import React from "react"; +import AddTodo from "./components/AddTodo"; +import TodoList from "./components/TodoList"; +import VisibilityFilters from "./components/VisibilityFilters"; +import "./styles.css"; + +export default function TodoApp() { + return ( +
+

Todo List

+ + + +
+ ); +} +``` + +```jsx +// components/AddTodo.js + +import React from "react"; + +class AddTodo extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( +
+ (this.input = r)} /> + +
+ ); + } +} + +export default AddTodo; +``` + +```jsx +// components/Todo.js + +import React from "react"; +import cx from "classnames"; + +const Todo = ({ todo }) => ( +
  • {} /** waiting for toggleTodo handler */} + > + {todo && todo.completed ? "👌" : "👋"}{" "} + + {todo.content} + +
  • +); + +export default Todo; +``` + +```jsx +// components/TodoList.js +import React from "react"; +import Todo from "./Todo"; + +const TodoList = ({ todos }) => ( + +); + +export default TodoList; +``` + +```jsx +// components/VisibilityFilters.js + +import React from "react"; +import cx from "classnames"; +import { VISIBILITY_FILTERS } from "../constants"; + +const VisibilityFilters = ({ activeFilter }) => { + return ( +
    + {Object.keys(VISIBILITY_FILTERS).map(filterKey => { + const currentFilter = VISIBILITY_FILTERS[filterKey]; + return ( + {} /** waiting for setFilter handler*/} + > + {currentFilter} + + ); + })} +
    + ); +}; + +export default VisibilityFilters; +``` + +```jsx +// index.js +import React from "react"; +import ReactDOM from "react-dom"; + +import TodoApp from "./TodoApp"; + +const rootElement = document.getElementById("root"); +ReactDOM.render(, rootElement); +``` + +```JavaScript +// constants.js +export const VISIBILITY_FILTERS = { + ALL: "all", + COMPLETED: "completed", + INCOMPLETE: "incomplete" +}; +``` + +```css +/** styles.css**/ + +.todo-app { + font-family: sans-serif; +} + +/** add todo **/ +.add-todo { + margin-left: 0.5rem; +} + +/** todo list **/ +.todo-list { + margin-top: 1rem; + text-align: left; + list-style: none; +} + +/** todo item **/ +.todo-item { + font-family: monospace; + cursor: pointer; + line-height: 1.5; +} +.todo-item__text--completed { + text-decoration: line-through; + color: lightgray; +} + +/** visibility filters **/ +.filter { + padding: 0.3rem 0; + margin: 0 0.3rem; + cursor: pointer; +} +.filter--active { + border-bottom: 1px solid black; +} +``` + +
    + +**The Redux Store** + +Suppose also that we have created the [Redux](https://github.com/reduxjs/redux) as follows. To learn about designing your [Redux](https://github.com/reduxjs/redux) store, [the official Redux docs](https://redux.js.org/basics) has an excellent guide and we will not cover the same topics again. + +- Reducers + - A `todoList` reducer with a state that holds a list `id` ‘s of each todo. Defaults to `[]`. + - A `todoMap` reducer with a state that holds the mapping to each todo. Defaults to `{}`. + - Each todo is a simple object of two fields, `content` that is a string describing the todo, and `completed` a boolean indicating whether the todo is completed +- Action Creators + - `addTodo` creates the action to add todo’s. It takes a single string variable `content` and returns an `ADD_TODO` action with `payload` containing a self-incremented `id` and `content` + - `toggleTodo` creates the action to toggle todo’s. It takes a single number variable `id` and returns a `TOGGLE_TODO` action with `payload` containing `id` only + - `setFilter` creates the action to set the app’s active filter. It takes a single string variable `filter` and returns a `SET_FILTER` action with `payload` containing the `filter` itself +- Action Types + - We use a file `actionTypes.js` to hold the constants of action types to be reused +- Selectors + - `getTodoList` returns the `todoList` state + - `getTodoById` finds the todo in the store given by `id` + - `getTodos` is slightly more complex. It takes all the `id`s from `todoList` state, finds each todo in the `todoMap` state, and returns the final array of todo’s. + +
    + Expand Code + +``` +. +└── redux +├── reducers +│ ├── index.js +│ ├── todoList.js +│ ├── todoMap.js +│ └── visibilityFilters.js +├── actionTypes.js +├── actions.js +├── selectors.js +└── store.js +``` + +```JavaScript +// redux/store.js +import { createStore } from "redux"; +import rootReducer from "./reducers"; + +export default createStore(rootReducer); +``` + +```JavaScript +// redux/reducers/index.js +import { combineReducers } from "redux"; +import todoList from "./todoList"; +import todoMap from "./todoMap"; +import visibilityFilter from "./visibilityFilter"; + +export default combineReducers({ todoList, todoMap, visibilityFilter }); +``` + +```JavaScript +// redux/reducers/todoList.js +import { ADD_TODO } from "../actionTypes"; + +const defaultState = []; +const todoList = (state = defaultState, action) => { + switch (action.type) { + case ADD_TODO: { + const { id } = action.payload; + return [...state, id]; + } + default: + return state; + } +}; + +export default todoList; +``` + +```JavaScript +// redux/reducers/todoMap.js +import { ADD_TODO, TOGGLE_TODO } from "../actionTypes"; + +const defaultState = {}; + +const todoMap = (state = defaultState, action) => { + switch (action.type) { + case ADD_TODO: { + const { id, content } = action.payload; + return { + ...state, + [id]: { + content, + completed: false + } + }; + } + case TOGGLE_TODO: { + const { id } = action.payload; + const currentTodo = state[id]; + return { + ...state, + [id]: { ...currentTodo, completed: !currentTodo.completed } + }; + } + default: + return state; + } +}; + +export default todoMap; +``` + +```JavaScript +// redux/reducers/visibilityFilter.js +import { SET_FILTER } from "../actionTypes"; +import { VISIBILITY_FILTERS } from "../../constants"; + +const defaultState = VISIBILITY_FILTERS.ALL; + +const visibilityFilter = (state = defaultState, action) => { + switch (action.type) { + case SET_FILTER: { + return action.payload.filter; + } + default: { + return state; + } + } +}; + +export default visibilityFilter; +``` + +```JavaScript +// redux/actions.js +import { ADD_TODO, TOGGLE_TODO, SET_FILTER } from "./actionTypes"; + +let nextTodoId = 0; + +export const addTodo = content => ({ + type: ADD_TODO, + payload: { + id: ++nextTodoId, + content + } +}); + +export const toggleTodo = id => ({ + type: TOGGLE_TODO, + payload: { id } +}); + +export const setFilter = filter => ({ type: SET_FILTER, payload: { filter } }); +``` + +```JavaScript +// redux/selectors.js +export const getTodoList = store => store.todoList; + +export const getTodoById = (store, id) => ({ ...store.todoMap[id], id }); + +/** + * example of a slightly more complex selector + * select from store combining information from multiple reducers + */ +export const getTodos = store => + getTodoList(store).map(id => getTodoById(store, id)); +``` + +```JavaScript +// redux/actionTypes.js +export const ADD_TODO = "ADD_TODO"; +export const TOGGLE_TODO = "TOGGLE_TODO"; +export const SET_FILTER = "SET_FILTER"; +``` + +
    + +Once again you may check out the code here [CodeSandbox](https://codesandbox.io/s/5w8l097704) + +We now show how to connect this store to our app using `react-redux`. + +### Providing the Store + +First we need to make the `store` available to our app. To do this, we wrap our app with the `` api provided by `react-redux`. + +```jsx +// index.js +import React from "react"; +import ReactDOM from "react-dom"; +import TodoApp from "./TodoApp"; + +import { Provider } from "react-redux"; +import store from "./redux/store"; + +const rootElement = document.getElementById("root"); +ReactDOM.render( + + + , + rootElement +); +``` + +Notice how our `` is now wrapped with the `` with `store` passed in as a prop. The `store` object has a few methods that do their magic. But we won’t go into them, yet. We’ll explain them later in the “whys and hows” section. + +![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536403768209_image.png) + +### Connecting the Components + +Our components need to read values from the [Redux](http://redux.js.org/) store (and re-read the values when the store updates). They also need to dispatch actions to trigger updates. + +`connect` takes in two parameters. The first one allows you to define which pieces of data from the store are needed by this component. The second one allows you to indicate which actions that component might dispatch. By convention, they are called `mapStateToProps` and `mapDispatchToProps`, respectively. The return of this call is another function that waits for you to feed in your component on a second call. This is one of a common practices of implementing _higher order components._ Feel free to read more on that. + +Let’s work on `` first. It needs to trigger changes to the `store` to add new todos. Therefore, it needs to be able to `dispatch` actions to the store. Here’s how we do it. + +Our `addTodo` action creator looks like this: + +```JavaScript +// redux/actions.js +import { ADD_TODO } from './actionTypes'; + +let nextTodoId = 0; +export const addTodo = content => ({ + type: ADD_TODO, + payload: { + id: ++nextTodoId, + content + } +}); + +// ... other actions +``` + +By passing it to `connect`, our component receives it as a prop, and it will automatically dispatch the action when it’s called. + +```jsx +// components/AddTodo.js + +// ... other imports +import { connect } from "react-redux"; +import { addTodo } from "../redux/actions"; + +class AddTodo extends React.Component { + // ... component implementation +} + +export default connect( + null, + { addTodo } +)(AddTodo); +``` + +Notice now that `` is wrapped with a parent component called ``. Meanwhile, `` now gains one prop: the `addTodo` action. + +![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536402763972_image.png) + +Try to type something in the input box and click “Add Todo”. Nothing happens. Why? We still need to implement what triggers the dispatch of our action `addTodo`. That would be the `onClick` event of the “Add Todo” button: + +```jsx +// components/AddTodo.js + +import React from "react"; +import { connect } from "react-redux"; +import { addTodo } from "../redux/actions"; + +class AddTodo extends React.Component { + constructor(props) { + super(props); + this.state = { input: "" }; + } + + updateInput = input => { + this.setState({ input }); + }; + + render() { + return ( +
    + this.updateInput(e.target.value)} /> + +
    + ); + } +} + +export default connect( + null, // will not subscribe to the store + { addTodo } +)(AddTodo); +``` + +Now our `` is connected to the store. It _should_ dispatch an action to change the store upon the user’s add todo interaction. But we are unable to see it before our `` is also connected to the store. So let’s do it now. + +The `` component is responsible for rendering the list of todos. Therefore, it needs to read data from the store. We enable it by calling `connect` with the `mapState` parameter, a function describing which part of the data we need from the store. + +Our `` component takes the todo item as props. We have this information from the `todoMap` store. However, we also need the information from the `todoList` store indicating which todos and in what order they should be rendered. Our `mapStateToProps` function may look like this: + +```jsx +// components/TodoList.js + +// ...other imports +import { connect } from "react-redux"; + +const TodoList = // ... UI component implementation + +const mapStateToProps = state => { + const { todoList, todoMap } = state; + const todos = []; + todoList.map(id => todos.push({ ...todoMap[id], id })); + return { todos }; +}; + +export default connect(mapStateToProps)(TodoList); +``` + +Luckily we have a selector that does exactly this. We may simply import the selector and use it here. + +```jsx +// components/TodoList.js + +// ...other imports +import { connect } from "react-redux"; +import { getTodos } from "../redux/selectors"; + +const TodoList = // ... UI component implementation + +export default connect(state => ({ todos: getTodos(state) }))(TodoList); +``` + +So that provides with a motivation to write selector functions for complex computation. You may further optimize by using [Reselect](https://github.com/reduxjs/reselect). + +Now that our `` is connected to the store. It should receive the list of todos, map over them, and pass each todo to the `` component, which will in turn render them to the screen. + +![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536405730234_image.png) + +We will connect more components. +Before we do this, let’s pause and learn a bit more about `connect` first. + +### Common ways of calling `connect` + +Depending on what kind of components you are working with, there are different ways of calling `connect` , with the most common ones summarized as below: + +| | Subscribe to Store | Do Not Subscribe to Store | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| Inject Action Creators | `connect(mapStateToProps,` ` mapDispatchToProps``)(Component) ` | `connect(null,` ` mapDispatchToProps``)(Component) ` | +| Do Not Inject Action Creators | `connect(mapStateToProps)(Component)` | `connect()(Component)` _Component will receive_ `*dispatch*` | + +#### 1. Subscribe to the store and inject action creators + +``` +import { addTodo } from './actionCreators'; +// ... Component +const mapStateToProps = state => state.partOfState; +export default connect(mapStateToProps, { addTodo })(Component); +``` + +#### 2. Do not subscribe to the store and inject action creators + +```jsx +import * as actionCreators from "./actionCreators"; +// ... Component +export default connect( + null, + actionCreators +)(Component); +``` + +#### 3. Subscribe to the store and do not inject action creators + +```jsx +// ... Component +const mapStateToProps = state => state.partOfState; +export default connect(mapStateToProps)(Component); +``` + +#### 4. Do not subscribe to the store and do not inject action creators + +```jsx +// ... Component +export default connect()(Component); // Component will receive `dispatch` +``` + + + +Now let’s connect the rest of our ``. + +How should we implement the interaction of toggling todos? A keen reader might already have an answer. If you have your environment set up and have followed through up until this point, now is a good time to leave it aside and implement the feature by itself. There would be no surprise that we connect our `` to dispatch `toggleTodo` in a similar fashion: + +```jsx +// components/Todo.js + +// ... other imports +import { connect } from "react-redux"; +import { toggleTodo } from "../redux/actions"; + +const Todo = // ... component implementation + +export default connect( + null, + { toggleTodo } +)(Todo); +``` + +Now our todo’s can be toggled complete. We’re almost there! + +![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536419073312_image.png) + +There are some common practices in implementing React applications. One such example is to separate components into _presentational_ components and _container_ components. [This article](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by Dan Abramov has a nice introduction. If this is a practice that you would like to follow, `react-redux` is with you. Check out here to learn about some of the best practices to program with `react-redux`. + +Finally, let’s implement our `VisibilityFilters` feature. + +The `` component needs to be able to read from the store which filter is currently active, and dispatch actions to the store. Therefore, we need to pass both a `mapStateToProps` and `mapDispatchToProps`. The `mapStateToProps` here can be a simple accessor of the `visibilityFilter` state. And the `mapDispatchToProps` will contain the `setFilter` action creator. + +```jsx +// components/VisibilityFilters.js + +// ... other imports +import { connect } from "react-redux"; +import { setFilter } from "../redux/actions"; + +const VisibilityFilters = // ... component implementation + +const mapStateToProps = state => { + return { activeFilter: state.visibilityFilter }; +}; +export default connect( + mapStateToProps, + { setFilter } +)(VisibilityFilters); +``` + +Meanwhile, we also need to update our `` component to filter todos according to the active filter. Previously the `mapStateToProps` we passed to the `` `connect` function call was simply the selector that selects the whole list of todos. Let’s add the filter within this `mapStateToProps`. + +```jsx +// components/TodoList.js + +// ... + +const mapStateToProps = state => { + const { visibilityFilter } = state; + const allTodos = getTodos(state); + return { + todos: + visibilityFilter === VISIBILITY_FILTERS.ALL + ? allTodos + : visibilityFilter === VISIBILITY_FILTERS.COMPLETED + ? allTodos.filter(todo => todo.completed) + : allTodos.filter(todo => !todo.completed) + }; +}; + +export default connect(mapStateToProps)(TodoList); +``` + +## 🎉🎊 + +![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536418948690_image.png) + +## References + +- Usage with React https://redux.js.org/basics/usagewithreact From 67a1b4dac79290a9163a7a8cf39a646d45879948 Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Wed, 12 Sep 2018 19:13:59 +0800 Subject: [PATCH 2/3] Revisions 1: Normalize `todos` store shape, add an overview section. --- docs/GettingStarted.md | 435 +++++++++++++++++++++++++++-------------- 1 file changed, 288 insertions(+), 147 deletions(-) diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 0db24c819..3fe42def1 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -1,42 +1,79 @@ # Getting Started -[Redux](https://github.com/reduxjs/redux) has no knowledge of [React](https://reactjs.org/). -You may create your [Redux](https://github.com/reduxjs/redux) app using other UI rendering frameworks, or not using any frameworks at all. -`react-redux` is the official [React](https://reactjs.org/) binding for [Redux](https://github.com/reduxjs/redux). -It lets your [React](https://reactjs.org/) components read data and dispatch actions from and to a [Redux](https://github.com/reduxjs/redux) store. +[React-Redux](https://github.com/reduxjs/react-redux) is the official [React](https://reactjs.org/) binding for [Redux](https://redux.js.org/). +Redux has no knowledge of React. +You may create your Redux app using other UI rendering frameworks, or not using any frameworks at all. +It lets your React components read data and dispatch actions from and to a Redux store. ## Installation -To use `react-redux` with your [React](https://reactjs.org/) app: +To use React-Redux with your React app: -``` +```bash npm install --save react-redux ``` +or + +``` +yarn add react-redux +``` + -## … In A Few Words +## `` and `connect` + +**React-Redux provides the `` that serves the Redux store of your app:** + +```js +import React from "react"; +import ReactDOM from "react-dom"; + +import { Provider } from "react-redux"; +import store from "./store"; + +import App from "./App"; + +const rootElement = document.getElementById("root"); +ReactDOM.render( + + + , + rootElement +); +``` -`react-redux` provides the `connect()`function to help you connect a [React](https://reactjs.org/) component to the [Redux](https://github.com/reduxjs/redux) `store`. +**React-Redux also provides the `connect()` function to help you connect a React component to the Redux `store`.** It enables you to: -1. Read data from the [Redux](https://github.com/reduxjs/redux) `store` into your app’s connected components as props -2. Dispatch actions to your `store` from any of your app’s connected components +- Read data from the Redux `store` into your app’s connected components as props +- Dispatch actions to your `store` from any of your app’s connected components + +Correspondingly, the `connect` function takes two arguments, both optional: -You can provide a function to specify which data you want to read from the store. By convention, it’s called `mapStateToProps`. To specify the actions you plan to dispatch, you provide the second parameter. By convention, we call it `mapDispatchToProps` . While it’s normally an object containing all the actions, it can also be a function. +- `mapStateToProps`: called every time the store state changes. It receives the entire store state, and should return an object of data this component needs. -Normally, you’ll call `connect` in this fashion: +- `mapDispatchToProps`: called once on component creation. It receives the dispatch method, and should return an object full of functions that use dispatch. This param _can_ be an object as well. And it will be how you normally use it. If `connect` receives an object full of action creators for this param, it binds `dispatch` for you automatically. -Calling `connect` will return a function that waits for you to pass in your unconnected component. -To obtain your connected component, you then call that function and pass in the component you’d like to subscribe to the store. +Normally, you’ll call `connect` in this way: ```jsx +const mapStateToProps = (state, ownProps) => ({ + // ... computed data from state and optionally ownProps +}); + +const mapDispatchToProps = { + // ... normally is an object full of action creators +}; + +// `connect` returns a new function that accepts the component to wrap: const connectToStore = connect( mapStateToProps, mapDispatchToProps ); -const connectedComponent = connectToStore(Component); +// and that function returns the connected, wrapper component: +const ConnectedComponent = connectToStore(Component); // We normally do both in one step, like this: connect( @@ -47,30 +84,34 @@ connect( ## A Todo List Example -Jump to +We show a step-by-step example by creating a todo list app using React-Redux. -- 🤞 [Just show me the code](https://codesandbox.io/s/5w8l097704) -- 👆 [Providing the store](###providing-the-store) -- ✌️ [Common Ways of Calling Connect](###common-ways-of-calling-connect) - -**The React UI Components** +**Jump to** -As an example, we will create a Todo List app using [React](https://reactjs.org/) and [Redux](https://github.com/reduxjs/redux). Suppose we have our [React](https://reactjs.org/) UI components implemented as follows: +- 🤞 [Just show me the code](https://codesandbox.io/s/9on71rvnyo) +- 👆 [Providing the store](#providing-the-store) +- ✌️ [Common Ways of Calling Connect](#common-ways-of-calling-connect) -- `TodoApp` is the entry component for our app. It renders the header, the `AddTodo`, `TodoList`, and `VisibilityFilters` components -- `AddTodo` is the component that allows a user to input a todo item and add to the list upon clicking its “Add Todo” button - - It sets state with the user’s input upon `onBlur`, and uses that value for adding a todo when the user clicks on the “Add Todo” button - - It is waiting for an `addTodo` handler function passed down as props to handle the user’s add todo interaction -- `TodoList` is the component that renders the list of todos -- `Todo` is the component that renders a single todo item - - It waits for a `toggleTodo` handler function, with which we toggle the todo as upon `onClick` of the todo -- `VisibilityFilters` renders a simple set of filters: _all_, _completed_, and _incomplete._ Clicking on each one of them filters the todos - - It accepts an `activeFilter` prop from the parent that indicates which filter is currently selected by the user. An active filter is rendered with an underscore - - It waits for a `setFilter` handler function to handle setting filters -- `constants` holds the constants data for our app, mainly visibility filters -- And finally `style.css` is the stylesheet, and `index` renders our app to the DOM +**The React UI Components** -We’ll start by briefly describing our UI components. For the full code source, check out [this CodeSandbox](https://codesandbox.io/s/mo7p88po0j). +We have implemented our React UI components as follows: + +- `TodoApp` is the entry component for our app. It renders the header, the `AddTodo`, `TodoList`, and `VisibilityFilters` components. +- `AddTodo` is the component that allows a user to input a todo item and add to the list upon clicking its “Add Todo” button: + - It uses a controlled input that sets state upon `onChange`. + - When the user clicks on the “Add Todo” button, it dispatches the action (that we will provide using React-Redux) to add the todo to the store. +- `TodoList` is the component that renders the list of todos: + - It renders the filtered list of todos when one of the `VisibilityFilters` is selected. +- `Todo` is the component that renders a single todo item: + - It renders the todo content, and shows that a todo is completed by crossing it out. + - It dispatches the action to toggle the todo's complete status upon `onClick`. +- `VisibilityFilters` renders a simple set of filters: _all_, _completed_, and _incomplete._ Clicking on each one of them filters the todos: + - It accepts an `activeFilter` prop from the parent that indicates which filter is currently selected by the user. An active filter is rendered with an underscore. + - It dispatches the `setFilter` action to update the selected filter. +- `constants` holds the constants data for our app. +- And finally `style.css` is the stylesheet, and `index` renders our app to the DOM. + +You may check out the sourcecode below or check out this CodeSandbox: [Todo App UI Only](https://codesandbox.io/s/mo7p88po0j).
    Expand Code @@ -117,16 +158,26 @@ import React from "react"; class AddTodo extends React.Component { constructor(props) { super(props); + this.state = { input: "" }; } + updateInput = input => { + this.setState({ input }); + }; + + handleAddTodo = () => { + // dispatches actions to add todo + // sets state back to empty string + }; + render() { return (
    - (this.input = r)} /> -
    @@ -146,7 +197,7 @@ import cx from "classnames"; const Todo = ({ todo }) => (
  • {} /** waiting for toggleTodo handler */} + onClick={() => {} /** dispatches action to toggle todo */} > {todo && todo.completed ? "👌" : "👋"}{" "} { "filter", currentFilter === activeFilter && "filter--active" )} - onClick={() => {} /** waiting for setFilter handler*/} + onClick={() => {} /** dispatches action to set filter */} > {currentFilter} @@ -278,22 +330,29 @@ export const VISIBILITY_FILTERS = { **The Redux Store** -Suppose also that we have created the [Redux](https://github.com/reduxjs/redux) as follows. To learn about designing your [Redux](https://github.com/reduxjs/redux) store, [the official Redux docs](https://redux.js.org/basics) has an excellent guide and we will not cover the same topics again. +We have also created the Redux as follows. To learn about designing your Redux store, [the official Redux docs](https://redux.js.org/basics) has an excellent guide. -- Reducers - - A `todoList` reducer with a state that holds a list `id` ‘s of each todo. Defaults to `[]`. - - A `todoMap` reducer with a state that holds the mapping to each todo. Defaults to `{}`. - - Each todo is a simple object of two fields, `content` that is a string describing the todo, and `completed` a boolean indicating whether the todo is completed +- Store + - `todos`: A normalized reducer of todos. It contains a `byIds` map of all todos and a `allIds` that contains the list of all ids. + - `visibilityFilters`: A simple string `all`, `completed`, or `incomplete`. - Action Creators - `addTodo` creates the action to add todo’s. It takes a single string variable `content` and returns an `ADD_TODO` action with `payload` containing a self-incremented `id` and `content` - `toggleTodo` creates the action to toggle todo’s. It takes a single number variable `id` and returns a `TOGGLE_TODO` action with `payload` containing `id` only - `setFilter` creates the action to set the app’s active filter. It takes a single string variable `filter` and returns a `SET_FILTER` action with `payload` containing the `filter` itself +- Reducers + - The `todos` reducer + - Appends the `id` to its `allIds` field and sets the todo within its `byIds` field upon receiving the `ADD_TODO` action + - Toggles the `completed` field for the todo upon receiving the `TOGGLE_TODO` action + - The `visibilityFilters` reducer sets its slice of store to the new filter it receives from the `SET_FILTER` action payload - Action Types - We use a file `actionTypes.js` to hold the constants of action types to be reused - Selectors - - `getTodoList` returns the `todoList` state + - `getTodoList` returns the `allIds` list from the `todos` store - `getTodoById` finds the todo in the store given by `id` - - `getTodos` is slightly more complex. It takes all the `id`s from `todoList` state, finds each todo in the `todoMap` state, and returns the final array of todo’s. + - `getTodos` is slightly more complex. It takes all the `id`s from `allIds`, finds each todo in `byIds`, and returns the final array of todo’s. + - `getTodosByVisibilityFilter` filters the todos according to the visibility filter + +Once again you may expand the code below or check out this CodeSandbox here [Todo App (UI + Unconnected Redux)](https://codesandbox.io/s/6vwyqrpqk3).
    Expand Code @@ -303,8 +362,7 @@ Suppose also that we have created the [Redux](https://github.com/reduxjs/redux) └── redux ├── reducers │ ├── index.js -│ ├── todoList.js -│ ├── todoMap.js +│ ├── todos.js │ └── visibilityFilters.js ├── actionTypes.js ├── actions.js @@ -331,56 +389,47 @@ export default combineReducers({ todoList, todoMap, visibilityFilter }); ``` ```JavaScript -// redux/reducers/todoList.js -import { ADD_TODO } from "../actionTypes"; - -const defaultState = []; -const todoList = (state = defaultState, action) => { - switch (action.type) { - case ADD_TODO: { - const { id } = action.payload; - return [...state, id]; - } - default: - return state; - } -}; - -export default todoList; -``` - -```JavaScript -// redux/reducers/todoMap.js +// redux/reducers/todos.js import { ADD_TODO, TOGGLE_TODO } from "../actionTypes"; -const defaultState = {}; +const initialState = { + allIds: [], + byIds: {} +}; -const todoMap = (state = defaultState, action) => { +export default function(state = initialState, action) { switch (action.type) { case ADD_TODO: { const { id, content } = action.payload; return { ...state, - [id]: { - content, - completed: false + allIds: [...state.allIds, id], + byIds: { + ...state.byIds, + [id]: { + content, + completed: false + } } }; } case TOGGLE_TODO: { const { id } = action.payload; - const currentTodo = state[id]; return { ...state, - [id]: { ...currentTodo, completed: !currentTodo.completed } + byIds: { + ...state.byIds, + [id]: { + ...state.byIds[id], + completed: !state.byIds[id].completed + } + } }; } default: return state; } -}; - -export default todoMap; +} ``` ```JavaScript @@ -428,9 +477,15 @@ export const setFilter = filter => ({ type: SET_FILTER, payload: { filter } }); ```JavaScript // redux/selectors.js -export const getTodoList = store => store.todoList; +import { VISIBILITY_FILTERS } from "../constants"; + +export const getTodoList = store => + store && store.todos ? store.todos.allIds : []; -export const getTodoById = (store, id) => ({ ...store.todoMap[id], id }); +export const getTodoById = (store, id) => + store && store.todos && store.todos.byIds + ? { ...store.todos.byIds[id], id } + : {}; /** * example of a slightly more complex selector @@ -438,6 +493,19 @@ export const getTodoById = (store, id) => ({ ...store.todoMap[id], id }); */ export const getTodos = store => getTodoList(store).map(id => getTodoById(store, id)); + +export const getTodosByVisibilityFilter = (store, visibilityFilter) => { + const allTodos = getTodos(store); + switch (visibilityFilter) { + case VISIBILITY_FILTERS.COMPLETED: + return allTodos.filter(todo => todo.completed); + case VISIBILITY_FILTERS.INCOMPLETE: + return allTodos.filter(todo => !todo.completed); + case VISIBILITY_FILTERS.ALL: + default: + return allTodos; + } +}; ``` ```JavaScript @@ -449,13 +517,11 @@ export const SET_FILTER = "SET_FILTER";
    -Once again you may check out the code here [CodeSandbox](https://codesandbox.io/s/5w8l097704) - -We now show how to connect this store to our app using `react-redux`. +We now show how to connect this store to our app using React-Redux. ### Providing the Store -First we need to make the `store` available to our app. To do this, we wrap our app with the `` api provided by `react-redux`. +First we need to make the `store` available to our app. To do this, we wrap our app with the `` api provided by React-Redux. ```jsx // index.js @@ -477,13 +543,17 @@ ReactDOM.render( Notice how our `` is now wrapped with the `` with `store` passed in as a prop. The `store` object has a few methods that do their magic. But we won’t go into them, yet. We’ll explain them later in the “whys and hows” section. -![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536403768209_image.png) +![](https://i.imgur.com/LV0XvwA.png) ### Connecting the Components -Our components need to read values from the [Redux](http://redux.js.org/) store (and re-read the values when the store updates). They also need to dispatch actions to trigger updates. +Our components need to read values from the Redux store (and re-read the values when the store updates). They also need to dispatch actions to trigger updates. -`connect` takes in two parameters. The first one allows you to define which pieces of data from the store are needed by this component. The second one allows you to indicate which actions that component might dispatch. By convention, they are called `mapStateToProps` and `mapDispatchToProps`, respectively. The return of this call is another function that waits for you to feed in your component on a second call. This is one of a common practices of implementing _higher order components._ Feel free to read more on that. +`connect` takes in two parameters. +The first one allows you to define which pieces of data from the store are needed by this component. +The second one allows you to indicate which actions that component might dispatch. By convention, they are called `mapStateToProps` and `mapDispatchToProps`, respectively. +The return of this call is another function that accepts the component on a second call. +This is an example of a pattern called [_higher order components_](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e). Let’s work on `` first. It needs to trigger changes to the `store` to add new todos. Therefore, it needs to be able to `dispatch` actions to the store. Here’s how we do it. @@ -504,7 +574,6 @@ export const addTodo = content => ({ // ... other actions ``` - By passing it to `connect`, our component receives it as a prop, and it will automatically dispatch the action when it’s called. ```jsx @@ -526,9 +595,9 @@ export default connect( Notice now that `` is wrapped with a parent component called ``. Meanwhile, `` now gains one prop: the `addTodo` action. -![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536402763972_image.png) +![](https://i.imgur.com/u6aXbwl.png) -Try to type something in the input box and click “Add Todo”. Nothing happens. Why? We still need to implement what triggers the dispatch of our action `addTodo`. That would be the `onClick` event of the “Add Todo” button: +We also need to implement the `handleAddTodo` function to let it dispatch the `addTodo` action and reset the input ```jsx // components/AddTodo.js @@ -538,23 +607,24 @@ import { connect } from "react-redux"; import { addTodo } from "../redux/actions"; class AddTodo extends React.Component { - constructor(props) { - super(props); - this.state = { input: "" }; - } + // ... - updateInput = input => { - this.setState({ input }); + handleAddTodo = () => { + // dispatches actions to add todo + this.props.addTodo(this.state.input); + + // sets state back to empty string + this.setState({ input: "" }); }; render() { return (
    - this.updateInput(e.target.value)} /> -
    @@ -563,16 +633,19 @@ class AddTodo extends React.Component { } export default connect( - null, // will not subscribe to the store + null, { addTodo } )(AddTodo); + ``` Now our `` is connected to the store. It _should_ dispatch an action to change the store upon the user’s add todo interaction. But we are unable to see it before our `` is also connected to the store. So let’s do it now. -The `` component is responsible for rendering the list of todos. Therefore, it needs to read data from the store. We enable it by calling `connect` with the `mapState` parameter, a function describing which part of the data we need from the store. +The `` component is responsible for rendering the list of todos. Therefore, it needs to read data from the store. We enable it by calling `connect` with the `mapStateToProps` parameter, a function describing which part of the data we need from the store. -Our `` component takes the todo item as props. We have this information from the `todoMap` store. However, we also need the information from the `todoList` store indicating which todos and in what order they should be rendered. Our `mapStateToProps` function may look like this: +Our `` component takes the todo item as props. We have this information from the `byIds` field of the `todos`. +However, we also need the information from the `allIds` field of the store indicating which todos and in what order they should be rendered. +Our `mapStateToProps` function may look like this: ```jsx // components/TodoList.js @@ -583,9 +656,11 @@ import { connect } from "react-redux"; const TodoList = // ... UI component implementation const mapStateToProps = state => { - const { todoList, todoMap } = state; - const todos = []; - todoList.map(id => todos.push({ ...todoMap[id], id })); + const { byIds, allIds } = state.todos || {}; + const todos = + allIds && state.todos.allIds.length + ? allIds.map(id => (byIds ? { ...byIds[id], id } : null)) + : null; return { todos }; }; @@ -594,6 +669,25 @@ export default connect(mapStateToProps)(TodoList); Luckily we have a selector that does exactly this. We may simply import the selector and use it here. +```jsx +// redux/selectors.js + +export const getTodoList = store => + store && store.todos ? store.todos.allIds : []; + +export const getTodoById = (store, id) => + store && store.todos && store.todos.byIds + ? { ...store.todos.byIds[id], id } + : {}; + +/** + * example of a slightly more complex selector + * select from store combining information from multiple reducers + */ +export const getTodos = store => + getTodoList(store).map(id => getTodoById(store, id)); +``` + ```jsx // components/TodoList.js @@ -608,9 +702,10 @@ export default connect(state => ({ todos: getTodos(state) }))(TodoList); So that provides with a motivation to write selector functions for complex computation. You may further optimize by using [Reselect](https://github.com/reduxjs/reselect). -Now that our `` is connected to the store. It should receive the list of todos, map over them, and pass each todo to the `` component, which will in turn render them to the screen. +Now that our `` is connected to the store. It should receive the list of todos, map over them, and pass each todo to the `` component. `` will in turn render them to the screen. +Now try adding a todo. It should come up on our todo list! -![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536405730234_image.png) +![](https://i.imgur.com/N68xvrG.png) We will connect more components. Before we do this, let’s pause and learn a bit more about `connect` first. @@ -619,51 +714,72 @@ Before we do this, let’s pause and learn a bit more about `connect` first. Depending on what kind of components you are working with, there are different ways of calling `connect` , with the most common ones summarized as below: -| | Subscribe to Store | Do Not Subscribe to Store | -| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| Inject Action Creators | `connect(mapStateToProps,` ` mapDispatchToProps``)(Component) ` | `connect(null,` ` mapDispatchToProps``)(Component) ` | -| Do Not Inject Action Creators | `connect(mapStateToProps)(Component)` | `connect()(Component)` _Component will receive_ `*dispatch*` | +| | Do Not Subscribe to Store | Subscribe to Store | +| ----------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- | +| Do Not Inject Action Creators | `connect()(Component)` _Component will receive_ `*dispatch*` | `connect(mapStateToProps)(Component)` | +| Inject Action Creators | `connect(null, mapDispatchToProps)(Component)` | `connect(mapStateToProps, mapDispatchToProps)(Component)` | + +#### Do not subscribe to the store and do not inject action creators -#### 1. Subscribe to the store and inject action creators +Not providing neither `mapStateToProps` nor `mapDispatchToProps`, your component will: +- _not_ re-render on store changes +- receive `props.dispatch` that you may use to manually dispatch action creators. +```jsx +// ... Component +export default connect()(Component); // Component will receive `dispatch` (just like our !) ``` -import { addTodo } from './actionCreators'; + +#### Subscribe to the store and do not inject action creators + +Providing `mapStateToProps` and not providing `mapDispatchToProps`, your component will: + +- subscribe to the slice of store returned by `mapStateToProps` and re-render when that part of store changes only +- receive `props.dispatch` that you may use to manually dispatch action creators + +```jsx // ... Component const mapStateToProps = state => state.partOfState; -export default connect(mapStateToProps, { addTodo })(Component); +export default connect(mapStateToProps)(Component); ``` -#### 2. Do not subscribe to the store and inject action creators +#### Do not subscribe to the store and inject action creators + +Providing `mapDispatchToProps` and not providing `mapStateToProps`, your component will: +- _not_ re-render when the store changes +- receive each of the action creators you inject with `mapDispatchToProps` as props and will automatically dispatch them upon being called. ```jsx -import * as actionCreators from "./actionCreators"; +import { addTodo } from "./actionCreators"; // ... Component export default connect( null, - actionCreators + { addTodo } )(Component); ``` -#### 3. Subscribe to the store and do not inject action creators +#### Subscribe to the store and inject action creators + +Providing `connect` with both `mapStateToProps` and `mapDispatchToProps`, your component will: +- subscribe to the slice of store returned by `mapStateToProps` and re-render when that part of store changes only +- receive all of the action creators you inject with `mapDispatchToProps` as props and will automatically dispatch them upon being called. ```jsx +import * as actionCreators from './actionCreators'; // ... Component const mapStateToProps = state => state.partOfState; -export default connect(mapStateToProps)(Component); +export default connect(mapStateToProps, actionCreators)(Component); ``` -#### 4. Do not subscribe to the store and do not inject action creators - -```jsx -// ... Component -export default connect()(Component); // Component will receive `dispatch` -``` +These four cases cover the most basic usages of `connect`. To read more about `connect`, continue reading our [API section](./api.md) that explains it in more detail. +--- + Now let’s connect the rest of our ``. -How should we implement the interaction of toggling todos? A keen reader might already have an answer. If you have your environment set up and have followed through up until this point, now is a good time to leave it aside and implement the feature by itself. There would be no surprise that we connect our `` to dispatch `toggleTodo` in a similar fashion: +How should we implement the interaction of toggling todos? A keen reader might already have an answer. If you have your environment set up and have followed through up until this point, now is a good time to leave it aside and implement the feature by yourself. There would be no surprise that we connect our `` to dispatch `toggleTodo` in a similar way: ```jsx // components/Todo.js @@ -682,9 +798,12 @@ export default connect( Now our todo’s can be toggled complete. We’re almost there! -![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536419073312_image.png) +![](https://i.imgur.com/4UBXYtj.png) -There are some common practices in implementing React applications. One such example is to separate components into _presentational_ components and _container_ components. [This article](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by Dan Abramov has a nice introduction. If this is a practice that you would like to follow, `react-redux` is with you. Check out here to learn about some of the best practices to program with `react-redux`. +There are some common practices in implementing React applications. One such example is to separate components into _presentational_ and _container_ components. +You use this pattern when you realize that some of your components are more intelligent than others. +And you want to organize your program such that certain components are mainly responsible for connecting to the store (the "containers"), while some other components are mainly responsible for rendering whichever data they receive (the "presentational" components). +[This article](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by Dan Abramov has a nice introduction. Finally, let’s implement our `VisibilityFilters` feature. @@ -708,7 +827,28 @@ export default connect( )(VisibilityFilters); ``` -Meanwhile, we also need to update our `` component to filter todos according to the active filter. Previously the `mapStateToProps` we passed to the `` `connect` function call was simply the selector that selects the whole list of todos. Let’s add the filter within this `mapStateToProps`. +Meanwhile, we also need to update our `` component to filter todos according to the active filter. Previously the `mapStateToProps` we passed to the `` `connect` function call was simply the selector that selects the whole list of todos. Let’s write another selector to help filtering todos by their status. + +```js +// redux/selectors.js + +// ... other selectors + +export const getTodosByVisibilityFilter = (store, visibilityFilter) => { + const allTodos = getTodos(store); + switch (visibilityFilter) { + case VISIBILITY_FILTERS.COMPLETED: + return allTodos.filter(todo => todo.completed); + case VISIBILITY_FILTERS.INCOMPLETE: + return allTodos.filter(todo => !todo.completed); + case VISIBILITY_FILTERS.ALL: + default: + return allTodos; + } +}; +``` + +And connecting to the store with the help of the selector: ```jsx // components/TodoList.js @@ -717,24 +857,25 @@ Meanwhile, we also need to update our `` component to filter todos a const mapStateToProps = state => { const { visibilityFilter } = state; - const allTodos = getTodos(state); - return { - todos: - visibilityFilter === VISIBILITY_FILTERS.ALL - ? allTodos - : visibilityFilter === VISIBILITY_FILTERS.COMPLETED - ? allTodos.filter(todo => todo.completed) - : allTodos.filter(todo => !todo.completed) - }; + const todos = getTodosByVisibilityFilter(state, visibilityFilter); + return { todos }; }; export default connect(mapStateToProps)(TodoList); ``` -## 🎉🎊 +Now we've finished a very simple example of a todo app with React-Redux. All our components are connected! Isn't that nice? 🎉🎊 + +![](https://i.imgur.com/ONqer2R.png) -![](https://d2mxuefqeaa7sj.cloudfront.net/s_630DC123DAA5434307EAAA11ADF93376B702FDF3645C9313F6B041E413AA4611_1536418948690_image.png) +## Links +- [Usage with React](https://redux.js.org/basics/usagewithreact) +- [Using the React-Redux Bindings](https://blog.isquaredsoftware.com/presentations/workshops/redux-fundamentals/react-redux.html) +- [Higher Order Components in Depth](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e) +- [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) -## References +## Get More Help -- Usage with React https://redux.js.org/basics/usagewithreact +- [Reactiflux](https://www.reactiflux.com) Redux channel +- [StackOverflow](https://stackoverflow.com/questions/tagged/react-redux) +- [GitHub Issues](https://github.com/reduxjs/react-redux/issues/) From b6b3651af1eeba88168744fcb94e56665fac67ad Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Mon, 17 Sep 2018 12:25:41 +0800 Subject: [PATCH 3/3] Revisions 2: Refactor selectors to alleviate verbose existence checks, add illustrations on store changes with DevTools, and remove the paragraph on containers. --- docs/GettingStarted.md | 191 ++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 117 deletions(-) diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 3fe42def1..49d45bac6 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -1,9 +1,6 @@ # Getting Started -[React-Redux](https://github.com/reduxjs/react-redux) is the official [React](https://reactjs.org/) binding for [Redux](https://redux.js.org/). -Redux has no knowledge of React. -You may create your Redux app using other UI rendering frameworks, or not using any frameworks at all. -It lets your React components read data and dispatch actions from and to a Redux store. +[React-Redux](https://github.com/reduxjs/react-redux) is the official [React](https://reactjs.org/) binding for [Redux](https://redux.js.org/). ​​It lets your React components read data from a Redux store, and dispatch actions to the store to update data. ## Installation @@ -23,7 +20,7 @@ yarn add react-redux ## `` and `connect` -**React-Redux provides the `` that serves the Redux store of your app:** +React-Redux consists of two main pieces. The first is a component called ``, which makes the Redux store available to the rest of your app: ```js import React from "react"; @@ -43,7 +40,7 @@ ReactDOM.render( ); ``` -**React-Redux also provides the `connect()` function to help you connect a React component to the Redux `store`.** +The second piece is a function called `connect()`, which encapsulates the process of talking to the store. It enables you to: @@ -54,7 +51,9 @@ Correspondingly, the `connect` function takes two arguments, both optional: - `mapStateToProps`: called every time the store state changes. It receives the entire store state, and should return an object of data this component needs. -- `mapDispatchToProps`: called once on component creation. It receives the dispatch method, and should return an object full of functions that use dispatch. This param _can_ be an object as well. And it will be how you normally use it. If `connect` receives an object full of action creators for this param, it binds `dispatch` for you automatically. +- `mapDispatchToProps`: this parameter can either be a function, or an object. + - If it’s a function, it will be called once on component creation. It will receive `dispatch` as an argument, and should return an object full of functions that use `dispatch` to dispatch actions. + - If it’s an object full of action creators, each action creator will be turned into a prop function that automatically dispatches its action when called. **Note**: We recommend using this “object shorthand” form. Normally, you’ll call `connect` in this way: @@ -84,7 +83,7 @@ connect( ## A Todo List Example -We show a step-by-step example by creating a todo list app using React-Redux. +To see this in practice, we’ll show a step-by-step example by creating a todo list app using React-Redux. **Jump to** @@ -109,7 +108,7 @@ We have implemented our React UI components as follows: - It accepts an `activeFilter` prop from the parent that indicates which filter is currently selected by the user. An active filter is rendered with an underscore. - It dispatches the `setFilter` action to update the selected filter. - `constants` holds the constants data for our app. -- And finally `style.css` is the stylesheet, and `index` renders our app to the DOM. +- And finally `index` renders our app to the DOM. You may check out the sourcecode below or check out this CodeSandbox: [Todo App UI Only](https://codesandbox.io/s/mo7p88po0j). @@ -124,10 +123,9 @@ You may check out the sourcecode below or check out this CodeSandbox: [Todo App │ ├── TodoList.js │ ├── Todo.js │ └── VisibilityFilters.js -├── index.js ├── TodoApp.js ├── constants.js -└── style.css +└── index.js ``` ```jsx @@ -136,7 +134,6 @@ import React from "react"; import AddTodo from "./components/AddTodo"; import TodoList from "./components/TodoList"; import VisibilityFilters from "./components/VisibilityFilters"; -import "./styles.css"; export default function TodoApp() { return ( @@ -265,17 +262,6 @@ const VisibilityFilters = ({ activeFilter }) => { export default VisibilityFilters; ``` -```jsx -// index.js -import React from "react"; -import ReactDOM from "react-dom"; - -import TodoApp from "./TodoApp"; - -const rootElement = document.getElementById("root"); -ReactDOM.render(, rootElement); -``` - ```JavaScript // constants.js export const VISIBILITY_FILTERS = { @@ -285,45 +271,15 @@ export const VISIBILITY_FILTERS = { }; ``` -```css -/** styles.css**/ - -.todo-app { - font-family: sans-serif; -} - -/** add todo **/ -.add-todo { - margin-left: 0.5rem; -} - -/** todo list **/ -.todo-list { - margin-top: 1rem; - text-align: left; - list-style: none; -} +```jsx +// index.js +import React from "react"; +import ReactDOM from "react-dom"; -/** todo item **/ -.todo-item { - font-family: monospace; - cursor: pointer; - line-height: 1.5; -} -.todo-item__text--completed { - text-decoration: line-through; - color: lightgray; -} +import TodoApp from "./TodoApp"; -/** visibility filters **/ -.filter { - padding: 0.3rem 0; - margin: 0 0.3rem; - cursor: pointer; -} -.filter--active { - border-bottom: 1px solid black; -} +const rootElement = document.getElementById("root"); +ReactDOM.render(, rootElement); ```
  • @@ -333,11 +289,11 @@ export const VISIBILITY_FILTERS = { We have also created the Redux as follows. To learn about designing your Redux store, [the official Redux docs](https://redux.js.org/basics) has an excellent guide. - Store - - `todos`: A normalized reducer of todos. It contains a `byIds` map of all todos and a `allIds` that contains the list of all ids. + - `todos`: A normalized reducer of todos. It contains a `byIds` map of all todos and a `allIds` that contains the list of all ids. - `visibilityFilters`: A simple string `all`, `completed`, or `incomplete`. - Action Creators - - `addTodo` creates the action to add todo’s. It takes a single string variable `content` and returns an `ADD_TODO` action with `payload` containing a self-incremented `id` and `content` - - `toggleTodo` creates the action to toggle todo’s. It takes a single number variable `id` and returns a `TOGGLE_TODO` action with `payload` containing `id` only + - `addTodo` creates the action to add todos. It takes a single string variable `content` and returns an `ADD_TODO` action with `payload` containing a self-incremented `id` and `content` + - `toggleTodo` creates the action to toggle todos. It takes a single number variable `id` and returns a `TOGGLE_TODO` action with `payload` containing `id` only - `setFilter` creates the action to set the app’s active filter. It takes a single string variable `filter` and returns a `SET_FILTER` action with `payload` containing the `filter` itself - Reducers - The `todos` reducer @@ -349,7 +305,7 @@ We have also created the Redux as follows. To learn about designing your Redux s - Selectors - `getTodoList` returns the `allIds` list from the `todos` store - `getTodoById` finds the todo in the store given by `id` - - `getTodos` is slightly more complex. It takes all the `id`s from `allIds`, finds each todo in `byIds`, and returns the final array of todo’s. + - `getTodos` is slightly more complex. It takes all the `id`s from `allIds`, finds each todo in `byIds`, and returns the final array of todos - `getTodosByVisibilityFilter` filters the todos according to the visibility filter Once again you may expand the code below or check out this CodeSandbox here [Todo App (UI + Unconnected Redux)](https://codesandbox.io/s/6vwyqrpqk3). @@ -479,21 +435,21 @@ export const setFilter = filter => ({ type: SET_FILTER, payload: { filter } }); // redux/selectors.js import { VISIBILITY_FILTERS } from "../constants"; +export const getTodosState = store => store.todos; + export const getTodoList = store => - store && store.todos ? store.todos.allIds : []; + getTodosState(store) ? getTodosState(store).allIds : []; export const getTodoById = (store, id) => - store && store.todos && store.todos.byIds - ? { ...store.todos.byIds[id], id } - : {}; + getTodoState(store) ? { ...getTodosState(store).byIds[id], id } : {}; + +export const getTodos = store => + getTodoList(store).map(id => getTodoById(store, id)); /** * example of a slightly more complex selector * select from store combining information from multiple reducers */ -export const getTodos = store => - getTodoList(store).map(id => getTodoById(store, id)); - export const getTodosByVisibilityFilter = (store, visibilityFilter) => { const allTodos = getTodos(store); switch (visibilityFilter) { @@ -521,7 +477,7 @@ We now show how to connect this store to our app using React-Redux. ### Providing the Store -First we need to make the `store` available to our app. To do this, we wrap our app with the `` api provided by React-Redux. +First we need to make the `store` available to our app. To do this, we wrap our app with the `` API provided by React-Redux. ```jsx // index.js @@ -541,7 +497,7 @@ ReactDOM.render( ); ``` -Notice how our `` is now wrapped with the `` with `store` passed in as a prop. The `store` object has a few methods that do their magic. But we won’t go into them, yet. We’ll explain them later in the “whys and hows” section. +Notice how our `` is now wrapped with the `` with `store` passed in as a prop. ![](https://i.imgur.com/LV0XvwA.png) @@ -549,11 +505,7 @@ Notice how our `` is now wrapped with the `` with `store` Our components need to read values from the Redux store (and re-read the values when the store updates). They also need to dispatch actions to trigger updates. -`connect` takes in two parameters. -The first one allows you to define which pieces of data from the store are needed by this component. -The second one allows you to indicate which actions that component might dispatch. By convention, they are called `mapStateToProps` and `mapDispatchToProps`, respectively. -The return of this call is another function that accepts the component on a second call. -This is an example of a pattern called [_higher order components_](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e). +`connect` takes in two parameters. The first one allows you to define which pieces of data from the store are needed by this component. The second one allows you to indicate which actions that component might dispatch. By convention, they are called `mapStateToProps` and `mapDispatchToProps`, respectively. The return of this call is another function that accepts the component on a second call. This is an example of a pattern called [_higher order components_](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e). Let’s work on `` first. It needs to trigger changes to the `store` to add new todos. Therefore, it needs to be able to `dispatch` actions to the store. Here’s how we do it. @@ -574,6 +526,7 @@ export const addTodo = content => ({ // ... other actions ``` + By passing it to `connect`, our component receives it as a prop, and it will automatically dispatch the action when it’s called. ```jsx @@ -636,16 +589,19 @@ export default connect( null, { addTodo } )(AddTodo); - ``` -Now our `` is connected to the store. It _should_ dispatch an action to change the store upon the user’s add todo interaction. But we are unable to see it before our `` is also connected to the store. So let’s do it now. +Now our `` is connected to the store. When we add a todo it would dispatch an action to change the store. We are not seeing it in the app because the other components are not connected yet. If you have the Redux DevTools Extension hooked up, you should see the action being dispatched: + +![](https://i.imgur.com/kHvkqhI.png) + +You should also see that the store has changed accordingly: + +![](https://i.imgur.com/yx27RVC.png) The `` component is responsible for rendering the list of todos. Therefore, it needs to read data from the store. We enable it by calling `connect` with the `mapStateToProps` parameter, a function describing which part of the data we need from the store. -Our `` component takes the todo item as props. We have this information from the `byIds` field of the `todos`. -However, we also need the information from the `allIds` field of the store indicating which todos and in what order they should be rendered. -Our `mapStateToProps` function may look like this: +Our `` component takes the todo item as props. We have this information from the `byIds` field of the `todos`. However, we also need the information from the `allIds` field of the store indicating which todos and in what order they should be rendered. Our `mapStateToProps` function may look like this: ```jsx // components/TodoList.js @@ -658,7 +614,7 @@ const TodoList = // ... UI component implementation const mapStateToProps = state => { const { byIds, allIds } = state.todos || {}; const todos = - allIds && state.todos.allIds.length + allIds && allIds.length ? allIds.map(id => (byIds ? { ...byIds[id], id } : null)) : null; return { todos }; @@ -672,13 +628,13 @@ Luckily we have a selector that does exactly this. We may simply import the sele ```jsx // redux/selectors.js +export const getTodosState = store => store.todos; + export const getTodoList = store => - store && store.todos ? store.todos.allIds : []; + getTodosState(store) ? getTodosState(store).allIds : []; export const getTodoById = (store, id) => - store && store.todos && store.todos.byIds - ? { ...store.todos.byIds[id], id } - : {}; + getTodoState(store) ? { ...getTodosState(store).byIds[id], id } : {}; /** * example of a slightly more complex selector @@ -700,30 +656,29 @@ const TodoList = // ... UI component implementation export default connect(state => ({ todos: getTodos(state) }))(TodoList); ``` -So that provides with a motivation to write selector functions for complex computation. You may further optimize by using [Reselect](https://github.com/reduxjs/reselect). +So that provides with a motivation to write selector functions for complex computation. You may further optimize the performance by using [Reselect](https://github.com/reduxjs/reselect) to write “memoized” selectors that can skip unnecessary work. See [this Redux’s docs page on Computing Derived Data](https://redux.js.org/recipes/computingderiveddata#sharing-selectors-across-multiple-components) for more information on using selectors. -Now that our `` is connected to the store. It should receive the list of todos, map over them, and pass each todo to the `` component. `` will in turn render them to the screen. -Now try adding a todo. It should come up on our todo list! +Now that our `` is connected to the store. It should receive the list of todos, map over them, and pass each todo to the `` component. `` will in turn render them to the screen. Now try adding a todo. It should come up on our todo list! ![](https://i.imgur.com/N68xvrG.png) -We will connect more components. -Before we do this, let’s pause and learn a bit more about `connect` first. +We will connect more components. Before we do this, let’s pause and learn a bit more about `connect` first. ### Common ways of calling `connect` Depending on what kind of components you are working with, there are different ways of calling `connect` , with the most common ones summarized as below: -| | Do Not Subscribe to Store | Subscribe to Store | -| ----------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- | -| Do Not Inject Action Creators | `connect()(Component)` _Component will receive_ `*dispatch*` | `connect(mapStateToProps)(Component)` | -| Inject Action Creators | `connect(null, mapDispatchToProps)(Component)` | `connect(mapStateToProps, mapDispatchToProps)(Component)` | +| | Do Not Subscribe to the Store | Subscribe to the Store | +| ----------------------------- | ---------------------------------------------- | --------------------------------------------------------- | +| Do Not Inject Action Creators | `connect()(Component)` | `connect(mapStateToProps)(Component)` | +| Inject Action Creators | `connect(null, mapDispatchToProps)(Component)` | `connect(mapStateToProps, mapDispatchToProps)(Component)` | #### Do not subscribe to the store and do not inject action creators -Not providing neither `mapStateToProps` nor `mapDispatchToProps`, your component will: -- _not_ re-render on store changes -- receive `props.dispatch` that you may use to manually dispatch action creators. +If you call `connect` without providing any arguments, your component will: + +- _not_ re-render when the store changes +- receive `props.dispatch` that you may use to manually dispatch action ```jsx // ... Component @@ -732,10 +687,10 @@ export default connect()(Component); // Component will receive `dispatch` (just #### Subscribe to the store and do not inject action creators -Providing `mapStateToProps` and not providing `mapDispatchToProps`, your component will: +If you call `connect` with only `mapStateToProps`, your component will: -- subscribe to the slice of store returned by `mapStateToProps` and re-render when that part of store changes only -- receive `props.dispatch` that you may use to manually dispatch action creators +- subscribe to the values that `mapStateToProps` extracts from the store, and re-render only when those values have changed +- receive `props.dispatch` that you may use to manually dispatch action ```jsx // ... Component @@ -745,9 +700,10 @@ export default connect(mapStateToProps)(Component); #### Do not subscribe to the store and inject action creators -Providing `mapDispatchToProps` and not providing `mapStateToProps`, your component will: +If you call `connect` with only `mapDispatchToProps`, your component will: + - _not_ re-render when the store changes -- receive each of the action creators you inject with `mapDispatchToProps` as props and will automatically dispatch them upon being called. +- receive each of the action creators you inject with `mapDispatchToProps` as props and automatically dispatch the actions upon being called ```jsx import { addTodo } from "./actionCreators"; @@ -760,18 +716,22 @@ export default connect( #### Subscribe to the store and inject action creators -Providing `connect` with both `mapStateToProps` and `mapDispatchToProps`, your component will: -- subscribe to the slice of store returned by `mapStateToProps` and re-render when that part of store changes only -- receive all of the action creators you inject with `mapDispatchToProps` as props and will automatically dispatch them upon being called. +If you call `connect` with both `mapStateToProps` and `mapDispatchToProps`, your component will: + +- subscribe to the values that `mapStateToProps` extracts from the store, and re-render only when those values have changed +- receive all of the action creators you inject with `mapDispatchToProps` as props and automatically dispatch the actions upon being called. ```jsx -import * as actionCreators from './actionCreators'; +import * as actionCreators from "./actionCreators"; // ... Component const mapStateToProps = state => state.partOfState; -export default connect(mapStateToProps, actionCreators)(Component); +export default connect( + mapStateToProps, + actionCreators +)(Component); ``` -These four cases cover the most basic usages of `connect`. To read more about `connect`, continue reading our [API section](./api.md) that explains it in more detail. +These four cases cover the most basic usages of `connect`. To read more about `connect`, continue reading our [API section](./api.md) that explains it in more detail. @@ -800,11 +760,6 @@ Now our todo’s can be toggled complete. We’re almost there! ![](https://i.imgur.com/4UBXYtj.png) -There are some common practices in implementing React applications. One such example is to separate components into _presentational_ and _container_ components. -You use this pattern when you realize that some of your components are more intelligent than others. -And you want to organize your program such that certain components are mainly responsible for connecting to the store (the "containers"), while some other components are mainly responsible for rendering whichever data they receive (the "presentational" components). -[This article](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by Dan Abramov has a nice introduction. - Finally, let’s implement our `VisibilityFilters` feature. The `` component needs to be able to read from the store which filter is currently active, and dispatch actions to the store. Therefore, we need to pass both a `mapStateToProps` and `mapDispatchToProps`. The `mapStateToProps` here can be a simple accessor of the `visibilityFilter` state. And the `mapDispatchToProps` will contain the `setFilter` action creator. @@ -869,10 +824,12 @@ Now we've finished a very simple example of a todo app with React-Redux. All our ![](https://i.imgur.com/ONqer2R.png) ## Links + - [Usage with React](https://redux.js.org/basics/usagewithreact) - [Using the React-Redux Bindings](https://blog.isquaredsoftware.com/presentations/workshops/redux-fundamentals/react-redux.html) - [Higher Order Components in Depth](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e) -- [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) + +- [Computing Derived Data](https://redux.js.org/recipes/computingderiveddata#sharing-selectors-across-multiple-components) ## Get More Help