Feat: react redux migration (#16200)

* feat: crudely enables test to run solution code against React challenge (and passes!)

* feat: Updates comment

* feat: Adds React 2 and 3, validates challenges in app

* feat: Adds React 4, validates tests

* feat: Adds Peter's migrated challenge seed files for all challenges

* feat: Adds redux, react-redux imports, adds tests for React 7,

* feat: Adds tests for React 08

* fix(challenges): wrap reserved words in <code> and add tests

* feat: complete first two tests for React 9

* feat: modifies tests in React 09

* feat: Adds working tests for React 37, including async setState tests

* feat: Escape hatch to avoid async tests in automated test suite

* feat: Updates React 15 with working tests

* feat: build passes, yay

* feat: Provisions original code string in challenges and adds tests for React Redux 01

* fix(tests): add self-closing tags challenge, other small fixes

* fix(challenge): add react_10, some other stuff

* fix(challenges): update react 22, add react 23

* fix(challenges): react 5 and react 8

* feat: removes dependencies that will break in browser, will replace later

* feat: fix build

* feat: add redux 1

* fix(challenge): add react 24 tests

* feat: partial implemented Redux 2

* feat: migrate redux 3

* feat: Adds React-Redux 04 with working tests under npm test

* feat: Updates automated test runner, just provide all the dependencies. Adds Redux-Thunk.

* feat: Adds working tests for React Redux 07

* feat: redux challenge 4

* feat: migrate redux 5

* feat: redux 6

* feat: migrate Redux test 7

* fix(challenge): add react 25 tests

* feat: Adds tests for React 48, npm test does not pass...

* feat: Migrate Redux test 8

* fix(challenges): skip 26, add react 27 tests

* fix(challenges): add react 28 tests, replace function w/ => throughout, fix linter warnings

* feat: fixes (patches) hard to understand problem with automated test suite

* feat: updates async tests patch

* feat: adds converted tests for React 47

* feat: adds converted tests for React 46

* feat: Partially adds tests for React 43

* docs: adds TO-DO tests for React 43

* feat: migrates tests for React 42

* feat: migrates tests for React 41

* feat: migrates tests for React 39

* feat: Migrates tests for React 38, automated test script fails again!

* feat: migrates tests for React 32

* feat: QAs more React Redux challenge in FCC UI

* feat: Updates tests for React 7

* feat: Migrates React-Redux 3 tests and hardcodes deep-freeze dependency

* feat: migrates React Redux 05 tests

* feat: migrates React Redux 06 tests

* feat: Migrates React Redux 10

* feat: Migrates tests for React 16

* feat: Migrates React 17 tests

* feat: Migrates React 18 tests

* feat: Migrates React 19 tests

* feat: Migrates React 19 tests

* feat: fixing usage of code, replace with editor.getOriginalCode

* feat: Migrates React 21 tests

* feat: Finishes migration of React 09

* fix(challenges): add react 45 tests 💀

* feat: Adds React 11 tests

* feat: Migrates React 50 tests

* feat: Re-enables original code in FCC editor, QAs challenges blocked by original code

* feat: hacks head tail code in editor test environment

* feat: updates React 20 head code

* feat: QAs React Redux 07 in UI

* fix(challenges): add React 29 tests

* fix(challenges): add React 30 tests

* feat: updates async tests

* feat: Migrates React 12, gets ReactDOM challenges working and QAs them

* feat: Migrates React 13 tests

* feat: Migrates tests for React 14 and updates challenge description formatting

* feat: Refactors 2nd test for Redux 02

* feat: Migrates React 33

* feat: Removes React 26 and 43

* feat: Adds React 34 from Kevin

* fix(challenges): add React 31 & 35 tests (thanks Kevin)

* feat: Migrate Redux challenge 10 - pass both UI QA and terminal test

* fix(challenge): add react 40 tests

* feat: Migrates React Redux 02 tests

* feat: Migrates React Redux 08 and fixes async syntax in React challenge

* fix(challenge): add react 49 tests with caveat

* feat: fixes React 49 tests and adds first tests for React Redux 09

* feat: Migrate Redux 11 - pass both terminal test and UI test

* feat: Migrate Redux 12 - passing both UI test and terminal test

* feat: Migrate Redux 13 - passing both terminal and UI tests

* feat: Adding in code tags for previous redux challenges - terminal and UI tests pass

* feat: Migrates React Redux 09 and React 44 (thanks Kevin)

* feat: fix code tag issues - passed UI and terminal tests

* feat: Migrates Redux 14 tests

* feat: Migrates Redux 14

* feat: Migrates Redux 15

* feat: Migrates Redux 17

* feat: Final migration and QA of Redux, except for Redux 9

* feat: migrates React 36 and QAs

* feat: Rewrites Redux 09 and migrates

* feat: refactors pull request and cleans up code

* style(challenges): QA React challenges

* style(challenges): QA react challenges

* fix(challenges): fix react 41 and 45 tests

* style(challenges): QA redux challenges

* style(challenges): QA react and redux challenges

* fix(seed/react): Move head/tail to files

* fix(seed/redux): Move head/tail to file level

* chore(packages): Move jsdom to dev deps

* fix(seed/react/redux): Async funcs

make async func defined

* fix(seed): %s/editor.getUserCode/getUserInput/gc

* fix(Challenges/build): Make sure head/tail is bundled and transformed

* feat(Challenges.react): Add tail to render component

* chore(seed): Disable modern challenge testing for now

We will put these on beta while we update the auto testing framework
This commit is contained in:
Berkeley Martinez
2017-12-18 13:04:03 -08:00
committed by Quincy Larson
parent 7879f3183a
commit 3c56044e6c
5 changed files with 4660 additions and 53 deletions

View File

@ -3,25 +3,758 @@
"order": 7, "order": 7,
"time": "5 hours", "time": "5 hours",
"helpRoom": "Help", "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": "<body><div id='root'></div>${ source }</body>",
"challenges": [ "challenges": [
{ {
"id": "587d7dbc367417b2b2512bb0", "id": "5a24c314108439a4d4036141",
"title": "Introduction to the React and Redux Challenges", "title": "Getting Started with React Redux",
"releasedOn": "December 25, 2017",
"description": [ "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 the <code>react-redux</code> package. It provides a way for you to pass Redux <code>state</code> and <code>dispatch</code> to your React components as <code>props</code>.",
"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 <code>react-redux</code> to connect the Redux store with your component, thereby extracting the local state into the Redux store.",
"<hr>",
"Start with a <code>DisplayMessages</code> component. Add a constructor to this component and initialize it with a state that has two properties: <code>input</code>, that's set to an empty string, and <code>messages</code>, 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 <div />",
" }",
"};"
],
"tail": "ReactDOM.render(<DisplayMessages />, document.getElementById('root'))"
}
},
"tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })(), 'message: The <code>DisplayMessages</code> component should render an empty <code>div</code> element.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'message: The <code>DisplayMessages</code> constructor should be called properly with <code>super</code>, passing in <code>props</code>.');",
"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 <code>DisplayMessages</code> component should have an initial state equal to <code>{input: \"\", messages: []}</code>.');"
],
"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 <div/>\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 <code>DisplayMessages</code> component.",
"<hr>",
"First, in the <code>render()</code> method, have the component render an <code>input</code> element, <code>button</code> element, and <code>ul</code> element. When the <code>input</code> element changes, it should trigger a <code>handleChange()</code> method. Also, the <code>input</code> element should render the value of <code>input</code> that's in the component's state. The <code>button</code> element should trigger a <code>submitMessage()</code> method when it's clicked.",
"Second, write these two methods. The <code>handleChange()</code> method should update the <code>input</code> with what the user is typing. The <code>submitMessage()</code> method should concatenate the current message (stored in <code>input</code>) to the <code>messages</code> array in local state, and clear the value of the <code>input</code>.",
"Finally, use the <code>ul</code> to map over the array of <code>messages</code> and render it to the screen as a list of <code>li</code> 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",
"", "",
"The React and Redux challenges have not been ported into freeCodeCamp yet. You can visit <a href=\"http://hysterical-amusement.surge.sh/\" target=\"_blank\">this link to work through the alpha version</a> of these challenges. If you have feedback, you can open an issue (or pull request) directly on <a href=\"https://github.com/bonham000/fcc-react-tests-module\" target=\"_blank\">this repository.</a>", " render() {",
" return (",
" <div>",
" <h2>Type in a new Message:</h2>",
" { /* render an input, button, and ul here */ }",
"",
" { /* change code above this line */ }",
" </div>",
" );",
" }",
"};"
],
"tail": "ReactDOM.render(<DisplayMessages />, document.getElementById('root'))"
}
},
"tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return ( typeof initialState === 'object' && initialState.input === '' && initialState.messages.length === 0); })(), 'message: The <code>DisplayMessages</code> component should initialize with a state equal to <code>{ input: \"\", messages: [] }</code>.');",
"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 <code>DisplayMessages</code> component should render a <code>div</code> containing an <code>h2</code> element, a <code>button</code> element, a <code>ul</code> element, and <code>li</code> 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 <code>input</code> element should render the value of <code>input</code> 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 <code>handleChange</code> should update the <code>input</code> 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 <code>Add message</code> button should call the method <code>submitMessage</code> which should add the current <code>input</code> to the <code>messages</code> 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 <code>submitMessage</code> 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 <div>\n <h2>Type in a new Message:</h2>\n <input\n value={this.state.input}\n onChange={this.handleChange}/><br/>\n <button onClick={this.submitMessage}>Submit</button>\n <ul>\n {this.state.messages.map( (message, idx) => {\n return (\n <li key={idx}>{message}</li>\n )\n })\n }\n </ul>\n </div>\n );\n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
},
{
"id": "5a24c314108439a4d4036143",
"title": "Extract State Logic to Redux",
"releasedOn": "December 25, 2017",
"description": [
"Now that you finished the React component, you need to move the logic it's performing locally in its <code>state</code> 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.",
"<hr>",
"First, define an action type 'ADD' and set it to a const <code>ADD</code>. Next, define an action creator <code>addMessage()</code> which creates the action to add a message. You'll need to pass a <code>message</code> to this action creator and include the message in the returned <code>action</code>.",
"Then create a reducer called <code>messageReducer()</code> 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 <code>ADD</code> should exist and hold a value equal to the string <code>ADD</code>');",
"assert((function() { const addAction = addMessage('__TEST__MESSAGE__'); return addAction.type === ADD && addAction.message === '__TEST__MESSAGE__'; })(), 'message: The action creator <code>addMessage</code> should return an object with <code>type</code> equal to <code>ADD</code> and message equal to the message that is passed in.');",
"assert(typeof messageReducer === 'function', 'message: <code>messageReducer</code> 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 <code>addMessage</code> 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 <code>messageReducer</code> should return the current state if called with any other actions.');"
], ],
"releasedOn": "Feb 17, 2017", "solutions": [
"challengeSeed": [], "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);"
"tests": [], ],
"type": "waypoint", "type": "modern",
"challengeType": 7,
"isRequired": false, "isRequired": false,
"translations": {} "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 <code>react-redux</code> package to help accomplish these tasks.",
"React Redux provides a small API with two key features: <code>Provider</code> and <code>connect</code>. Another challenge covers <code>connect</code>. The <code>Provider</code> is a wrapper component from React Redux that wraps your React app. This wrapper then allows you to access the Redux <code>store</code> and <code>dispatch</code> functions throughout your component tree. <code>Provider</code> takes two props, the Redux store and the child components of your app. Defining the <code>Provider</code> for an App component might look like this:",
"<blockquote>&lt;Provider store={store}&gt;<br> &lt;App/&gt;<br>&lt;/Provider&gt;</blockquote>",
"<hr>",
"The code editor now shows all your Redux and React code from the past several challenges. It includes the Redux store, actions, and the <code>DisplayMessages</code> component. The only new piece is the <code>AppWrapper</code> component at the bottom. Use this top level component to render the <code>Provider</code> from <code>ReactRedux</code>, and pass the Redux store as a prop. Then render the <code>DisplayMessages</code> component as a child. Once you are finished, you should see your React component rendered to the page.",
"<strong>Note:</strong>&nbsp;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 <code>Provider</code> for you to use in the <code>AppWrapper</code> 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 (",
" <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",
"",
" // change code above this line",
"};"
],
"tail": "ReactDOM.render(<AppWrapper />, document.getElementById('root'))"
}
},
"tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'message: The <code>AppWrapper</code> should render.');",
"getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/ /g,'').includes('<Providerstore={store}>'); })(), 'message: The <code>Provider</code> wrapper component should have a prop of <code>store</code> 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: <code>DisplayMessages</code> should render as a child of <code>AppWrapper</code>.');",
"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 <code>DisplayMessages</code> component should render an h2, input, button, and <code>ul</code> 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 <div>\n <h2>Type in a new Message:</h2>\n <input\n value={this.state.input}\n onChange={this.handleChange}/><br/>\n <button onClick={this.submitMessage}>Submit</button>\n <ul>\n {this.state.messages.map( (message, idx) => {\n return (\n <li key={idx}>{message}</li>\n )\n })\n }\n </ul>\n </div>\n );\n }\n};\n\nconst Provider = ReactRedux.Provider;\n\nclass AppWrapper extends React.Component {\n // change code below this line\n render() {\n return (\n <Provider store = {store}>\n <DisplayMessages/>\n </Provider>\n );\n }\n // change code above this line\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
},
{
"id": "5a24c314108439a4d4036145",
"title": "Map State to Props",
"releasedOn": "December 25, 2017",
"description": [
"The <code>Provider</code> component allows you to provide <code>state</code> and <code>dispatch</code> 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: <code>mapStateToProps()</code> and <code>mapDispatchToProps()</code>.",
"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 <code>connect</code> method to connect them to your components in another challenge.",
"<strong>Note:</strong>&nbsp;Behind the scenes, React Redux uses the <code>store.subscribe()</code> method to implement <code>mapStateToProps()</code>.",
"<hr>",
"Create a function <code>mapStateToProps()</code>. This function should take <code>state</code> as an argument, then return an object which maps that state to specific property names. These properties will become accessible to your component via <code>props</code>. 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 <code>messages</code> in the object that's being returned, and set it to <code>state</code>."
],
"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 <code>state</code> should be an empty array.');",
"assert(typeof mapStateToProps === 'function', 'message: <code>mapStateToProps</code> should be a function.');",
"assert(typeof mapStateToProps() === 'object', 'message: <code>mapStateToProps</code> should return an object.');",
"assert(mapStateToProps(['messages']).messages.pop() === 'messages', 'message: Passing an array as state to <code>mapStateToProps</code> should return this array assigned to a key of <code>messages</code>.');"
],
"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 <code>mapDispatchToProps()</code> 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 <code>mapStateToProps()</code> function you wrote in the last challenge. It returns an object that maps dispatch actions to property names, which become component <code>props</code>. However, instead of returning a piece of <code>state</code>, each property returns a function that calls <code>dispatch</code> with an action creator and any relevant action data. You have access to this <code>dispatch</code> because it's passed in to <code>mapDispatchToProps()</code> as a parameter when you define the function, just like you passed <code>state</code> to <code>mapStateToProps()</code>. Behind the scenes, React Redux is using Redux's <code>store.dispatch()</code> to conduct these dispatches with <code>mapDispatchToProps()</code>. This is similar to how it uses <code>store.subscribe()</code> for components that are mapped to <code>state</code>.",
"For example, you have a <code>loginUser()</code> action creator that takes a <code>username</code> as an action payload. The object returned from <code>mapDispatchToProps()</code> for this action creator would look something like:",
"<blockquote>{<br> submitLoginUser: function(username) {<br> dispatch(loginUser(username));<br> }<br>}</blockquote>",
"<hr>",
"The code editor provides an action creator called <code>addMessage()</code>. Write the function <code>mapDispatchToProps()</code> that takes <code>dispatch</code> as an argument, then returns an object. The object should have a property <code>submitNewMessage</code> set to the dispatch function, which takes a parameter for the new message to add when it dispatches <code>addMessage()</code>."
],
"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: <code>addMessage</code> should return an object with keys <code>type</code> and <code>message</code>.');",
"assert(typeof mapDispatchToProps === 'function', 'message: <code>mapDispatchToProps</code> should be a function.');",
"assert(typeof mapDispatchToProps() === 'object', 'message: <code>mapDispatchToProps</code> 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 <code>addMessage</code> with <code>submitNewMessage</code> from <code>mapDispatchToProps</code> 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 <code>mapStateToProps()</code> and the <code>mapDispatchToProps()</code> functions, you can use them to map <code>state</code> and <code>dispatch</code> to the <code>props</code> of one of your React components. The <code>connect</code> method from React Redux can handle this task. This method takes two optional arguments, <code>mapStateToProps()</code> and <code>mapDispatchToProps()</code>. They are optional because you may have a component that only needs access to <code>state</code> 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:",
"<code>connect(mapStateToProps, mapDispatchToProps)(MyComponent)</code>",
"<strong>Note:</strong>&nbsp;If you want to omit one of the arguments to the <code>connect</code> method, you pass <code>null</code> in its place.",
"<hr>",
"The code editor has the <code>mapStateToProps()</code> and <code>mapDispatchToProps()</code> functions and a new React component called <code>Presentational</code>. Connect this component to Redux with the <code>connect</code> method from the <code>ReactRedux</code> global object, and call it immediately on the <code>Presentational</code> component. Assign the result to a new <code>const</code> called <code>ConnectedComponent</code> that represents the connected component. That's it, now you're connected to Redux! Try changing either of <code>connect</code>'s arguments to <code>null</code> 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 <h3>This is a Presentational Component</h3>",
" }",
"};",
"",
"const connect = ReactRedux.connect;",
"// change code below this line",
""
],
"tail": [
"",
"const store = Redux.createStore(",
" (state = '__INITIAL__STATE__', action) => state",
");",
"class AppWrapper extends React.Component {",
" render() {",
" return (",
" <ReactRedux.Provider store = {store}>",
" <ConnectedComponent/>",
" </ReactRedux.Provider>",
" );",
" }",
"};",
"ReactDOM.render(<AppWrapper />, document.getElementById('root'))"
]
}
},
"tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })(), 'message: The <code>Presentational</code> 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 <code>Presentational</code> component should receive a prop <code>messages</code> via <code>connect</code>.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const props = mockedComponent.find('Presentational').props(); return typeof props.submitNewMessage === 'function'; })(), 'message: The <code>Presentational</code> component should receive a prop <code>submitNewMessage</code> via <code>connect</code>.');"
],
"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 <h3>This is a Presentational Component</h3>\n }\n};\n\nconst connect = ReactRedux.connect;\n// change code below this line\n\nconst ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational); \n"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
},
{
"id": "5a24c314108439a4d4036148",
"title": "Connect Redux to the Messages App",
"releasedOn": "December 25, 2017",
"description": [
"Now that you understand how to use <code>connect</code> 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 <code>Presentational</code>, and this wasn't arbitrary. This term <i>generally</i> 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.",
"<hr>",
"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 <code>Presentational</code>. Create a new component held in a constant called <code>Container</code> that uses <code>connect</code> to connect the <code>Presentational</code> component to Redux. Then, in the <code>AppWrapper</code>, render the React Redux <code>Provider</code> component. Pass <code>Provider</code> the Redux <code>store</code> as a prop and render <code>Container</code> 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 (",
" <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:",
"",
"",
"class AppWrapper extends React.Component {",
" constructor(props) {",
" super(props);",
" }",
" render() {",
" // complete the return statement:",
" return (",
"",
" );",
" }",
"};"
],
"tail": "ReactDOM.render(<AppWrapper />, document.getElementById('root'))"
}
},
"tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'message: The <code>AppWrapper</code> should render to the page.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })(), 'message: The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> 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 <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> 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 <code>Presentational</code> component should receive <code>messages</code> 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 <code>Presentational</code> component should receive the <code>submitMessage</code> 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 <div>\n <h2>Type in a new Message:</h2>\n <input\n value={this.state.input}\n onChange={this.handleChange}/><br/>\n <button onClick={this.submitMessage}>Submit</button>\n <ul>\n {this.state.messages.map( (message, idx) => {\n return (\n <li key={idx}>{message}</li>\n )\n })\n }\n </ul>\n </div>\n );\n }\n};\n\n// React-Redux:\nconst mapStateToProps = (state) => {\n return { messages: state }\n};\n\nconst mapDispatchToProps = (dispatch) => {\n return {\n submitNewMessage: (newMessage) => {\n dispatch(addMessage(newMessage))\n }\n }\n};\n\nconst Provider = ReactRedux.Provider;\nconst connect = ReactRedux.connect;\n\n// define the Container component here:\nconst Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);\n\nclass AppWrapper extends React.Component {\n constructor(props) {\n super(props);\n }\n render() {\n // complete the return statement:\n return (\n <Provider store={store}>\n <Container/>\n </Provider>\n );\n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
},
{
"id": "5a24c314108439a4d4036149",
"title": "Extract Local State into Redux",
"releasedOn": "December 25, 2017",
"description": [
"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 <code>Presentational</code> component and into Redux. Currently, you have Redux connected, but you are handling the state locally within the <code>Presentational</code> component.",
"<hr>",
"In the <code>Presentational</code> component, first, remove the <code>messages</code> property in the local <code>state</code>. These messages will be managed by Redux. Next, modify the <code>submitMessage()</code> method so that it dispatches <code>submitNewMessage()</code> from <code>this.props</code>, and pass in the current message input from local <code>state</code> as an argument. Because you removed <code>messages</code> from local state, remove the <code>messages</code> property from the call to <code>this.setState()</code> here as well. Finally, modify the <code>render()</code> method so that it maps over the messages received from <code>props</code> rather than <code>state</code>.",
"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 <code>state</code>: your component still tracks user input locally in its own <code>state</code>. 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 (",
" <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>",
" );",
" }",
"};",
"// Change code above this line",
"",
"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>",
" );",
" }",
"};"
],
"tail": "ReactDOM.render(<AppWrapper />, document.getElementById('root'))"
}
},
"tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'message: The <code>AppWrapper</code> should render to the page.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })(), 'message: The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> 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 <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> 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 <code>Presentational</code> component should receive <code>messages</code> 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 <code>Presentational</code> component should receive the <code>submitMessage</code> 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 <code>Presentational</code> component should contain one property, <code>input</code>, 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 <code>input</code> element should update the state of the <code>Presentational</code> 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 <code>submitMessage</code> on the <code>Presentational</code> 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 <code>Presentational</code> component should render the <code>messages</code> 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 <div>\n <h2>Type in a new Message:</h2>\n <input\n value={this.state.input}\n onChange={this.handleChange}/><br/>\n <button onClick={this.submitMessage}>Submit</button>\n <ul>\n {this.props.messages.map( (message, idx) => {\n return (\n <li key={idx}>{message}</li>\n )\n })\n }\n </ul>\n </div>\n );\n }\n};\n// Change code above this line\n\nconst mapStateToProps = (state) => {\n return {messages: state}\n};\n\nconst mapDispatchToProps = (dispatch) => {\n return {\n submitNewMessage: (message) => {\n dispatch(addMessage(message))\n }\n }\n};\n\nconst Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);\n\nclass AppWrapper extends React.Component {\n render() {\n return (\n <Provider store={store}>\n <Container/>\n </Provider>\n );\n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
},
{
"id": "5a24c314108439a4d403614a",
"title": "Moving Forward From Here",
"releasedOn": "December 25, 2017",
"description": [
"Congratulations! You finished the lessons on React and Redux. 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 <code>import</code> 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",
"<a id='CRA' target ='_blank' href='https://github.com/facebookincubator/create-react-app'>Create React App</a> 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.",
"<hr>",
"Log the message <code>'Now I know React and Redux!'</code> 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(",
"// <Provider store={store}>",
"// <App/>",
"// </Provider>,",
"// document.getElementById('root')",
"// );",
"",
"// change code below this line",
""
]
}
},
"tests": [
"assert(editor.getValue().includes('console.log(\"Now I know React and Redux!\")') || editor.getValue().includes('console.log(\\'Now I know React and Redux!\\')'), 'message: The message <code>Now I know React and Redux!</code> should be logged to the console.');"
],
"solutions": [
"console.log('Now I know React and Redux!');"
],
"type": "modern",
"isRequired": false,
"translations": {},
"reactRedux": true
} }
] ]
} }

