{ "name": "React and Redux", "order": 7, "time": "5 hours", "helpRoom": "Help", "required": [ { "src": "https://cdnjs.cloudflare.com/ajax/libs/react/16.1.1/umd/react.development.js" }, { "src": "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.1.1/umd/react-dom.development.js" }, { "src": "https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js" }, { "src": "https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js" } ], "template": "
${ source }", "challenges": [ { "id": "5a24c314108439a4d4036141", "title": "Getting Started with React Redux", "releasedOn": "December 25, 2017", "description": [ "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 thereact-redux
package. It provides a way for you to pass Redux state
and dispatch
to your React components as props
.",
"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-redux
to connect the Redux store with your component, thereby extracting the local state into the Redux store.",
"DisplayMessages
component. Add a constructor to this component and initialize it with a state that has two properties: input
, that's set to an empty string, and messages
, that's set to an empty array."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"class DisplayMessages extends React.Component {",
" // change code below this line",
"",
" // change code above this line",
" render() {",
" return ",
" }",
"};"
],
"tail": "ReactDOM.render(DisplayMessages
component should render an empty div
element.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'message: The DisplayMessages
constructor should be called properly with super
, passing in props
.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return typeof initialState === 'object' && initialState.input === '' && Array.isArray(initialState.messages) && initialState.messages.length === 0; })(), 'message: The DisplayMessages
component should have an initial state equal to {input: \"\", messages: []}
.');"
],
"solutions": [
"class DisplayMessages extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n messages: []\n }\n }\n render() {\n return \n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"react": true
},
{
"id": "5a24c314108439a4d4036142",
"title": "Manage State Locally First",
"releasedOn": "December 25, 2017",
"description": [
"Here you'll finish creating the DisplayMessages
component.",
"render()
method, have the component render an input
element, button
element, and ul
element. When the input
element changes, it should trigger a handleChange()
method. Also, the input
element should render the value of input
that's in the component's state. The button
element should trigger a submitMessage()
method when it's clicked.",
"Second, write these two methods. The handleChange()
method should update the input
with what the user is typing. The submitMessage()
method should concatenate the current message (stored in input
) to the messages
array in local state, and clear the value of the input
.",
"Finally, use the ul
to map over the array of messages
and render it to the screen as a list of li
elements."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"class DisplayMessages extends React.Component {",
" constructor(props) {",
" super(props);",
" this.state = {",
" input: '',",
" messages: []",
" }",
" }",
" // add handleChange() and submitMessage() methods here",
"",
" render() {",
" return (",
" DisplayMessages
component should initialize with a state equal to { input: \"\", messages: [] }
.');",
"async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const state = () => { mockedComponent.setState({messages: ['__TEST__MESSAGE']}); return waitForIt(() => mockedComponent )}; const updated = await state(); assert(updated.find('div').length === 1 && updated.find('h2').length === 1 && updated.find('button').length === 1 && updated.find('ul').length === 1, 'message: The DisplayMessages
component should render a div
containing an h2
element, a button
element, a ul
element, and li
elements as children.'); }; ",
"async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const testValue = '__TEST__EVENT__INPUT'; const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); assert(updated.find('input').props().value === testValue, 'message: The input
element should render the value of input
in local state.'); }; ",
"async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage = '__TEST__EVENT__MESSAGE__'; const changed = () => { causeChange(mockedComponent, testMessage); return waitForIt(() => mockedComponent )}; const afterInput = await changed(); assert(initialState.input === '' && afterInput.state().input === '__TEST__EVENT__MESSAGE__', 'message: Calling the method handleChange
should update the input
value in state to the current input.'); }; ",
"async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage_1 = '__FIRST__MESSAGE__'; const firstChange = () => { causeChange(mockedComponent, testMessage_1); return waitForIt(() => mockedComponent )}; const firstResult = await firstChange(); const firstSubmit = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterSubmit_1 = await firstSubmit(); const submitState_1 = afterSubmit_1.state(); const testMessage_2 = '__SECOND__MESSAGE__'; const secondChange = () => { causeChange(mockedComponent, testMessage_2); return waitForIt(() => mockedComponent )}; const secondResult = await secondChange(); const secondSubmit = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterSubmit_2 = await secondSubmit(); const submitState_2 = afterSubmit_2.state(); assert(initialState.messages.length === 0 && submitState_1.messages.length === 1 && submitState_2.messages.length === 2 && submitState_2.messages[1] === testMessage_2, 'message: Clicking the Add message
button should call the method submitMessage
which should add the current input
to the messages
array in state.'); }; ",
"async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage = '__FIRST__MESSAGE__'; const firstChange = () => { causeChange(mockedComponent, testMessage); return waitForIt(() => mockedComponent )}; const firstResult = await firstChange(); const firstState = firstResult.state(); const firstSubmit = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterSubmit = await firstSubmit(); const submitState = afterSubmit.state(); assert(firstState.input === testMessage && submitState.input === '', 'message: The submitMessage
method should clear the current input.'); }; "
],
"solutions": [
"class DisplayMessages extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n messages: []\n }\n this.handleChange = this.handleChange.bind(this); \n this.submitMessage = this.submitMessage.bind(this); \n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n submitMessage() {\n const currentMessage = this.state.input;\n this.setState({\n input: '',\n messages: this.state.messages.concat(currentMessage)\n });\n }\n render() {\n return (\n state
into 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.",
"ADD
. Next, define an action creator addMessage()
which creates the action to add a message. You'll need to pass a message
to this action creator and include the message in the returned action
.",
"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."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"// define ADD, addMessage(), messageReducer(), and store here:",
""
]
}
},
"tests": [
"assert(ADD === 'ADD', 'message: The const ADD
should exist and hold a value equal to the string ADD
');",
"assert((function() { const addAction = addMessage('__TEST__MESSAGE__'); return addAction.type === ADD && addAction.message === '__TEST__MESSAGE__'; })(), 'message: The action creator addMessage
should return an object with type
equal to ADD
and message equal to the message that is passed in.');",
"assert(typeof messageReducer === 'function', 'message: messageReducer
should be a function.');",
"assert((function() { const initialState = store.getState(); return typeof store === 'object' && initialState.length === 0; })(), 'message: The store should exist and have an initial state set to an empty array.');",
"assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(addMessage('__A__TEST__MESSAGE')); const addState = store.getState(); return (isFrozen && addState[0] === '__A__TEST__MESSAGE'); })(), 'message: Dispatching addMessage
against the store should immutably add a new message to the array of messages held in state.');",
"assert((function() { const addState = store.getState(); store.dispatch({type: 'FAKE_ACTION'}); const testState = store.getState(); return (addState === testState); })(), 'message: The messageReducer
should return the current state if called with any other actions.');"
],
"solutions": [
"const ADD = 'ADD';\n\nconst addMessage = (message) => {\n return {\n type: ADD,\n message\n }\n};\n\nconst messageReducer = (state = [], action) => {\n switch (action.type) {\n case ADD:\n return [\n ...state,\n action.message\n ];\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(messageReducer);"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
},
{
"id": "5a24c314108439a4d4036144",
"title": "Use Provider to Connect Redux to React",
"releasedOn": "December 25, 2017",
"description": [
"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-redux
package to help accomplish these tasks.",
"React Redux provides a small API with two key features: Provider
and connect
. Another challenge covers connect
. The Provider
is a wrapper component from React Redux that wraps your React app. This wrapper then allows you to access the Redux store
and dispatch
functions throughout your component tree. Provider
takes two props, the Redux store and the child components of your app. Defining the Provider
for an App component might look like this:",
"<Provider store={store}>", "
<App/>
</Provider>
DisplayMessages
component. The only new piece is the AppWrapper
component at the bottom. Use this top level component to render the Provider
from ReactRedux
, and pass the Redux store as a prop. Then render the DisplayMessages
component 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 Provider
for you to use in the AppWrapper
render method."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"// 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 (",
" AppWrapper
should render.');",
"getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/ /g,'').includes('Provider
wrapper component should have a prop of store
passed to it, equal to the Redux store.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })(), 'message: DisplayMessages
should render as a child of AppWrapper
.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('div').length === 1 && mockedComponent.find('h2').length === 1 && mockedComponent.find('button').length === 1 && mockedComponent.find('ul').length === 1; })(), 'message: The DisplayMessages
component should render an h2, input, button, and ul
element.');"
],
"solutions": [
"// Redux Code:\nconst ADD = 'ADD';\n\nconst addMessage = (message) => {\n return {\n type: ADD,\n message\n }\n};\n\nconst messageReducer = (state = [], action) => {\n switch (action.type) {\n case ADD:\n return [\n ...state,\n action.message\n ];\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(messageReducer);\n\n// React Code:\n\nclass DisplayMessages extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n messages: []\n }\n this.handleChange = this.handleChange.bind(this); \n this.submitMessage = this.submitMessage.bind(this); \n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n submitMessage() {\n const currentMessage = this.state.input;\n this.setState({\n input: '',\n messages: this.state.messages.concat(currentMessage)\n });\n }\n render() {\n return (\n Provider
component allows you to provide state
and dispatch
to 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()
and mapDispatchToProps()
.",
"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 connect
method to connect them to your components in another challenge.",
"Note: Behind the scenes, React Redux uses the store.subscribe()
method to implement mapStateToProps()
.",
"mapStateToProps()
. This function should take state
as an argument, then return an object which maps that state to specific property names. These properties will become accessible to your component via props
. 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 property messages
in the object that's being returned, and set it to state
."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const state = [];",
"",
"// change code below this line",
""
]
}
},
"tests": [
"assert(Array.isArray(state) && state.length === 0, 'message: The const state
should be an empty array.');",
"assert(typeof mapStateToProps === 'function', 'message: mapStateToProps
should be a function.');",
"assert(typeof mapStateToProps() === 'object', 'message: mapStateToProps
should return an object.');",
"assert(mapStateToProps(['messages']).messages.pop() === 'messages', 'message: Passing an array as state to mapStateToProps
should return this array assigned to a key of messages
.');"
],
"solutions": [
"const state = [];\n\n// change code below this line\n\nconst mapStateToProps = (state) => {\n return {\n messages: state\n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
},
{
"id": "5a24c314108439a4d4036146",
"title": "Map Dispatch to Props",
"releasedOn": "December 25, 2017",
"description": [
"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 the mapStateToProps()
function you wrote in the last challenge. It returns an object that maps dispatch actions to property names, which become component props
. However, instead of returning a piece of state
, each property returns a function that calls dispatch
with an action creator and any relevant action data. You have access to this dispatch
because it's passed in to mapDispatchToProps()
as a parameter when you define the function, just like you passed state
to mapStateToProps()
. Behind the scenes, React Redux is using Redux's store.dispatch()
to conduct these dispatches with mapDispatchToProps()
. This is similar to how it uses store.subscribe()
for components that are mapped to state
.",
"For example, you have a loginUser()
action creator that takes a username
as an action payload. The object returned from mapDispatchToProps()
for this action creator would look something like:",
"{", "
submitLoginUser: function(username) {
dispatch(loginUser(username));
}
}
addMessage()
. Write the function mapDispatchToProps()
that takes dispatch
as an argument, then returns an object. The object should have a property submitNewMessage
set to the dispatch function, which takes a parameter for the new message to add when it dispatches addMessage()
."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const addMessage = (message) => {",
" return {",
" type: 'ADD',",
" message: message",
" }",
"};",
"",
"// change code below this line",
""
]
}
},
"tests": [
"assert((function() { const addMessageTest = addMessage(); return ( addMessageTest.hasOwnProperty('type') && addMessageTest.hasOwnProperty('message')); })(), 'message: addMessage
should return an object with keys type
and message
.');",
"assert(typeof mapDispatchToProps === 'function', 'message: mapDispatchToProps
should be a function.');",
"assert(typeof mapDispatchToProps() === 'object', 'message: mapDispatchToProps
should return an object.');",
"assert((function() { let testAction; const dispatch = (fn) => { testAction = fn; }; let dispatchFn = mapDispatchToProps(dispatch); dispatchFn.submitNewMessage('__TEST__MESSAGE__'); return (testAction.type === 'ADD' && testAction.message === '__TEST__MESSAGE__'); })(), 'message: Dispatching addMessage
with submitNewMessage
from mapDispatchToProps
should return a message to the dispatch function.');"
],
"solutions": [
"const addMessage = (message) => {\n return {\n type: 'ADD',\n message: message\n }\n};\n\n// change code below this line\n\nconst mapDispatchToProps = (dispatch) => {\n return {\n submitNewMessage: function(message) {\n dispatch(addMessage(message));\n }\n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
},
{
"id": "5a24c314108439a4d4036147",
"title": "Connect Redux to React",
"releasedOn": "December 25, 2017",
"description": [
"Now that you've written both the mapStateToProps()
and the mapDispatchToProps()
functions, you can use them to map state
and dispatch
to the props
of one of your React components. The connect
method from React Redux can handle this task. This method takes two optional arguments, mapStateToProps()
and mapDispatchToProps()
. They are optional because you may have a component that only needs access to state
but 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.",
"mapStateToProps()
and mapDispatchToProps()
functions and a new React component called Presentational
. Connect this component to Redux with the connect
method from the ReactRedux
global object, and call it immediately on the Presentational
component. Assign the result to a new const
called ConnectedComponent
that represents the connected component. That's it, now you're connected to Redux! Try changing either of connect
's arguments to null
and observe the test results."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"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 Presentational
component should render.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const props = mockedComponent.find('Presentational').props(); return props.messages === '__INITIAL__STATE__'; })(), 'message: The Presentational
component should receive a prop messages
via connect
.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const props = mockedComponent.find('Presentational').props(); return typeof props.submitNewMessage === 'function'; })(), 'message: The Presentational
component should receive a prop submitNewMessage
via connect
.');"
],
"solutions": [
"const addMessage = (message) => {\n return {\n type: 'ADD',\n message: message\n }\n};\n\nconst mapStateToProps = (state) => {\n return {\n messages: state\n }\n};\n\nconst mapDispatchToProps = (dispatch) => {\n return {\n submitNewMessage: (message) => {\n dispatch(addMessage(message));\n }\n }\n};\n\nclass Presentational extends React.Component {\n constructor(props) {\n super(props);\n }\n render() {\n return connect
to 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.",
"Presentational
. Create a new component held in a constant called Container
that uses connect
to connect the Presentational
component to Redux. Then, in the AppWrapper
, render the React Redux Provider
component. Pass Provider
the Redux store
as a prop and render Container
as a child. Once everything is setup, you will see the messages app rendered to the page again."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"// 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 (",
" AppWrapper
should render to the page.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })(), 'message: The Presentational
component should render an h2
, input
, button
, and ul
elements.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); return ( PresentationalComponent.find('div').length === 1 && PresentationalComponent.find('h2').length === 1 && PresentationalComponent.find('button').length === 1 && PresentationalComponent.find('ul').length === 1 ); })(), 'message: The Presentational
component should render an h2
, input
, button
, and ul
elements.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })(), 'message: The Presentational
component should receive messages
from the Redux store as a prop.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })(), 'message: The Presentational
component should receive the submitMessage
action creator as a prop.');"
],
"solutions": [
"// Redux:\nconst ADD = 'ADD';\n\nconst addMessage = (message) => {\n return {\n type: ADD,\n message: message\n }\n};\n\nconst messageReducer = (state = [], action) => {\n switch (action.type) {\n case ADD:\n return [\n ...state,\n action.message\n ];\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(messageReducer);\n\n// React:\nclass Presentational extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n messages: []\n }\n this.handleChange = this.handleChange.bind(this); \n this.submitMessage = this.submitMessage.bind(this); \n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n submitMessage() {\n const currentMessage = this.state.input;\n this.setState({\n input: '',\n messages: this.state.messages.concat(currentMessage)\n });\n }\n render() {\n return (\n Presentational
component and into Redux. Currently, you have Redux connected, but you are handling the state locally within the Presentational
component.",
"Presentational
component, first, remove the messages
property in the local state
. These messages will be managed by Redux. Next, modify the submitMessage()
method so that it dispatches submitNewMessage()
from this.props
, and pass in the current message input from local state
as an argument. Because you removed messages
from local state, remove the messages
property from the call to this.setState()
here as well. Finally, modify the render()
method so that it maps over the messages received from props
rather than state
.",
"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 own state
. 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."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"// 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;",
"",
"// Change code below this line",
"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() {",
" this.setState({",
" input: '',",
" messages: this.state.messages.concat(this.state.input)",
" });",
" }",
" render() {",
" return (",
" AppWrapper
should render to the page.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })(), 'message: The Presentational
component should render an h2
, input
, button
, and ul
elements.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); return ( PresentationalComponent.find('div').length === 1 && PresentationalComponent.find('h2').length === 1 && PresentationalComponent.find('button').length === 1 && PresentationalComponent.find('ul').length === 1 ); })(), 'message: The Presentational
component should render an h2
, input
, button
, and ul
elements.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })(), 'message: The Presentational
component should receive messages
from the Redux store as a prop.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })(), 'message: The Presentational
component should receive the submitMessage
action creator as a prop.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalState = mockedComponent.find('Presentational').instance().state; return typeof PresentationalState.input === 'string' && Object.keys(PresentationalState).length === 1; })(), 'message: The state of the Presentational
component should contain one property, input
, which is initialized to an empty string.');",
"async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const testValue = '__MOCK__INPUT__'; const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); let initialInput = mockedComponent.find('Presentational').find('input'); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); const updatedInput = updated.find('Presentational').find('input'); assert(initialInput.props().value === '' && updatedInput.props().value === '__MOCK__INPUT__', 'message: Typing in the input
element should update the state of the Presentational
component.'); }; ",
"async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); let beforeProps = mockedComponent.find('Presentational').props(); const testValue = '__TEST__EVENT__INPUT__'; const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const clickButton = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterChange = await changed(); const afterChangeInput = afterChange.find('input').props().value; const afterClick = await clickButton(); const afterProps = mockedComponent.find('Presentational').props(); assert(beforeProps.messages.length === 0 && afterChangeInput === testValue && afterProps.messages.pop() === testValue && afterClick.find('input').props().value === '', 'message: Dispatching the submitMessage
on the Presentational
component should update Redux store and clear the input in local state.'); }; ",
"async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); let beforeProps = mockedComponent.find('Presentational').props(); const testValue = '__TEST__EVENT__INPUT__'; const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const clickButton = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterChange = await changed(); const afterChangeInput = afterChange.find('input').props().value; const afterClick = await clickButton(); const afterProps = mockedComponent.find('Presentational').props(); assert(beforeProps.messages.length === 0 && afterChangeInput === testValue && afterProps.messages.pop() === testValue && afterClick.find('input').props().value === '' && afterClick.find('ul').childAt(0).text() === testValue, 'message: The Presentational
component should render the messages
from the Redux store.'); }; "
],
"solutions": [
"// Redux:\nconst ADD = 'ADD';\n\nconst addMessage = (message) => {\n return {\n type: ADD,\n message: message\n }\n};\n\nconst messageReducer = (state = [], action) => {\n switch (action.type) {\n case ADD:\n return [\n ...state,\n action.message\n ];\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(messageReducer);\n\n// React:\nconst Provider = ReactRedux.Provider;\nconst connect = ReactRedux.connect;\n\n// Change code below this line\nclass Presentational extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: ''\n }\n this.handleChange = this.handleChange.bind(this); \n this.submitMessage = this.submitMessage.bind(this); \n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n submitMessage() {\n this.props.submitNewMessage(this.state.input);\n this.setState({\n input: ''\n });\n }\n render() {\n return (\n import
statements (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.",
"'Now I know React and Redux!'
to the console."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"// 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(",
"// Now I know React and Redux!
should be logged to the console.');"
],
"solutions": [
"console.log('Now I know React and Redux!');"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
}
]
}