- In a React Redux app, you create a single Redux store that manages the state of your entire app. Your React components subscribe to only the pieces of data in the store that are relevant to their role. Then, you dispatch actions directly from React components, which then trigger store updates.
-
This series of challenges introduces how to use Redux with React. First, here's a review of some of the key principles of each technology. React is a view library that you provide with data, then it renders the view in an efficient, predictable way. Redux is a state management framework that you can use to simplify the management of your application's state. Typically, in a React Redux app, you create a single Redux store that manages the state of your entire app. Your React components subscribe to only the pieces of data in the store that are relevant to their role. Then, you dispatch actions directly from React components, which then trigger store updates.
-
Although React components can manage their own state locally, when you have a complex app, it's generally better to keep the app state in a single location with Redux. There are exceptions when individual components may have local state specific only to them. Finally, because Redux is not designed to work with React out of the box, you need to use the
react-reduxpackage. It provides a way for you to pass Reduxstateanddispatchto your React components asprops. -
Over the next few challenges, first, you'll create a simple React component which allows you to input new text messages. These are added to an array that's displayed in the view. This should be a nice review of what you learned in the React lessons. Next, you'll create a Redux store and actions that manage the state of the messages array. Finally, you'll use
react-reduxto connect the Redux store with your component, thereby extracting the local state into the Redux store.
- Start with a
DisplayMessagescomponent. Add a constructor to this component and initialize it with a state that has two properties:input, that's set to an empty string, andmessages, that's set to an empty array.
class DisplayMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
}
}
render() {
return <div />
}
};- Here you'll finish creating the
DisplayMessagescomponent.
-
First, in the
render()method, have the component render aninputelement,buttonelement, andulelement. When theinputelement changes, it should trigger ahandleChange()method. Also, theinputelement should render the value ofinputthat's in the component's state. Thebuttonelement should trigger asubmitMessage()method when it's clicked. -
Second, write these two methods. The
handleChange()method should update theinputwith what the user is typing. ThesubmitMessage()method should concatenate the current message (stored ininput) to themessagesarray in local state, and clear the value of theinput. -
Finally, use the
ulto map over the array ofmessagesand render it to the screen as a list oflielements.
class DisplayMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
// add handleChange() and submitMessage() methods here
handleChange(event) {
this.setState({
input: event.target.value,
messages: this.state.messages
})
}
submitMessage() {
this.setState({
input: '',
messages: [...this.state.messages, this.state.input]
})
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
{ /* render an input, button, and ul here */ }
<input value = {this.state.input} onChange={this.handleChange}/><br />
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.state.messages.map((item, index) => {
return <li key={index}>{item}</li>
})}
</ul>
</div>
);
}
};- Now that you finished the React component, you need to move the logic it's performing locally in its
stateinto Redux. This is the first step to connect the simple React app to Redux. The only functionality your app has is to add new messages from the user to an unordered list. The example is simple in order to demonstrate how React and Redux work together.
-
First, define an action type 'ADD' and set it to a const
ADD. Next, define an action creatoraddMessage()which creates the action to add a message. You'll need to pass amessageto this action creator and include the message in the returnedaction. -
Then create a reducer called
messageReducer()that handles the state for the messages. The initial state should equal an empty array. This reducer should add a message to the array of messages held in state, or return the current state. Finally, create your Redux store and pass it the reducer.
// define ADD, addMessage(), messageReducer(), and store here:
const ADD = "ADD";
const addMessage = message => {
return {
type: ADD,
message
};
};
// Use ES6 default paramter to give the 'previousState' parameter an initial value.
const messageReducer = (previousState = [], action) => {
// Use switch statement to lay out the reducer logic in response to different action type
switch (action.type) {
case ADD:
// Use ES6 spread operator to return a new array where the new message is added to previousState
return [...previousState, action.message];
break;
default:
// A default case to fall back on in case if the update to Redux store is not for this specific state.
return previousState;
}
};
const store = Redux.createStore(messageReducer);-
In the last challenge, you created a Redux store to handle the messages array and created an action for adding new messages. The next step is to provide React access to the Redux store and the actions it needs to dispatch updates. React Redux provides its
react-reduxpackage to help accomplish these tasks. -
React Redux provides a small API with two key features:
Providerandconnect. Another challenge coversconnect. TheProvideris a wrapper component from React Redux that wraps your React app. This wrapper then allows you to access the Reduxstoreanddispatchfunctions throughout your component tree.Providertakes two props, the Redux store and the child components of your app. Defining theProviderfor an App component might look like this:
<Provider store={store}>
<App/>
</Provider>-
The code editor now shows all your Redux and React code from the past several challenges. It includes the Redux store, actions, and the
DisplayMessagescomponent. The only new piece is theAppWrappercomponent at the bottom. Use this top level component to render theProviderfromReactRedux, and pass the Redux store as a prop. Then render theDisplayMessagescomponent as a child. Once you are finished, you should see your React component rendered to the page. -
Note: React Redux is available as a global variable here, so you can access the Provider with dot notation. The code in the editor takes advantage of this and sets it to a constant
Providerfor you to use in theAppWrapperrender method.
// Redux Code:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React Code:
class DisplayMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
const currentMessage = this.state.input;
this.setState({
input: '',
messages: this.state.messages.concat(currentMessage)
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.state.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
const Provider = ReactRedux.Provider;
class AppWrapper extends React.Component {
// render the Provider here
render() {
return (
<Provider store={store}>
<DisplayMessages />
</Provider>
)
}
// change code above this line
};-
The
Providercomponent allows you to providestateanddispatchto your React components, but you must specify exactly what state and actions you want. This way, you make sure that each component only has access to the state it needs. You accomplish this by creating two functions:mapStateToProps()andmapDispatchToProps(). -
In these functions, you declare what pieces of state you want to have access to and which action creators you need to be able to dispatch. Once these functions are in place, you'll see how to use the React Redux
connectmethod to connect them to your components in another challenge. -
Note: Behind the scenes, React Redux uses the
store.subscribe()method to implementmapStateToProps().
- Create a function
mapStateToProps(). This function should takestateas an argument, then return an object which maps that state to specific property names. These properties will become accessible to your component viaprops. Since this example keeps the entire state of the app in a single array, you can pass that entire state to your component. Create a propertymessagesin the object that's being returned, and set it tostate.
const state = [];
const mapStateToProps = (state) => {
return {
messages: state
}
}-
The
mapDispatchToProps()function is used to provide specific action creators to your React components so they can dispatch actions against the Redux store. It's similar in structure to themapStateToProps()function you wrote in the last challenge. It returns an object that maps dispatch actions to property names, which become componentprops. However, instead of returning a piece ofstate, each property returns a function that callsdispatchwith an action creator and any relevant action data. You have access to thisdispatchbecause it's passed in tomapDispatchToProps()as a parameter when you define the function, just like you passedstatetomapStateToProps(). Behind the scenes, React Redux is using Redux'sstore.dispatch()to conduct these dispatches withmapDispatchToProps(). This is similar to how it usesstore.subscribe()for components that are mapped tostate. -
For example, you have a
loginUser()action creator that takes ausernameas an action payload. The object returned frommapDispatchToProps()for this action creator would look something like:
{
submitLoginUser: function(username) {
dispatch(loginUser(username));
}
}- The code editor provides an action creator called
addMessage(). Write the functionmapDispatchToProps()that takesdispatchas an argument, then returns an object. The object should have a propertysubmitNewMessageset to the dispatch function, which takes a parameter for the new message to add when it dispatchesaddMessage().
const addMessage = (message) => {
return {
type: 'ADD',
message: message
}
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message)=>{
dispatch(addMessage(message))
}
}
}-
Now that you've written both the
mapStateToProps()and themapDispatchToProps()functions, you can use them to mapstateanddispatchto thepropsof one of your React components. Theconnectmethod from React Redux can handle this task. This method takes two optional arguments,mapStateToProps()andmapDispatchToProps(). They are optional because you may have a component that only needs access tostatebut doesn't need to dispatch any actions, or vice versa. -
To use this method, pass in the functions as arguments, and immediately call the result with your component. This syntax is a little unusual and looks like:
connect(mapStateToProps, mapDispatchToProps)(MyComponent)- Note: If you want to omit one of the arguments to the connect method, you pass null in its place.
- The code editor has the
mapStateToProps()andmapDispatchToProps()functions and a new React component calledPresentational. Connect this component to Redux with theconnectmethod from theReactReduxglobal object, and call it immediately on thePresentationalcomponent. Assign the result to a newconstcalledConnectedComponentthat represents the connected component. That's it, now you're connected to Redux! Try changing either ofconnect's arguments tonulland observe the test results.
const addMessage = (message) => {
return {
type: 'ADD',
message: message
}
};
const mapStateToProps = (state) => {
return {
messages: state
}
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message) => {
dispatch(addMessage(message));
}
}
};
class Presentational extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h3>This is a Presentational Component</h3>
}
};
const connect = ReactRedux.connect;
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational)-
Now that you understand how to use
connectto connect React to Redux, you can apply what you've learned to your React component that handles messages. -
In the last lesson, the component you connected to Redux was named
Presentational, and this wasn't arbitrary. This term generally refers to React components that are not directly connected to Redux. They are simply responsible for the presentation of UI and do this as a function of the props they receive. By contrast, container components are connected to Redux. These are typically responsible for dispatching actions to the store and often pass store state to child components as props.
- The code editor has all the code you've written in this section so far. The only change is that the React component is renamed to
Presentational. Create a new component held in a constant calledContainerthat usesconnectto connect thePresentationalcomponent to Redux. Then, in theAppWrapper, render the React ReduxProvidercomponent. PassProviderthe Reduxstoreas a prop and renderContaineras a child. Once everything is setup, you will see the messages app rendered to the page again.
// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message: message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React:
class Presentational extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
const currentMessage = this.state.input;
this.setState({
input: '',
messages: this.state.messages.concat(currentMessage)
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.state.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
// React-Redux:
const mapStateToProps = (state) => {
return { messages: state }
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (newMessage) => {
dispatch(addMessage(newMessage))
}
}
};
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
// define the Container component here:
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational)
class AppWrapper extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={store}>
<Container />
</Provider>
);
}
};
- You're almost done! Recall that you wrote all the Redux code so that Redux could control the state management of your React messages app. Now that Redux is connected, you need to extract the state management out of the
Presentationalcomponent and into Redux. Currently, you have Redux connected, but you are handling the state locally within thePresentationalcomponent.
-
In the
Presentationalcomponent, first, remove themessagesproperty in the localstate. These messages will be managed by Redux. Next, modify thesubmitMessage()method so that it dispatchessubmitNewMessage()fromthis.props, and pass in the current message input from localstateas an argument. Because you removedmessagesfrom local state, remove themessagesproperty from the call tothis.setState()here as well. Finally, modify therender()method so that it maps over the messages received frompropsrather thanstate. -
Once these changes are made, the app will continue to function the same, except Redux manages the state. This example also illustrates how a component may have local
state: your component still tracks user input locally in its ownstate. You can see how Redux provides a useful state management framework on top of React. You achieved the same result using only React's local state at first, and this is usually possible with simple apps. However, as your apps become larger and more complex, so does your state management, and this is the problem Redux solves.
// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message: message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React:
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
class Presentational extends React.Component {
constructor(props) {
super(props);
this.state = {
input: ''
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.props.submitNewMessage(this.state.input)
this.setState({
input: ''
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.props.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
const mapStateToProps = (state) => {
return {messages: state}
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message) => {
dispatch(addMessage(message))
}
}
};
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
class AppWrapper extends React.Component {
render() {
return (
<Provider store={store}>
<Container/>
</Provider>
);
}
};-
There's one last item worth pointing out before you move on. Typically, you won't write React apps in a code editor like this. This challenge gives you a glimpse of what the syntax looks like if you're working with npm and a file system on your own machine. The code should look similar, except for the use of
importstatements (these pull in all of the dependencies that have been provided for you in the challenges). The "Managing Packages with npm" section covers npm in more detail. -
Finally, writing React and Redux code generally requires some configuration. This can get complicated quickly. If you are interested in experimenting on your own machine, the
-
Create React App comes configured and ready to go.
-
Alternatively, you can enable Babel as a JavaScript Preprocessor in CodePen, add React and ReactDOM as external JavaScript resources, and work there as well.
// import React from 'react'
// import ReactDOM from 'react-dom'
// import { Provider, connect } from 'react-redux'
// import { createStore, combineReducers, applyMiddleware } from 'redux'
// import thunk from 'redux-thunk'
// import rootReducer from './redux/reducers'
// import App from './components/App'
// const store = createStore(
// rootReducer,
// applyMiddleware(thunk)
// );
// ReactDOM.render(
// <Provider store={store}>
// <App/>
// </Provider>,
// document.getElementById('root')
// );