File diff suppressed because it is too large Load Diff

View File

@ -3,31 +3,928 @@
"order": 6, "order": 6,
"time": "5 hours", "time": "5 hours",
"helpRoom": "Help", "helpRoom": "Help",
"required": [
{
"src": "https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js"
},
{
"src": "https://cdnjs.cloudflare.com/ajax/libs/redux-thunk/2.2.0/redux-thunk.js"
}
],
"challenges": [ "challenges": [
{ {
"id": "587d7dbd367417b2b2512bb2", "id": "5a24c314108439a4d403614b",
"title": "Introduction to the Redux Challenges", "title": "Create a Redux Store",
"releasedOn": "December 25, 2017",
"description": [ "description": [
[ "Redux is a state management framework that can be used with a number of different web technologies, including React.",
"", "In Redux, there is a single state object that's responsible for the entire state of your application. This means if you had a React app with ten components, and each component had its own local state, the entire state of your app would be defined by a single state object housed in the Redux <code>store</code>. This is the first important principle to understand when learning Redux: the Redux store is the single source of truth when it comes to application state.",
"", "This also means that any time any piece of your app wants to update state, it <strong>must</strong> do so through the Redux store. The unidirectional data flow makes it easier to track state management in your app.",
"Redux is self-described as a \"predictable state container for JavaScript apps.\" State (as introduced in the React challenges) is a way to manage what is displayed to a user based on that user's inputs or interactions with the application. While it's not necessary to use Redux with React, or vice-versa, the two are often used in conjunction as the features of Redux support applications built with React. Some of these features include:<br><br><ol><li>An immutable single state store or \"single state of truth\" through which the application makes UI updates</li><li>Logical structure of `actions` carrying data to `reducer` functions that return a new application state</li><li>A trackable history of changes to state</li></ol><br><br>Redux is often added to a project when the complexity of managing the application's state becomes tedious. It helps minimize some of the headaches that can be caused by data mutability or passing state back-and-forth between many React components. Redux also supports debugging with tools like <a href=\"https://github.com/gaearon/redux-devtools\" target=\"_blank\">Redux DevTools</a>. Let's jump in to learning how Redux can simplify your React apps.", "<hr>",
"" "The Redux <code>store</code> is an object which holds and manages application <code>state</code>. There is a method called <code>createStore()</code> on the Redux object, which you use to create the Redux <code>store</code>. This method takes a <code>reducer</code> function as a required argument. The <code>reducer</code> function is covered in a later challenge, and is already defined for you in the code editor. It simply takes <code>state</code> as an argument and returns <code>state</code>.",
"Declare a <code>store</code> variable and assign it to the <code>createStore()</code> method, passing in the <code>reducer</code> as an argument.",
"<strong>Note:</strong>&nbsp;The code in the editor uses ES6 default argument syntax to initialize this state to hold a value of <code>5</code>. If you're not familiar with default arguments, you can refer to the <a target=\"_blank\" href=\"http://beta.freecodecamp.com/en/challenges/es6/set-default-parameters-for-your-functions\">ES6 section in the Beta Curriculum</a> which covers this topic."
], ],
[ "files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const reducer = (state = 5) => {",
" return state;",
"}",
"", "",
"// Redux methods are available from a Redux object",
"// For example: Redux.createStore()",
"// Define the store here:",
"", "",
"The Redux challenges have not been ported into freeCodeCamp yet. You can visit <a href=\"http://hysterical-amusement.surge.sh/\" target=\"_blank\">this link to work through the alpha version</a> of these challenges. If you have feedback, you can open an issue (or pull request) directly on <a href=\"https://github.com/bonham000/fcc-react-tests-module\" target=\"_blank\">this repository.</a>",
"" ""
] ]
}
},
"tests": [
"assert(typeof store.getState === 'function', 'message: The redux store exists.');",
"assert(store.getState()=== 5, 'message: The redux store has a value of 5 for the state.');"
], ],
"releasedOn": "Feb 17, 2017", "solutions": [
"challengeSeed": [], "const reducer = (state = 5) => {\n return state;\n}\n\n// Redux methods are available from a Redux object\n// For example: Redux.createStore()\n// Define the store here:\n\nconst store = Redux.createStore(reducer);"
"tests": [], ],
"type": "waypoint", "type": "modern",
"challengeType": 7,
"isRequired": false, "isRequired": false,
"translations": {} "translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d403614c",
"title": "Get State from the Redux Store",
"releasedOn": "December 25, 2017",
"description": [
"The Redux store object provides several methods that allow you to interact with it. For example, you can retrieve the current <code>state</code> held in the Redux store object with the <code>getState()</code> method.",
"<hr>",
"The code from the previous challenge is re-written more concisely in the code editor. Use <code>store.getState()</code> to retrieve the <code>state</code> from the <code>store</code>, and assign this to a new variable <code>currentState</code>."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const store = Redux.createStore(",
" (state = 5) => state",
");",
"",
"// change code below this line",
""
]
}
},
"tests": [
"assert(store.getState()===5, 'message: The redux store should have a value of 5 for the initial state.');",
"getUserInput => assert(currentState === 5 && getUserInput('index').includes('store.getState()'), 'message: A variable <code>currentState</code> should exist and should be assigned the current state of the Redux store.');"
],
"solutions": [
"const store = Redux.createStore(\n (state = 5) => state\n);\n\n// change code below this line\nconst currentState = store.getState();"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d403614d",
"title": "Define a Redux Action",
"releasedOn": "December 25, 2017",
"description": [
"Since Redux is a state management framework, updating state is one of its core tasks. In Redux, all state updates are triggered by dispatching actions. An action is simply a JavaScript object that contains information about an action event that has occurred. The Redux store receives these action objects, then updates its state accordingly. Sometimes a Redux action also carries some data. For example, the action carries a username after a user logs in. While the data is optional, actions must carry a <code>type</code> property that specifies the 'type' of action that occurred.",
"Think of Redux actions as messengers that deliver information about events happening in your app to the Redux store. The store then conducts the business of updating state based on the action that occurred.",
"<hr>",
"Writing a Redux action is as simple as declaring an object with a type property. Declare an object <code>action</code> and give it a property <code>type</code> set to the string <code>'LOGIN'</code>."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"// Define an action here:",
""
]
}
},
"tests": [
"assert((function() { return typeof action === 'object' })(), 'message: An action object should exist.');",
"assert((function() { return action.type === 'LOGIN' })(), 'message: The action should have a key property type with value <code>LOGIN</code>.');"
],
"solutions": [
"const action = {\n type: 'LOGIN'\n}"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d403614e",
"title": "Define an Action Creator",
"releasedOn": "December 25, 2017",
"description": [
"After creating an action, the next step is sending the action to the Redux store so it can update its state. In Redux, you define action creators to accomplish this. An action creator is simply a JavaScript function that returns an action. In other words, action creators create objects that represent action events.",
"<hr>",
"Define a function named <code>actionCreator()</code> that returns the <code>action</code> object when called."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const action = {",
" type: 'LOGIN'",
"}",
"// Define an action creator here:",
""
]
}
},
"tests": [
"assert(typeof actionCreator === 'function', 'message: The function <code>actionCreator</code> should exist.');",
"assert(typeof action === 'object', 'message: Running the <code>actionCreator</code> function should return the action object.');",
"assert(action.type === 'LOGIN', 'message: The returned action should have a key property type with value <code>LOGIN</code>.');"
],
"solutions": [
"const action = {\n type: 'LOGIN'\n}\n// Define an action creator here:\nconst actionCreator = () => {\n return action;\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d403614f",
"title": "Dispatch an Action Event",
"releasedOn": "December 25, 2017",
"description": [
"od is what you use to dispatch actions to the Redux store. Calling <code>store.dispatch()</code> and passing the value returned from an action creator sends an action back to the store.",
"Recall that action creators return an object with a type property that specifies the action that has occurred. Then the method dispatches an action object to the Redux store. Based on the previous challenge's example, the following lines are equivalent, and both dispatch the action of type <code>LOGIN</code>:",
"<blockquote>store.dispatch(actionCreator());<br>store.dispatch({ type: 'LOGIN' });</blockquote>",
"<hr>",
"The Redux store in the code editor has an initialized state that's an object containing a <code>login</code> property currently set to <code>false</code>. There's also an action creator called <code>loginAction()</code> which returns an action of type <code>LOGIN</code>. Dispatch the <code>LOGIN</code> action to the Redux store by calling the <code>dispatch</code> method, and pass in the action created by <code>loginAction()</code>."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const store = Redux.createStore(",
" (state = {login: false}) => state",
");",
"",
"const loginAction = () => {",
" return {",
" type: 'LOGIN'",
" }",
"};",
"",
"// Dispatch the action here:",
""
]
}
},
"tests": [
"assert(loginAction().type === 'LOGIN', 'message: Calling the function <code>loginAction</code> should return an object with <code>type</code> property set to the string <code>LOGIN</code>.');",
"assert(store.getState().login === false, 'message: The store should be initialized with an object with property <code>login</code> set to <code>false</code>.');",
"getUserInput => assert((function() { let noWhiteSpace = getUserInput('index').replace(/ /g,''); return noWhiteSpace.includes('store.dispatch(loginAction())') || noWhiteSpace.includes('store.dispatch({type: \\'LOGIN\\'})') === true })(), 'message: The <code>store.dispatch()</code> method should be used to dispatch an action of type <code>LOGIN</code>.');"
],
"solutions": [
"const store = Redux.createStore(\n (state = {login: false}) => state\n);\n\nconst loginAction = () => {\n return {\n type: 'LOGIN'\n }\n};\n\n// Dispatch the action here:\nstore.dispatch(loginAction());"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036150",
"title": "Handle an Action in the Store",
"releasedOn": "December 25, 2017",
"description": [
"After an action is created and dispatched, the Redux store needs to know how to respond to that action. This is the job of a <code>reducer</code> function. Reducers in Redux are responsible for the state modifications that take place in response to actions. A <code>reducer</code> takes <code>state</code> and <code>action</code> as arguments, and it always returns a new <code>state</code>. It is important to see that this is the <strong>only</strong> role of the reducer. It has no side effects &mdash; it never calls an API endpoint and it never has any hidden surprises. The reducer is simply a pure function that takes state and action, then returns new state.",
"Another key principle in Redux is that <code>state</code> is read-only. In other words, the <code>reducer</code> function must <strong>always</strong> return a new copy of <code>state</code> and never modify state directly. Redux does not enforce state immutability, however, you are responsible for enforcing it in the code of your reducer functions. You'll practice this in later challenges.",
"<hr>",
"The code editor has the previous example as well as the start of a <code>reducer</code> function for you. Fill in the body of the <code>reducer</code> function so that if it receives an action of type <code>'LOGIN'</code> it returns a state object with <code>login</code> set to <code>true</code>. Otherwise, it returns the current <code>state</code>. Note that the current <code>state</code> and the dispatched <code>action</code> are passed to the reducer, so you can access the action's type directly with <code>action.type</code>."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const defaultState = {",
" login: false",
"};",
"",
"const reducer = (state = defaultState, action) => {",
" // change code below this line",
"",
" // change code above this line",
"};",
"",
"const store = Redux.createStore(reducer);",
"",
"const loginAction = () => {",
" return {",
" type: 'LOGIN'",
" }",
"};"
]
}
},
"tests": [
"assert(loginAction().type === 'LOGIN', 'message: Calling the function <code>loginAction</code> should return an object with type property set to the string <code>LOGIN</code>.');",
"assert(store.getState().login === false, 'message: The store should be initialized with an object with property <code>login</code> set to <code>false</code>.');",
"assert((function() { const initialState = store.getState(); store.dispatch(loginAction()); const afterState = store.getState(); return initialState.login === false && afterState.login === true })(), 'message: Dispatching <code>loginAction</code> should update the <code>login</code> property in the store state to <code>true</code>.');",
"assert((function() { store.dispatch({type: '__TEST__ACTION__'}); let afterTest = store.getState(); return typeof afterTest === 'object' && afterTest.hasOwnProperty('login') })(), 'message: If the action is not of type <code>LOGIN</code>, the store should return the current state.');"
],
"solutions": [
"const defaultState = {\n login: false\n};\n\nconst reducer = (state = defaultState, action) => {\n\n if (action.type === 'LOGIN') {\n return {login: true}\n }\n\n else {\n return state\n }\n\n};\n\nconst store = Redux.createStore(reducer);\n\nconst loginAction = () => {\n return {\n type: 'LOGIN'\n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036151",
"title": "Use a Switch Statement to Handle Multiple Actions",
"releasedOn": "December 25, 2017",
"description": [
"You can tell the Redux store how to handle multiple action types. Say you are managing user authentication in your Redux store. You want to have a state representation for when users are logged in and when they are logged out. You represent this with a single state object with the property <code>authenticated</code>. You also need action creators that create actions corresponding to user login and user logout, along with the action objects themselves.",
"<hr>",
"The code editor has a store, actions, and action creators set up for you. Fill in the <code>reducer</code> function to handle multiple authentication actions. Use a JavaScript <code>switch</code> statement in the <code>reducer</code> to respond to different action events. This is a standard pattern in writing Redux reducers. The switch statement should switch over <code>action.type</code> and return the appropriate authentication state.",
"<strong>Note:</strong>&nbsp;At this point, don't worry about state immutability, since it is small and simple in this example. For each action, you can return a new object &mdash; for example, <code>{authenticated: true}</code>. Also, don't forget to write a <code>default</code> case in your switch statement that returns the current <code>state</code>. This is important because once your app has multiple reducers, they are all run any time an action dispatch is made, even when the action isn't related to that reducer. In such a case, you want to make sure that you return the current <code>state</code>."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const defaultState = {",
" authenticated: false",
"};",
"",
"const authReducer = (state = defaultState, action) => {",
" // change code below this line",
"",
" // change code above this line",
"};",
"",
"const store = Redux.createStore(authReducer);",
"",
"const loginUser = () => {",
" return {",
" type: 'LOGIN'",
" }",
"};",
"",
"const logoutUser = () => {",
" return {",
" type: 'LOGOUT'",
" }",
"};"
]
}
},
"tests": [
"assert(loginUser().type === 'LOGIN', 'message: Calling the function <code>loginUser</code> should return an object with type property set to the string <code>LOGIN</code>.');",
"assert(logoutUser().type === 'LOGOUT', 'message: Calling the function <code>logoutUser</code> should return an object with type property set to the string <code>LOGOUT</code>.');",
"assert(store.getState().authenticated === false, 'message: The store should be initialized with an object with an <code>authenticated</code> property set to <code>false</code>.');",
"assert((function() { const initialState = store.getState(); store.dispatch(loginUser()); const afterLogin = store.getState(); return initialState.authenticated === false && afterLogin.authenticated === true })(), 'message: Dispatching <code>loginUser</code> should update the <code>authenticated</code> property in the store state to <code>true</code>.');",
"assert((function() { store.dispatch(loginUser()); const loggedIn = store.getState(); store.dispatch(logoutUser()); const afterLogout = store.getState(); return loggedIn.authenticated === true && afterLogout.authenticated === false })(), 'message: Dispatching <code>logoutUser</code> should update the <code>authenticated</code> property in the store state to <code>false</code>.');",
"getUserInput => assert( getUserInput('index').toString().includes('switch') && getUserInput('index').toString().includes('case') && getUserInput('index').toString().includes('default'), 'message: The <code>authReducer</code> function should handle multiple action types with a <code>switch</code> statement.');"
],
"solutions": [
"const defaultState = {\n authenticated: false\n};\n\nconst authReducer = (state = defaultState, action) => {\n\n switch (action.type) {\n\n case 'LOGIN':\n return {\n authenticated: true\n }\n\n case 'LOGOUT':\n return {\n authenticated: false\n }\n\n default:\n return state;\n\n }\n\n};\n\nconst store = Redux.createStore(authReducer);\n\nconst loginUser = () => {\n return {\n type: 'LOGIN'\n }\n};\n\nconst logoutUser = () => {\n return {\n type: 'LOGOUT'\n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036152",
"title": "Use const for Action Types",
"releasedOn": "December 25, 2017",
"description": [
"A common practice when working with Redux is to assign action types as read-only constants, then reference these constants wherever they are used. You can refactor the code you're working with to write the action types as <code>const</code> declarations.",
"<hr>",
"Declare <code>LOGIN</code> and <code>LOGOUT</code> as <code>const</code> values and assign them to the strings <code>'LOGIN'</code> and <code>'LOGOUT'</code>, respectively. Then, edit the <code>authReducer()</code> and the action creators to reference these constants instead of string values.",
"<strong>Note:</strong>&nbsp;It's generally a convention to write constants in all uppercase, and this is standard practice in Redux as well."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"// change code below this line",
"",
"// change code above this line",
"",
"const defaultState = {",
" authenticated: false",
"};",
"",
"const authReducer = (state = defaultState, action) => {",
"",
" switch (action.type) {",
"",
" case 'LOGIN':",
" return {",
" authenticated: true",
" }",
"",
" case 'LOGOUT':",
" return {",
" authenticated: false",
" }",
"",
" default:",
" return state;",
"",
" }",
"",
"};",
"",
"const store = Redux.createStore(authReducer);",
"",
"const loginUser = () => {",
" return {",
" type: 'LOGIN'",
" }",
"};",
"",
"const logoutUser = () => {",
" return {",
" type: 'LOGOUT'",
" }",
"};"
]
}
},
"tests": [
"assert(loginUser().type === 'LOGIN', 'message: Calling the function <code>loginUser</code> should return an object with <code>type</code> property set to the string <code>LOGIN</code>.');",
"assert(logoutUser().type === 'LOGOUT', 'message: Calling the function <code>logoutUser</code> should return an object with <code>type</code> property set to the string <code>LOGOUT</code>.');",
"assert(store.getState().authenticated === false, 'message: The store should be initialized with an object with property <code>login</code> set to <code>false</code>.');",
"assert((function() { const initialState = store.getState(); store.dispatch(loginUser()); const afterLogin = store.getState(); return initialState.authenticated === false && afterLogin.authenticated === true })(), 'message: Dispatching <code>loginUser</code> should update the <code>login</code> property in the store state to <code>true</code>.');",
"assert((function() { store.dispatch(loginUser()); const loggedIn = store.getState(); store.dispatch(logoutUser()); const afterLogout = store.getState(); return loggedIn.authenticated === true && afterLogout.authenticated === false })(), 'message: Dispatching <code>logoutUser</code> should update the <code>login</code> property in the store state to <code>false</code>.');",
"getUserInput => assert((function() { return typeof authReducer === 'function' && getUserInput('index').toString().includes('switch') && getUserInput('index').toString().includes('case') && getUserInput('index').toString().includes('default') })(), 'message: The <code>authReducer</code> function should handle multiple action types with a switch statement.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/ /g,''); return (noWhiteSpace.includes('constLOGIN=\\'LOGIN\\'') || noWhiteSpace.includes('constLOGIN=\"LOGIN\"')) && (noWhiteSpace.includes('constLOGOUT=\\'LOGOUT\\'') || noWhiteSpace.includes('constLOGOUT=\"LOGOUT\"')) })(), 'message: <code>LOGIN</code> and <code>LOGOUT</code> should be declared as <code>const</code> values and should be assigned strings of <code>LOGIN</code>and <code>LOGOUT</code>.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/ /g,''); return noWhiteSpace.includes('caseLOGIN:') && noWhiteSpace.includes('caseLOGOUT:') && noWhiteSpace.includes('type:LOGIN') && noWhiteSpace.includes('type:LOGOUT') })(), 'message: The action creators and the reducer should reference the <code>LOGIN</code> and <code>LOGOUT</code> constants.');"
],
"solutions": [
"const LOGIN = 'LOGIN';\nconst LOGOUT = 'LOGOUT';\n\nconst defaultState = {\n authenticated: false\n};\n\nconst authReducer = (state = defaultState, action) => {\n\n switch (action.type) {\n\n case LOGIN:\n return {\n authenticated: true\n }\n\n case LOGOUT:\n return {\n authenticated: false\n }\n\n default:\n return state;\n\n }\n\n};\n\nconst store = Redux.createStore(authReducer);\n\nconst loginUser = () => {\n return {\n type: LOGIN\n }\n};\n\nconst logoutUser = () => {\n return {\n type: LOGOUT\n }\n};"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036153",
"title": "Register a Store Listener",
"releasedOn": "December 25, 2017",
"description": [
"Another method you have access to on the Redux <code>store</code> object is <code>store.subscribe()</code>. This allows you to subscribe listener functions to the store, which are called whenever an action is dispatched against the store. One simple use for this method is to subscribe a function to your store that simply logs a message every time an action is received and the store is updated.",
"<hr>",
"Write a callback function that increments the global variable <code>count</code> every time the store receives an action, and pass this function in to the <code>store.subscribe()</code> method. You'll see that <code>store.dispatch()</code> is called three times in a row, each time directly passing in an action object. Watch the console output between the action dispatches to see the updates take place."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"head": [
"count = 0;"
],
"contents": [
"const ADD = 'ADD';",
"",
"const reducer = (state = 0, action) => {",
" switch(action.type) {",
" case ADD:",
" return state + 1;",
" default:",
" return state;",
" }",
"};",
"",
"const store = Redux.createStore(reducer);",
"",
"// global count variable:",
"let count = 0;",
"",
"// change code below this line",
"",
"// change code above this line",
"",
"store.dispatch({type: ADD});",
"console.log(count);",
"store.dispatch({type: ADD});",
"console.log(count);",
"store.dispatch({type: ADD});",
"console.log(count);"
]
}
},
"tests": [
"assert((function() { const initialState = store.getState(); store.dispatch({ type: 'ADD' }); const newState = store.getState(); return newState === (initialState + 1); })(), 'message: Dispatching the <code>ADD</code> action on the store should increment the state by <code>1</code>.');",
"getUserInput => assert(getUserInput('index').includes('store.subscribe('), 'message: There should be a listener function subscribed to the store using <code>store.subscribe</code>.');",
"assert(store.getState() === count, 'message: The callback to <code>store.subscribe</code> should also increment the global <code>count</code> variable as the store is updated.');"
],
"solutions": [
"const ADD = 'ADD';\n\nconst reducer = (state = 0, action) => {\n switch(action.type) {\n case ADD:\n return state + 1;\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(reducer);\n let count = 0; \n// change code below this line\n\nstore.subscribe( () =>\n { \n count++; \n } \n);\n\n// change code above this line\n\nstore.dispatch({type: ADD});\nstore.dispatch({type: ADD});\nstore.dispatch({type: ADD});"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036154",
"title": "Combine Multiple Reducers",
"releasedOn": "December 25, 2017",
"description": [
"When the state of your app begins to grow more complex, it may be tempting to divide state into multiple pieces. Instead, remember the first principle of Redux: all app state is held in a single state object in the store. Therefore, Redux provides reducer composition as a solution for a complex state model. You define multiple reducers to handle different pieces of your application's state, then compose these reducers together into one root reducer. The root reducer is then passed into the Redux <code>createStore()</code> method.",
"In order to let us combine multiple reducers together, Redux provides the <code>combineReducers()</code> method. This method accepts an object as an argument in which you define properties which associate keys to specific reducer functions. The name you give to the keys will be used by Redux as the name for the associated piece of state.",
"Typically, it is a good practice to create a reducer for each piece of application state when they are distinct or unique in some way. For example, in a note-taking app with user authentication, one reducer could handle authentication while another handles the text and notes that the user is submitting. For such an application, we might write the <code>combineReducers()</code> method like this:",
"<blockquote>const rootReducer = Redux.combineReducers({<br> auth: authenticationReducer,<br> notes: notesReducer<br>});</blockquote>",
"Now, the key <code>notes</code> will contain all of the state associated with our notes and handled by our <code>notesReducer</code>. This is how multiple reducers can be composed to manage more complex application state. In this example, the state held in the Redux store would then be a single object containing <code>auth</code> and <code>notes</code> properties.",
"<hr>",
"There are <code>counterReducer()</code> and <code>authReducer()</code> functions provided in the code editor, along with a Redux store. Finish writing the <code>rootReducer()</code> function using the <code>Redux.combineReducers()</code> method. Assign <code>counterReducer</code> to a key called <code>count</code> and <code>authReducer</code> to a key called <code>auth</code>."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const INCREMENT = 'INCREMENT';",
"const DECREMENT = 'DECREMENT';",
"",
"const counterReducer = (state = 0, action) => {",
" switch(action.type) {",
" case INCREMENT:",
" return state + 1;",
" case DECREMENT:",
" return state - 1;",
" default:",
" return state;",
" }",
"};",
"",
"const LOGIN = 'LOGIN';",
"const LOGOUT = 'LOGOUT';",
"",
"const authReducer = (state = {authenticated: false}, action) => {",
" switch(action.type) {",
" case LOGIN:",
" return {",
" authenticated: true",
" }",
" case LOGOUT:",
" return {",
" authenticated: false",
" }",
" default:",
" return state;",
" }",
"};",
"",
"const rootReducer = // define the root reducer here",
"",
"const store = Redux.createStore(rootReducer);",
""
]
}
},
"tests": [
"assert((function() { const initalState = store.getState().count; store.dispatch({type: INCREMENT}); store.dispatch({type: INCREMENT}); const firstState = store.getState().count; store.dispatch({type: DECREMENT}); const secondState = store.getState().count; return firstState === initalState + 2 && secondState === firstState - 1 })(), 'message: The <code>counterReducer</code> should increment and decrement the <code>state</code>.');",
"assert((function() { store.dispatch({type: LOGIN}); const loggedIn = store.getState().auth.authenticated; store.dispatch({type: LOGOUT}); const loggedOut = store.getState().auth.authenticated; return loggedIn === true && loggedOut === false })(), 'message: The <code>authReducer</code> should toggle the <code>state</code> of <code>authenticated</code> between <code>true</code> and <code>false</code>.');",
"assert((function() { const state = store.getState(); return typeof state.auth === 'object' && typeof state.auth.authenticated === 'boolean' && typeof state.count === 'number' })(), 'message: The store <code>state</code> should have two keys: <code>count</code>, which holds a number, and <code>auth</code>, which holds an object. The <code>auth</code> object should have a property of <code>authenticated</code>, which holds a boolean.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g,''); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })(), 'message: The <code>rootReducer</code> should be a function that combines the <code>counterReducer</code> and the <code>authReducer</code>.');"
],
"solutions": [
"const INCREMENT = 'INCREMENT';\nconst DECREMENT = 'DECREMENT';\n\nconst counterReducer = (state = 0, action) => {\n switch(action.type) {\n case INCREMENT:\n return state + 1;\n case DECREMENT:\n return state - 1;\n default:\n return state;\n }\n};\n\nconst LOGIN = 'LOGIN';\nconst LOGOUT = 'LOGOUT';\n\nconst authReducer = (state = {authenticated: false}, action) => {\n switch(action.type) {\n case LOGIN:\n return {\n authenticated: true\n }\n case LOGOUT:\n return {\n authenticated: false\n }\n default:\n return state;\n }\n};\n\nconst rootReducer = Redux.combineReducers({\n count: counterReducer,\n auth: authReducer\n});\n\nconst store = Redux.createStore(rootReducer);"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036155",
"title": "Send Action Data to the Store",
"releasedOn": "December 25, 2017",
"description": [
"By now you've learned how to dispatch actions to the Redux store, but so far these actions have not contained any information other than a <code>type</code>. You can also send specific data along with your actions. In fact, this is very common because actions usually originate from some user interaction and tend to carry some data with them. The Redux store often needs to know about this data.",
"<hr>",
"There's a basic <code>notesReducer()</code> and an <code>addNoteText()</code> action creator defined in the code editor. Finish the body of the <code>addNoteText()</code> function so that it returns an <code>action</code> object. The object should include a <code>type</code> property with a value of <code>ADD_NOTE</code>, and also a <code>text</code> property set to the <code>note</code> data that's passed into the action creator. When you call the action creator, you'll pass in specific note information that you can access for the object.",
"Next, finish writing the <code>switch</code> statement in the <code>notesReducer()</code>. You need to add a case that handles the <code>addNoteText()</code> actions. This case should be triggered whenever there is an action of type <code>ADD_NOTE</code> and it should return the <code>text</code> property on the incoming <code>action</code> as the new <code>state</code>.",
"The action is dispatched at the bottom of the code. Once you're finished, run the code and watch the console. That's all it takes to send action-specific data to the store and use it when you update store <code>state</code>."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const ADD_NOTE = 'ADD_NOTE';",
"",
"const notesReducer = (state = 'Initial State', action) => {",
" switch(action.type) {",
" // change code below this line",
"",
" // change code above this line",
" default:",
" return state;",
" }",
"};",
"",
"const addNoteText = (note) => {",
" // change code below this line",
"",
" // change code above this line",
"};",
"",
"const store = Redux.createStore(notesReducer);",
"",
"console.log(store.getState());",
"store.dispatch(addNoteText('Hello!'));",
"console.log(store.getState());"
]
}
},
"tests": [
"assert((function() { const addNoteFn = addNoteText('__TEST__NOTE'); return addNoteFn.type === ADD_NOTE && addNoteFn.text === '__TEST__NOTE' })(), 'message: The action creator <code>addNoteText</code> should return an object with keys <code>type</code> and <code>text</code>.');",
"assert((function() { const initialState = store.getState(); store.dispatch(addNoteText('__TEST__NOTE')); const newState = store.getState(); return initialState !== newState && newState === '__TEST__NOTE' })(), 'message: Dispatching an action of type <code>ADD_NOTE</code> with the <code>addNoteText</code> action creator should update the <code>state</code> to the string passed to the action creator.');"
],
"solutions": [
"const ADD_NOTE = 'ADD_NOTE';\n\nconst notesReducer = (state = 'Initial State', action) => {\n switch(action.type) {\n // change code below this line\n case ADD_NOTE:\n return action.text;\n // change code above this line\n default:\n return state;\n }\n};\n\nconst addNoteText = (note) => {\n // change code below this line\n return {\n type: ADD_NOTE,\n text: note\n }\n // change code above this line\n};\n\nconst store = Redux.createStore(notesReducer);\n\nconsole.log(store.getState());\nstore.dispatch(addNoteText('Hello Redux!'));\nconsole.log(store.getState());"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036156",
"title": "Use Middleware to Handle Asynchronous Actions",
"releasedOn": "December 25, 2017",
"description": [
"So far these challenges have avoided discussing asynchronous actions, but they are an unavoidable part of web development. At some point you'll need to call asynchronous endpoints in your Redux app, so how do you handle these types of requests? Redux provides middleware designed specifically for this purpose, called Redux Thunk middleware. Here's a brief description how to use this with Redux.",
"To include Redux Thunk middleware, you pass it as an argument to <code>Redux.applyMiddleware()</code>. This statement is then provided as a second optional parameter to the <code>createStore()</code> function. Take a look at the code at the bottom of the editor to see this. Then, to create an asynchronous action, you return a function in the action creator that takes <code>dispatch</code> as an argument. Within this function, you can dispatch actions and perform asynchronous requests.",
"In this example, an asynchronous request is simulated with a <code>setTimeout()</code> call. It's common to dispatch an action before initiating any asynchronous behavior so that your application state knows that some data is being requested (this state could display a loading icon, for instance). Then, once you receive the data, you dispatch another action which carries the data as a payload along with information that the action is completed.",
"Remember that you're passing <code>dispatch</code> as a parameter to this special action creator. This is what you'll use to dispatch your actions, you simply pass the action directly to dispatch and the middleware takes care of the rest.",
"<hr>",
"Write both dispatches in the <code>handleAsync()</code> action creator. Dispatch <code>requestingData()</code> before the <code>setTimeout()</code> (the simulated API call). Then, after you receive the (pretend) data, dispatch the <code>receivedData()</code> action, passing in this data. Now you know how to handle asynchronous actions in Redux. Everything else continues to behave as before."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const REQUESTING_DATA = 'REQUESTING_DATA'",
"const RECEIVED_DATA = 'RECEIVED_DATA'",
"",
"const requestingData = () => { return {type: REQUESTING_DATA} }",
"const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }",
"",
"const handleAsync = () => {",
" return function(dispatch) {",
" // dispatch request action here",
"",
" setTimeout(function() {",
" let data = {",
" users: ['Jeff', 'William', 'Alice']",
" }",
" // dispatch received data action here",
"",
" }, 2500);",
" }",
"};",
"",
"const defaultState = {",
" fetching: false,",
" users: []",
"};",
"",
"const asyncDataReducer = (state = defaultState, action) => {",
" switch(action.type) {",
" case REQUESTING_DATA:",
" return {",
" fetching: true,",
" users: []",
" }",
" case RECEIVED_DATA:",
" return {",
" fetching: false,",
" users: action.users",
" }",
" default:",
" return state;",
" }",
"};",
"",
"const store = Redux.createStore(",
" asyncDataReducer,",
" Redux.applyMiddleware(ReduxThunk.default)",
");"
]
}
},
"tests": [
"assert(requestingData().type === REQUESTING_DATA, 'message: The <code>requestingData</code> action creator should return an object of type equal to the value of <code>REQUESTING_DATA</code>.');",
"assert(receivedData('data').type === RECEIVED_DATA, 'message: The <code>receivedData</code> action creator should return an object of type equal to the value of <code>RECEIVED_DATA</code>.');",
"assert(typeof asyncDataReducer === 'function', 'message: <code>asyncDataReducer</code> should be a function.');",
"assert((function() { const initialState = store.getState(); store.dispatch(requestingData()); const reqState = store.getState(); return initialState.fetching === false && reqState.fetching === true })(), 'message: Dispatching the requestingData action creator should update the store <code>state</code> property of fetching to <code>true</code>.');",
"assert((function() { const noWhiteSpace = handleAsync.toString().replace(/ /g,''); return noWhiteSpace.includes('dispatch(requestingData())') === true && noWhiteSpace.includes('dispatch(receivedData(data))') === true })(), 'message: Dispatching <code>handleAsync</code> should dispatch the data request action and then dispatch the received data action after a delay.');"
],
"solutions": [
"const REQUESTING_DATA = 'REQUESTING_DATA'\nconst RECEIVED_DATA = 'RECEIVED_DATA'\n\nconst requestingData = () => { return {type: REQUESTING_DATA} }\nconst receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }\n\nconst handleAsync = () => {\n return function(dispatch) {\n dispatch(requestingData());\n setTimeout(function() {\n let data = {\n users: ['Jeff', 'William', 'Alice']\n }\n dispatch(receivedData(data));\n }, 2500);\n }\n};\n\nconst defaultState = {\n fetching: false,\n users: []\n};\n\nconst asyncDataReducer = (state = defaultState, action) => {\n switch(action.type) {\n case REQUESTING_DATA:\n return {\n fetching: true,\n users: []\n }\n case RECEIVED_DATA:\n return {\n fetching: false,\n users: action.users\n }\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(\n asyncDataReducer,\n Redux.applyMiddleware(ReduxThunk.default)\n);"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036157",
"title": "Write a Counter with Redux",
"releasedOn": "December 25, 2017",
"description": [
"Now you've learned all the core principles of Redux! You've seen how to create actions and action creators, create a Redux store, dispatch your actions against the store, and design state updates with pure reducers. You've even seen how to manage complex state with reducer composition and handle asynchronous actions. These examples are simplistic, but these concepts are the core principles of Redux. If you understand them well, you're ready to start building your own Redux app. The next challenges cover some of the details regarding <code>state</code> immutability, but first, here's a review of everything you've learned so far.",
"<hr>",
"In this lesson, you'll implement a simple counter with Redux from scratch. The basics are provided in the code editor, but you'll have to fill in the details! Use the names that are provided and define <code>incAction</code> and <code>decAction</code> action creators, the <code>counterReducer()</code>, <code>INCREMENT</code> and <code>DECREMENT</code> action types, and finally the Redux <code>store</code>. Once you're finished you should be able to dispatch <code>INCREMENT</code> or <code>DECREMENT</code> actions to increment or decrement the state held in the <code>store</code>. Good luck building your first Redux app!"
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const INCREMENT = // define a constant for increment action types",
"const DECREMENT = // define a constant for decrement action types",
"",
"const counterReducer = // define the counter reducer which will increment or decrement the state based on the action it receives",
"",
"const incAction = // define an action creator for incrementing",
"",
"const decAction = // define an action creator for decrementing",
"",
"const store = // define the Redux store here, passing in your reducers"
]
}
},
"tests": [
"assert(incAction().type ===INCREMENT, 'message: The action creator <code>incAction</code> should return an action object with <code>type</code> equal to the value of <code>INCREMENT</code>');",
"assert(decAction().type === DECREMENT, 'message: The action creator <code>decAction</code> should return an action object with <code>type</code> equal to the value of <code>DECREMENT</code>');",
"assert(store.getState() === 0, 'message: The Redux store should initialize with a <code>state</code> of 0.');",
"assert((function() { const initialState = store.getState(); store.dispatch(incAction()); const incState = store.getState(); return initialState + 1 === incState })(), 'message: Dispatching <code>incAction</code> on the Redux store should increment the <code>state</code> by 1.');",
"assert((function() { const initialState = store.getState(); store.dispatch(decAction()); const decState = store.getState(); return initialState - 1 === decState })(), 'message: Dispatching <code>decAction</code> on the Redux store should decrement the <code>state</code> by 1.');",
"assert(typeof counterReducer === 'function', 'message: <code>counterReducer</code> should be a function');"
],
"solutions": [
"const INCREMENT = 'INCREMENT';\nconst DECREMENT = 'DECREMENT';\n\nconst counterReducer = (state = 0, action) => {\n switch(action.type) {\n case INCREMENT:\n return state + 1;\n case DECREMENT:\n return state - 1;\n default:\n return state;\n }\n};\n\nconst incAction = () => {\n return {\n type: INCREMENT\n }\n};\n\nconst decAction = () => {\n return {\n type: DECREMENT\n }\n};\n\nconst store = Redux.createStore(counterReducer);"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036158",
"title": "Never Mutate State",
"releasedOn": "December 25, 2017",
"description": [
"These final challenges describe several methods of enforcing the key principle of state immutability in Redux. Immutable state means that you never modify state directly, instead, you return a new copy of state.",
"If you took a snapshot of the state of a Redux app over time, you would see something like <code>state 1</code>, <code>state 2</code>, <code>state 3</code>,<code>state 4</code>, <code>...</code> and so on where each state may be similar to the last, but each is a distinct piece of data. This immutability, in fact, is what provides such features as time-travel debugging that you may have heard about.",
"Redux does not actively enforce state immutability in its store or reducers, that responsibility falls on the programmer. Fortunately, JavaScript (especially ES6) provides several useful tools you can use to enforce the immutability of your state, whether it is a <code>string</code>, <code>number</code>, <code>array</code>, or <code>object</code>. Note that strings and numbers are primitive values and are immutable by nature. In other words, 3 is always 3. You cannot change the value of the number 3. An <code>array</code> or <code>object</code>, however, is mutable. In practice, your state will probably consist of an <code>array</code> or <code>object</code>, as these are useful data structures for representing many types of information.",
"<hr>",
"There is a <code>store</code> and <code>reducer</code> in the code editor for managing to-do items. Finish writing the <code>ADD_TO_DO</code> case in the reducer to append a new to-do to the state. There are a few ways to accomplish this with standard JavaScript or ES6. See if you can find a way to return a new array with the item from <code>action.todo</code> appended to the end."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const ADD_TO_DO = 'ADD_TO_DO';",
"",
"// A list of strings representing tasks to do:",
"const todos = [",
" 'Go to the store',",
" 'Clean the house',",
" 'Cook dinner',",
" 'Learn to code',",
"];",
"",
"const immutableReducer = (state = todos, action) => {",
" switch(action.type) {",
" case ADD_TO_DO:",
" // don't mutate state here or the tests will fail",
" return",
" default:",
" return state;",
" }",
"};",
"",
"// an example todo argument would be 'Learn React',",
"const addToDo = (todo) => {",
" return {",
" type: ADD_TO_DO,",
" todo",
" }",
"}",
"",
"const store = Redux.createStore(immutableReducer);"
]
}
},
"tests": [
"assert((function() { const todos = [ 'Go to the store', 'Clean the house', 'Cook dinner', 'Learn to code' ]; const initialState = store.getState(); return Array.isArray(initialState) && initialState.join(',') === todos.join(','); })(), 'message: The Redux store should exist and initialize with a state equal to the <code>todos</code> array in the code editor.');",
"assert(typeof addToDo === 'function' && typeof immutableReducer === 'function', 'message: <code>addToDo</code> and <code>immutableReducer</code> both should be functions.');",
"assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(addToDo('__TEST__TO__DO__')); const finalState = store.getState(); const expectedState = [ 'Go to the store', 'Clean the house', 'Cook dinner', 'Learn to code', '__TEST__TO__DO__' ]; return( isFrozen && DeepEqual(finalState, expectedState)); })(), 'message: Dispatching an action of type <code>ADD_TO_DO</code> on the Redux store should add a <code>todo</code> item and should NOT mutate state.');"
],
"solutions": [
"const ADD_TO_DO = 'ADD_TO_DO';\n\n// A list of strings representing tasks to do:\nconst todos = [\n 'Go to the store',\n 'Clean the house',\n 'Cook dinner',\n 'Learn to code',\n];\n\nconst immutableReducer = (state = todos, action) => {\n switch(action.type) {\n case ADD_TO_DO:\n return state.concat(action.todo);\n default:\n return state;\n }\n};\n\n// an example todo argument would be 'Learn React',\nconst addToDo = (todo) => {\n return {\n type: ADD_TO_DO,\n todo\n }\n}\n\nconst store = Redux.createStore(immutableReducer);"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d4036159",
"title": "Use the Spread Operator on Arrays",
"releasedOn": "December 25, 2017",
"description": [
"One solution from ES6 to help enforce state immutability in Redux is the spread operator: <code>...</code>. The spread operator has a variety of applications, one of which is well-suited to the previous challenge of producing a new array from an existing array. This is relatively new, but commonly used syntax. For example, if you have an array <code>myArray</code> and write:",
"<code>let newArray = [...myArray];</code>",
"<code>newArray</code> is now a clone of <code>myArray</code>. Both arrays still exist separately in memory. If you perform a mutation like <code>newArray.push(5)</code>, <code>myArray</code> doesn't change. The <code>...</code> effectively <i>spreads</i> out the values in <code>myArray</code> into a new array. To clone an array but add additional values in the new array, you could write <code>[...myArray, 'new value']</code>. This would return a new array composed of the values in <code>myArray</code> and the string <code>'new value'</code> as the last value. The spread syntax can be used multiple times in array composition like this, but it's important to note that it only makes a shallow copy of the array. That is to say, it only provides immutable array operations for one-dimensional arrays.",
"<hr>",
"Use the spread operator to return a new copy of state when a to-do is added."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const immutableReducer = (state = ['Do not mutate state!'], action) => {",
" switch(action.type) {",
" case 'ADD_TO_DO':",
" // don't mutate state here or the tests will fail",
" return",
" default:",
" return state;",
" }",
"};",
"",
"const addToDo = (todo) => {",
" return {",
" type: 'ADD_TO_DO',",
" todo",
" }",
"}",
"",
"const store = Redux.createStore(immutableReducer);"
]
}
},
"tests": [
"assert((function() { const initialState = store.getState(); return ( Array.isArray(initialState) === true && initialState[0] === 'Do not mutate state!'); })(), 'message: The Redux store should exist and initialize with a state equal to <code>[Do not mutate state!]</code>.');",
"assert(typeof addToDo === 'function' && typeof immutableReducer === 'function', 'message: <code>addToDo</code> and <code>immutableReducer</code> both should be functions.');",
"assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(addToDo('__TEST__TO__DO__')); const finalState = store.getState(); const expectedState = [ 'Do not mutate state!', '__TEST__TO__DO__' ]; return( isFrozen && DeepEqual(finalState, expectedState)); })(), 'message: Dispatching an action of type <code>ADD_TO_DO</code> on the Redux store should add a <code>todo</code> item and should NOT mutate state.');",
"getUserInput => assert(getUserInput('index').includes('...state'), 'message: The spread operator should be used to return new state.');"
],
"solutions": [
"const immutableReducer = (state = ['Do not mutate state!'], action) => {\n switch(action.type) {\n case 'ADD_TO_DO':\n return [\n ...state,\n action.todo\n ];\n default:\n return state;\n }\n};\n\nconst addToDo = (todo) => {\n return {\n type: 'ADD_TO_DO',\n todo\n }\n}\n\nconst store = Redux.createStore(immutableReducer);"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d403615a",
"title": "Remove an Item from an Array",
"releasedOn": "December 25, 2017",
"description": [
"Time to practice removing items from an array. The spread operator can be used here as well. Other useful JavaScript methods include <code>slice()</code> and <code>concat()</code>.",
"<hr>",
"The reducer and action creator were modified to remove an item from an array based on the index of the item. Finish writing the reducer so a new state array is returned with the item at the specific index removed."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const immutableReducer = (state = [0,1,2,3,4,5], action) => {",
" switch(action.type) {",
" case 'REMOVE_ITEM':",
" // don't mutate state here or the tests will fail",
" return",
" default:",
" return state;",
" }",
"};",
"",
"const removeItem = (index) => {",
" return {",
" type: 'REMOVE_ITEM',",
" index",
" }",
"}",
"",
"const store = Redux.createStore(immutableReducer);"
]
}
},
"tests": [
"assert((function() { const initialState = store.getState(); return (Array.isArray(initialState) === true && DeepEqual(initialState, [0, 1, 2, 3, 4, 5])); })(), 'message: The Redux store should exist and initialize with a state equal to <code>[0,1,2,3,4,5]</code>');",
"assert(typeof removeItem === 'function' && typeof immutableReducer === 'function', 'message: <code>removeItem</code> and <code>immutableReducer</code> both should be functions.');",
"assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(removeItem(3)); const state_1 = store.getState(); store.dispatch(removeItem(2)); const state_2 = store.getState(); store.dispatch(removeItem(0)); store.dispatch(removeItem(0)); store.dispatch(removeItem(0)); const state_3 = store.getState(); return isFrozen && DeepEqual(state_1, [0, 1, 2, 4, 5]) && DeepEqual(state_2, [0, 1, 4, 5]) && DeepEqual(state_3, [5]); })(), 'message: Dispatching the <code>removeItem</code> action creator should remove items from the state and should NOT mutate state.');"
],
"solutions": [
"const immutableReducer = (state = [0,1,2,3,4,5], action) => {\n switch(action.type) {\n case 'REMOVE_ITEM':\n return [\n ...state.slice(0, action.index),\n ...state.slice(action.index + 1)\n ];\n default:\n return state;\n }\n};\n\nconst removeItem = (index) => {\n return {\n type: 'REMOVE_ITEM',\n index\n }\n}\n\nconst store = Redux.createStore(immutableReducer);"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
},
{
"id": "5a24c314108439a4d403615b",
"title": "Copy an Object with Object.assign",
"releasedOn": "December 25, 2017",
"description": [
"The last several challenges worked with arrays, but there are ways to help enforce state immutability when state is an <code>object</code>, too. A useful tool for handling objects is the <code>Object.assign()</code> utility. <code>Object.assign()</code> takes a target object and source objects and maps properties from the source objects to the target object. Any matching properties are overwritten by properties in the source objects. This behavior is commonly used to make shallow copies of objects by passing an empty object as the first argument followed by the object(s) you want to copy. Here's an example:",
"<code>const newObject = Object.assign({}, obj1, obj2);</code>",
"This creates <code>newObject</code> as a new <code>object</code>, which contains the properties that currently exist in <code>obj1</code> and <code>obj2</code>.",
"<hr>",
"The Redux state and actions were modified to handle an <code>object</code> for the <code>state</code>. Edit the code to return a new <code>state</code> object for actions with type <code>ONLINE</code>, which set the <code>status</code> property to the string <code>online</code>. Try to use <code>Object.assign()</code> to complete the challenge."
],
"files": {
"indexjsx": {
"key": "indexjsx",
"ext": "jsx",
"name": "index",
"contents": [
"const defaultState = {",
" user: 'CamperBot',",
" status: 'offline',",
" friends: '732,982',",
" community: 'freeCodeCamp'",
"};",
"",
"const immutableReducer = (state = defaultState, action) => {",
" switch(action.type) {",
" case 'ONLINE':",
" // don't mutate state here or the tests will fail",
" return",
" default:",
" return state;",
" }",
"};",
"",
"const wakeUp = () => {",
" return {",
" type: 'ONLINE'",
" }",
"};",
"",
"const store = Redux.createStore(immutableReducer);"
]
}
},
"tests": [
"assert((function() { const expectedState = { user: 'CamperBot', status: 'offline', friends: '732,982', community: 'freeCodeCamp' }; const initialState = store.getState(); return DeepEqual(expectedState, initialState); })(), 'message: The Redux store should exist and initialize with a state that is equivalent to the <code>defaultState</code> object declared on line 1.');",
"assert(typeof wakeUp === 'function' && typeof immutableReducer === 'function', 'message: <code>wakeUp</code> and <code>immutableReducer</code> both should be functions.');",
"assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch({type: 'ONLINE'}); const finalState = store.getState(); const expectedState = { user: 'CamperBot', status: 'online', friends: '732,982', community: 'freeCodeCamp' }; return isFrozen && DeepEqual(finalState, expectedState); })(), 'message: Dispatching an action of type <code>ONLINE</code> should update the property <code>status</code> in state to <code>online</code> and should NOT mutate state.');",
"getUserInput => assert(getUserInput('index').includes('Object.assign'), 'message: <code>Object.assign</code> should be used to return new state.');"
],
"solutions": [
"const defaultState = {\n user: 'CamperBot',\n status: 'offline',\n friends: '732,982',\n community: 'freeCodeCamp'\n};\n\nconst immutableReducer = (state = defaultState, action) => {\n switch(action.type) {\n case 'ONLINE':\n return Object.assign({}, state, {\n status: 'online'\n });\n default:\n return state;\n }\n};\n\nconst wakeUp = () => {\n return {\n type: 'ONLINE'\n }\n};\n\nconst store = Redux.createStore(immutableReducer);"
],
"type": "modern",
"isRequired": false,
"translations": {},
"redux": true
} }
] ]
} }

View File

@ -45,6 +45,7 @@ Observable.combineLatest(
var isLocked = !!challengeSpec.isLocked; var isLocked = !!challengeSpec.isLocked;
var message = challengeSpec.message; var message = challengeSpec.message;
var required = challengeSpec.required || []; var required = challengeSpec.required || [];
var template = challengeSpec.template;
console.log('parsed %s successfully', blockName); console.log('parsed %s successfully', blockName);
@ -113,6 +114,7 @@ Observable.combineLatest(
}) })
.join(' '); .join(' ');
challenge.required = (challenge.required || []).concat(required); challenge.required = (challenge.required || []).concat(required);
challenge.template = challenge.template || template;
return challenge; return challenge;
}); });

View File

@ -4,7 +4,6 @@ import { Observable } from 'rx';
import tape from 'tape'; import tape from 'tape';
import getChallenges from './getChallenges'; import getChallenges from './getChallenges';
function createIsAssert(t, isThing) { function createIsAssert(t, isThing) {
const { assert } = t; const { assert } = t;
return function() { return function() {
@ -56,10 +55,21 @@ function createTest({
tests = [], tests = [],
solutions = [], solutions = [],
head = [], head = [],
tail = [] tail = [],
react = false,
redux = false,
reactRedux = false
}) { }) {
solutions = solutions.filter(solution => !!solution); solutions = solutions.filter(solution => !!solution);
tests = tests.filter(test => !!test); tests = tests.filter(test => !!test);
// No support for async tests
const isAsync = s => s.includes('(async () => ');
if (isAsync(tests.join(''))) {
console.log(`Replacing Async Tests for Challenge ${title}`);
tests = tests.map(t => isAsync(t) ? "assert(true, 'message: great');" : t);
}
head = head.join('\n'); head = head.join('\n');
tail = tail.join('\n'); tail = tail.join('\n');
const plan = tests.length; const plan = tests.length;
@ -81,16 +91,92 @@ function createTest({
}); });
} }
return Observable.just(t) return Observable.just(t)
.map(fillAssert) .map(fillAssert)
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
// assert and code used within the eval // assert and code used within the eval
.doOnNext(assert => { .doOnNext(assert => {
solutions.forEach(solution => { solutions.forEach(solution => {
// Original code string
const originalCode = solution;
tests.forEach(test => { tests.forEach(test => {
const code = solution; let code = solution;
const editor = { getValue() { return code; } };
/* NOTE: Provide dependencies for React/Redux challenges
* and configure testing environment
*/
let React,
ReactDOM,
Redux,
ReduxThunk,
ReactRedux,
Enzyme,
document;
// Fake Deep Equal dependency
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
// Hardcode Deep Freeze dependency
const DeepFreeze = (o) => {
Object.freeze(o);
Object.getOwnPropertyNames(o).forEach(function(prop) {
if (o.hasOwnProperty(prop)
&& o[prop] !== null
&& (
typeof o[prop] === 'object' ||
typeof o[prop] === 'function'
)
&& !Object.isFrozen(o[prop])) {
DeepFreeze(o[prop]);
}
});
return o;
};
if (react || redux || reactRedux) {
// Provide dependencies, just provide all of them
React = require('react');
ReactDOM = require('react-dom');
Redux = require('redux');
ReduxThunk = require('redux-thunk');
ReactRedux = require('react-redux');
Enzyme = require('enzyme');
const Adapter15 = require('enzyme-adapter-react-15');
Enzyme.configure({ adapter: new Adapter15() });
/* Transpile ALL the code
* (we may use JSX in head or tail or tests, too): */
const transform = require('babel-standalone').transform;
const options = { presets: [ 'es2015', 'react' ] };
head = transform(head, options).code;
solution = transform(solution, options).code;
tail = transform(tail, options).code;
test = transform(test, options).code;
const { JSDOM } = require('jsdom');
// Mock DOM document for ReactDOM.render method
const jsdom = new JSDOM(`<!doctype html>
<html>
<body>
<div id="challenge-node"></div>
</body>
</html>
`);
const { window } = jsdom;
// Mock DOM for ReactDOM tests
document = window.document;
global.window = window;
global.document = window.document;
}
const editor = {
getValue() { return code; },
getOriginalCode() { return originalCode; }
};
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
try { try {
(() => { (() => {
@ -98,7 +184,8 @@ function createTest({
head + '\n;;' + head + '\n;;' +
solution + '\n;;' + solution + '\n;;' +
tail + '\n;;' + tail + '\n;;' +
test); test
);
})(); })();
} catch (e) { } catch (e) {
t.fail(e); t.fail(e);
@ -114,6 +201,7 @@ Observable.from(getChallenges())
.flatMap(challengeSpec => { .flatMap(challengeSpec => {
return Observable.from(challengeSpec.challenges); return Observable.from(challengeSpec.challenges);
}) })
.filter(({ type }) => type !== 'modern')
.flatMap(challenge => { .flatMap(challenge => {
return createTest(challenge); return createTest(challenge);
}) })
@ -135,4 +223,3 @@ Observable.from(getChallenges())
err => { throw err; }, err => { throw err; },
() => process.exit(0) () => process.exit(0)
); );