diff --git a/curriculum/challenges/english/03-front-end-libraries/react/create-a-controlled-form.english.md b/curriculum/challenges/english/03-front-end-libraries/react/create-a-controlled-form.english.md index 510dc41a42..83de9104dd 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/create-a-controlled-form.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/create-a-controlled-form.english.md @@ -8,11 +8,13 @@ forumTopicId: 301384 --- ## Description +
The last challenge showed that React can control the internal state for certain elements like input and textarea, which makes them controlled components. This applies to other form elements as well, including the regular HTML form element.
## Instructions +
The MyForm component is set up with an empty form with a submit handler. The submit handler will be called when the form is submitted. We've added a button which submits the form. You can see it has the type set to submit indicating it is the button controlling the form. Add the input element in the form and set its value and onChange() attributes like the last challenge. You should then complete the handleSubmit method so that it sets the component state property submit to the current input value in the local state. @@ -21,6 +23,7 @@ Finally, create an h1 tag after the form which renders
## Tests +
```yml @@ -30,17 +33,77 @@ tests: - text: The state of MyForm should initialize with input and submit properties, both set to empty strings. testString: assert(Enzyme.mount(React.createElement(MyForm)).state('input') === '' && Enzyme.mount(React.createElement(MyForm)).state('submit') === ''); - text: Typing in the input element should update the input property of the component's state. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '''' }); return waitForIt(() => mockedComponent.state(''input''))}; const _2 = () => { mockedComponent.find(''input'').simulate(''change'', { target: { value: ''TestInput'' }}); return waitForIt(() => ({ state: mockedComponent.state(''input''), inputVal: mockedComponent.find(''input'').props().value }))}; const before = await _1(); const after = await _2(); assert(before === '''' && after.state === ''TestInput'' && after.inputVal === ''TestInput''); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(MyForm)); + const _1 = () => { + mockedComponent.setState({ input: '' }); + return mockedComponent.state('input'); + }; + const _2 = () => { + mockedComponent + .find('input') + .simulate('change', { target: { value: 'TestInput' } }); + return { + state: mockedComponent.state('input'), + inputVal: mockedComponent.find('input').props().value, + }; + }; + const before = _1(); + const after = _2(); + assert( + before === '' && + after.state === 'TestInput' && + after.inputVal === 'TestInput' + ); + })(); + " - text: Submitting the form should run handleSubmit which should set the submit property in state equal to the current input. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '''' }); mockedComponent.setState({submit: ''''}); mockedComponent.find(''input'').simulate(''change'', {target: {value: ''SubmitInput''}}); return waitForIt(() => mockedComponent.state(''submit''))}; const _2 = () => { mockedComponent.find(''form'').simulate(''submit''); return waitForIt(() => mockedComponent.state(''submit''))}; const before = await _1(); const after = await _2(); assert(before === '''' && after === ''SubmitInput''); };' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(MyForm)); + const _1 = () => { + mockedComponent.setState({ input: '' }); + mockedComponent.setState({ submit: '' }); + mockedComponent + .find('input') + .simulate('change', { target: { value: 'SubmitInput' } }); + return mockedComponent.state('submit'); + }; + const _2 = () => { + mockedComponent.find('form').simulate('submit'); + return mockedComponent.state('submit'); + }; + const before = _1(); + const after = _2(); + assert(before === '' && after === 'SubmitInput'); + })(); + " - text: The h1 header should render the value of the submit field from the component's state. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '''' }); mockedComponent.setState({submit: ''''}); mockedComponent.find(''input'').simulate(''change'', {target: {value: ''TestInput''}}); return waitForIt(() => mockedComponent.find(''h1'').text())}; const _2 = () => { mockedComponent.find(''form'').simulate(''submit''); return waitForIt(() => mockedComponent.find(''h1'').text())}; const before = await _1(); const after = await _2(); assert(before === '''' && after === ''TestInput''); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(MyForm)); + const _1 = () => { + mockedComponent.setState({ input: '' }); + mockedComponent.setState({ submit: '' }); + mockedComponent + .find('input') + .simulate('change', { target: { value: 'TestInput' } }); + return mockedComponent.find('h1').text(); + }; + const _2 = () => { + mockedComponent.find('form').simulate('submit'); + return mockedComponent.find('h1').text(); + }; + const before = _1(); + const after = _2(); + assert(before === '' && after === 'TestInput'); + })(); + " ```
## Challenge Seed +
@@ -63,35 +126,35 @@ class MyForm extends React.Component { } handleSubmit(event) { // change code below this line - + // change code above this line } render() { return (
- { /* change code below this line */ } + {/* change code below this line */} - { /* change code above this line */ } + {/* change code above this line */}
- { /* change code below this line */ } + {/* change code below this line */} - { /* change code above this line */ } + {/* change code above this line */}
); } -}; +} ```
- ### After Test +
```js -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')); ```
@@ -99,8 +162,8 @@ ReactDOM.render(, document.getElementById('root'))
## Solution -
+
```js class MyForm extends React.Component { @@ -119,8 +182,8 @@ class MyForm extends React.Component { }); } handleSubmit(event) { - event.preventDefault() - this.setState((state) => ({ + event.preventDefault(); + this.setState(state => ({ submit: state.input })); } @@ -128,16 +191,14 @@ class MyForm extends React.Component { return (
- +

{this.state.submit}

); } -}; +} ```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/optimize-re-renders-with-shouldcomponentupdate.english.md b/curriculum/challenges/english/03-front-end-libraries/react/optimize-re-renders-with-shouldcomponentupdate.english.md index 6d2409d7b4..ec243ddd4e 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/optimize-re-renders-with-shouldcomponentupdate.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/optimize-re-renders-with-shouldcomponentupdate.english.md @@ -8,17 +8,20 @@ forumTopicId: 301398 --- ## Description +
So far, if any component receives new state or new props, it re-renders itself and all its children. This is usually okay. But React provides a lifecycle method you can call when child components receive new state or props, and declare specifically if the components should update or not. The method is shouldComponentUpdate(), and it takes nextProps and nextState as parameters. This method is a useful way to optimize performance. For example, the default behavior is that your component re-renders when it receives new props, even if the props haven't changed. You can use shouldComponentUpdate() to prevent this by comparing the props. The method must return a boolean value that tells React whether or not to update the component. You can compare the current props (this.props) to the next props (nextProps) to determine if you need to update or not, and return true or false accordingly.
## Instructions +
The shouldComponentUpdate() method is added in a component called OnlyEvens. Currently, this method returns true so OnlyEvens re-renders every time it receives new props. Modify the method so OnlyEvens updates only if the value of its new props is even. Click the Add button and watch the order of events in your browser's console as the lifecycle hooks are triggered.
## Tests +
```yml @@ -28,15 +31,49 @@ tests: - text: The shouldComponentUpdate method should be defined on the OnlyEvens component. testString: assert((() => { const child = React.createElement(OnlyEvens).type.prototype.shouldComponentUpdate.toString().replace(/s/g,''); return child !== 'undefined'; })()); - text: The OnlyEvens component should return an h1 tag which renders the value of this.props.value. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(Controller)); const first = () => { mockedComponent.setState({ value: 1000 }); return waitForIt(() => mockedComponent.find(''h1'').html()); }; const second = () => { mockedComponent.setState({ value: 10 }); return waitForIt(() => mockedComponent.find(''h1'').html()); }; const firstValue = await first(); const secondValue = await second(); assert(firstValue === ''

1000

'' && secondValue === ''

10

''); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(Controller)); + const first = () => { + mockedComponent.setState({ value: 1000 }); + return mockedComponent.find('h1').html(); + }; + const second = () => { + mockedComponent.setState({ value: 10 }); + return mockedComponent.find('h1').html(); + }; + const firstValue = first(); + const secondValue = second(); + assert(firstValue === '

1000

' && secondValue === '

10

'); + })(); + " - text: OnlyEvens should re-render only when nextProps.value is even. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(Controller)); const first = () => { mockedComponent.setState({ value: 8 }); return waitForIt(() => mockedComponent.find(''h1'').text()); }; const second = () => { mockedComponent.setState({ value: 7 }); return waitForIt(() => mockedComponent.find(''h1'').text()); }; const third = () => { mockedComponent.setState({ value: 42 }); return waitForIt(() => mockedComponent.find(''h1'').text()); }; const firstValue = await first(); const secondValue = await second(); const thirdValue = await third(); assert(firstValue === ''8'' && secondValue === ''8'' && thirdValue === ''42''); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(Controller)); + const first = () => { + mockedComponent.setState({ value: 8 }); + return mockedComponent.find('h1').text(); + }; + const second = () => { + mockedComponent.setState({ value: 7 }); + return mockedComponent.find('h1').text(); + }; + const third = () => { + mockedComponent.setState({ value: 42 }); + return mockedComponent.find('h1').text(); + }; + const firstValue = first(); + const secondValue = second(); + const thirdValue = third(); + assert(firstValue === '8' && secondValue === '8' && thirdValue === '42'); + })(); + " ```
## Challenge Seed +
@@ -48,17 +85,17 @@ class OnlyEvens extends React.Component { } shouldComponentUpdate(nextProps, nextState) { console.log('Should I update?'); - // change code below this line + // change code below this line return true; - // change code above this line + // change code above this line } componentDidUpdate() { console.log('Component re-rendered.'); } render() { - return

{this.props.value}

+ return

{this.props.value}

; } -}; +} class Controller extends React.Component { constructor(props) { @@ -69,7 +106,7 @@ class Controller extends React.Component { this.addValue = this.addValue.bind(this); } addValue() { - this.setState((state) => ({ + this.setState(state => ({ value: state.value + 1 })); } @@ -77,21 +114,21 @@ class Controller extends React.Component { return (
- +
); } -}; +} ```
- ### After Test +
```js -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')); ```
@@ -99,8 +136,8 @@ ReactDOM.render(, document.getElementById('root'))
## Solution -
+
```js class OnlyEvens extends React.Component { @@ -117,9 +154,9 @@ class OnlyEvens extends React.Component { console.log('Component re-rendered.'); } render() { - return

{this.props.value}

+ return

{this.props.value}

; } -}; +} class Controller extends React.Component { constructor(props) { @@ -127,10 +164,10 @@ class Controller extends React.Component { this.state = { value: 0 }; - this.addValue = this.addValue.bind(this); + this.addValue = this.addValue.bind(this); } addValue() { - this.setState((state) => ({ + this.setState(state => ({ value: state.value + 1 })); } @@ -138,11 +175,11 @@ class Controller extends React.Component { return (
- +
); } -}; +} ```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/render-conditionally-from-props.english.md b/curriculum/challenges/english/03-front-end-libraries/react/render-conditionally-from-props.english.md index 29fd0f012d..7091663c63 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/render-conditionally-from-props.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/render-conditionally-from-props.english.md @@ -8,12 +8,14 @@ forumTopicId: 301405 --- ## Description +
So far, you've seen how to use if/else, &&, null and the ternary operator (condition ? expressionIfTrue : expressionIfFalse) to make conditional decisions about what to render and when. However, there's one important topic left to discuss that lets you combine any or all of these concepts with another powerful React feature: props. Using props to conditionally render code is very common with React developers — that is, they use the value of a given prop to automatically make decisions about what to render. In this challenge, you'll set up a child component to make rendering decisions based on props. You'll also use the ternary operator, but you can see how several of the other concepts that were covered in the last few challenges might be just as useful in this context.
## Instructions +
The code editor has two components that are partially defined for you: a parent called GameOfChance, and a child called Results. They are used to create a simple game where the user presses a button to see if they win or lose. First, you'll need a simple expression that randomly returns a different value every time it is run. You can use Math.random(). This method returns a value between 0 (inclusive) and 1 (exclusive) each time it is called. So for 50/50 odds, use Math.random() >= .5 in your expression. Statistically speaking, this expression will return true 50% of the time, and false the other 50%. On line 30, replace the comment with this expression to complete the variable declaration. @@ -21,6 +23,7 @@ Now you have an expression that you can use to make a randomized decision in the
## Tests +
```yml @@ -36,15 +39,144 @@ tests: - text: 'When the GameOfChance component is first rendered to the DOM, a p element should be returned with the inner text of Turn: 1.' testString: 'assert.strictEqual(Enzyme.mount(React.createElement(GameOfChance)).find(''p'').text(), ''Turn: 1'');' - text: 'Each time the button is clicked, the counter state should be incremented by a value of 1, and a single p element should be rendered to the DOM that contains the text "Turn: N", where N is the value of the counter state.' - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(GameOfChance)); const simulate = () => { comp.find(''button'').simulate(''click''); };const result = () => ({ count: comp.state(''counter''), text: comp.find(''p'').text() });const _1 = () => { simulate(); return waitForIt(() => result())}; const _2 = () => { simulate(); return waitForIt(() => result())}; const _3 = () => { simulate(); return waitForIt(() => result())}; const _4 = () => { simulate(); return waitForIt(() => result())}; const _5 = () => { simulate(); return waitForIt(() => result())}; const _1_val = await _1(); const _2_val = await _2(); const _3_val = await _3(); const _4_val = await _4(); const _5_val = await _5(); assert(_1_val.count === 2 && _1_val.text === ''Turn: 2'' && _2_val.count === 3 && _2_val.text === ''Turn: 3'' && _3_val.count === 4 && _3_val.text === ''Turn: 4'' && _4_val.count === 5 && _4_val.text === ''Turn: 5'' && _5_val.count === 6 && _5_val.text === ''Turn: 6''); }; ' + testString: "(() => { + const comp = Enzyme.mount(React.createElement(GameOfChance)); + const simulate = () => { + comp.find('button').simulate('click'); + }; + const result = () => ({ + count: comp.state('counter'), + text: comp.find('p').text(), + }); + const _1 = () => { + simulate(); + return result(); + }; + const _2 = () => { + simulate(); + return result(); + }; + const _3 = () => { + simulate(); + return result(); + }; + const _4 = () => { + simulate(); + return result(); + }; + const _5 = () => { + simulate(); + return result(); + }; + const _1_val = _1(); + const _2_val = _2(); + const _3_val = _3(); + const _4_val = _4(); + const _5_val = _5(); + assert( + _1_val.count === 2 && + _1_val.text === 'Turn: 2' && + _2_val.count === 3 && + _2_val.text === 'Turn: 3' && + _3_val.count === 4 && + _3_val.text === 'Turn: 4' && + _4_val.count === 5 && + _4_val.text === 'Turn: 5' && + _5_val.count === 6 && + _5_val.text === 'Turn: 6' + ); + })(); + " - text: When the GameOfChance component is first mounted to the DOM and each time the button is clicked thereafter, a single h1 element should be returned that randomly renders either You Win! or You Lose!. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(GameOfChance)); const simulate = () => { comp.find(''button'').simulate(''click''); };const result = () => ({ h1: comp.find(''h1'').length, text: comp.find(''h1'').text() });const _1 = result(); const _2 = () => { simulate(); return waitForIt(() => result())}; const _3 = () => { simulate(); return waitForIt(() => result())}; const _4 = () => { simulate(); return waitForIt(() => result())}; const _5 = () => { simulate(); return waitForIt(() => result())}; const _6 = () => { simulate(); return waitForIt(() => result())}; const _7 = () => { simulate(); return waitForIt(() => result())}; const _8 = () => { simulate(); return waitForIt(() => result())}; const _9 = () => { simulate(); return waitForIt(() => result())}; const _10 = () => { simulate(); return waitForIt(() => result())}; const _2_val = await _2(); const _3_val = await _3(); const _4_val = await _4(); const _5_val = await _5(); const _6_val = await _6(); const _7_val = await _7(); const _8_val = await _8(); const _9_val = await _9(); const _10_val = await _10(); const __text = new Set([_1.text, _2_val.text, _3_val.text, _4_val.text, _5_val.text, _6_val.text, _7_val.text, _8_val.text, _9_val.text, _10_val.text]); const __h1 = new Set([_1.h1, _2_val.h1, _3_val.h1, _4_val.h1, _5_val.h1, _6_val.h1, _7_val.h1, _8_val.h1, _9_val.h1, _10_val.h1]); assert(__text.size === 2 && __h1.size === 1); }; ' + testString: "(() => { + const comp = Enzyme.mount(React.createElement(GameOfChance)); + const simulate = () => { + comp.find('button').simulate('click'); + }; + const result = () => ({ + h1: comp.find('h1').length, + text: comp.find('h1').text(), + }); + const _1 = result(); + const _2 = () => { + simulate(); + return result(); + }; + const _3 = () => { + simulate(); + return result(); + }; + const _4 = () => { + simulate(); + return result(); + }; + const _5 = () => { + simulate(); + return result(); + }; + const _6 = () => { + simulate(); + return result(); + }; + const _7 = () => { + simulate(); + return result(); + }; + const _8 = () => { + simulate(); + return result(); + }; + const _9 = () => { + simulate(); + return result(); + }; + const _10 = () => { + simulate(); + return result(); + }; + const _2_val = _2(); + const _3_val = _3(); + const _4_val = _4(); + const _5_val = _5(); + const _6_val = _6(); + const _7_val = _7(); + const _8_val = _8(); + const _9_val = _9(); + const _10_val = _10(); + const __text = new Set([ + _1.text, + _2_val.text, + _3_val.text, + _4_val.text, + _5_val.text, + _6_val.text, + _7_val.text, + _8_val.text, + _9_val.text, + _10_val.text, + ]); + const __h1 = new Set([ + _1.h1, + _2_val.h1, + _3_val.h1, + _4_val.h1, + _5_val.h1, + _6_val.h1, + _7_val.h1, + _8_val.h1, + _9_val.h1, + _10_val.h1, + ]); + assert(__text.size === 2 && __h1.size === 1); + })(); + " ```
## Challenge Seed +
@@ -55,22 +187,16 @@ class Results extends React.Component { super(props); } render() { - return ( -

- { - /* change code here */ - } -

- ) - }; -}; + return

{/* change code here */}

; + } +} class GameOfChance extends React.Component { constructor(props) { super(props); this.state = { counter: 1 - } + }; this.handleClick = this.handleClick.bind(this); } handleClick() { @@ -83,24 +209,24 @@ class GameOfChance extends React.Component { return (
- { /* change code below this line */ } + {/* change code below this line */} - { /* change code above this line */ } + {/* change code above this line */}

{'Turn: ' + this.state.counter}

); } -}; +} ```
- ### After Test +
```js -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')); ```
@@ -108,8 +234,8 @@ ReactDOM.render(, document.getElementById('root'))
## Solution -
+
```js class Results extends React.Component { @@ -117,24 +243,16 @@ class Results extends React.Component { super(props); } render() { - return ( -

- { - this.props.fiftyFifty ? - 'You Win!' : - 'You Lose!' - } -

- ) - }; -}; + return

{this.props.fiftyFifty ? 'You Win!' : 'You Lose!'}

; + } +} class GameOfChance extends React.Component { constructor(props) { super(props); this.state = { counter: 1 - } + }; this.handleClick = this.handleClick.bind(this); } handleClick() { @@ -143,7 +261,7 @@ class GameOfChance extends React.Component { }); } render() { - const expression = Math.random() >= .5; + const expression = Math.random() >= 0.5; return (
@@ -152,7 +270,7 @@ class GameOfChance extends React.Component {
); } -}; +} ```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-a-ternary-expression-for-conditional-rendering.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-a-ternary-expression-for-conditional-rendering.english.md index 1a12d74dfb..5a03e68737 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/use-a-ternary-expression-for-conditional-rendering.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/use-a-ternary-expression-for-conditional-rendering.english.md @@ -8,22 +8,25 @@ forumTopicId: 301414 --- ## Description +
Before moving on to dynamic rendering techniques, there's one last way to use built-in JavaScript conditionals to render what you want: the ternary operator. The ternary operator is often utilized as a shortcut for if/else statements in JavaScript. They're not quite as robust as traditional if/else statements, but they are very popular among React developers. One reason for this is because of how JSX is compiled, if/else statements can't be inserted directly into JSX code. You might have noticed this a couple challenges ago — when an if/else statement was required, it was always outside the return statement. Ternary expressions can be an excellent alternative if you want to implement conditional logic within your JSX. Recall that a ternary operator has three parts, but you can combine several ternary expressions together. Here's the basic syntax: ```js -condition ? expressionIfTrue : expressionIfFalse +condition ? expressionIfTrue : expressionIfFalse; ```
## Instructions +
The code editor has three constants defined within the CheckUserAge component's render() method. They are called buttonOne, buttonTwo, and buttonThree. Each of these is assigned a simple JSX expression representing a button element. First, initialize the state of CheckUserAge with input and userAge both set to values of an empty string. Once the component is rendering information to the page, users should have a way to interact with it. Within the component's return statement, set up a ternary expression that implements the following logic: when the page first loads, render the submit button, buttonOne, to the page. Then, when a user enters their age and clicks the button, render a different button based on the age. If a user enters a number less than 18, render buttonThree. If a user enters a number greater than or equal to 18, render buttonTwo.
## Tests +
```yml @@ -35,11 +38,101 @@ tests: - text: When the CheckUserAge component is first rendered to the DOM, the button's inner text should be Submit. testString: assert(Enzyme.mount(React.createElement(CheckUserAge)).find('button').text() === 'Submit'); - text: When a number of less than 18 is entered into the input element and the button is clicked, the button's inner text should read You Shall Not Pass. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const initialButton = mockedComponent.find(''button'').text(); const enter3AndClickButton = () => { mockedComponent.find(''input'').simulate(''change'', {target: { value: ''3'' }}); mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find(''button'').text(); }); }; const enter17AndClickButton = () => { mockedComponent.find(''input'').simulate(''change'', {target: { value: ''17'' }}); mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find(''button'').text(); }); }; const userAge3 = await enter3AndClickButton(); const userAge17 = await enter17AndClickButton(); assert(initialButton === ''Submit'' && userAge3 === ''You Shall Not Pass'' && userAge17 === ''You Shall Not Pass''); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); + const initialButton = mockedComponent.find('button').text(); + const enter3AndClickButton = () => { + mockedComponent + .find('input') + .simulate('change', { target: { value: '3' } }); + mockedComponent.find('button').simulate('click'); + mockedComponent.update(); + return mockedComponent.find('button').text(); + }; + const enter17AndClickButton = () => { + mockedComponent + .find('input') + .simulate('change', { target: { value: '17' } }); + mockedComponent.find('button').simulate('click'); + mockedComponent.update(); + return mockedComponent.find('button').text(); + }; + const userAge3 = enter3AndClickButton(); + const userAge17 = enter17AndClickButton(); + assert( + initialButton === 'Submit' && + userAge3 === 'You Shall Not Pass' && + userAge17 === 'You Shall Not Pass' + ); + })(); + " - text: When a number greater than or equal to 18 is entered into the input element and the button is clicked, the button's inner text should read You May Enter. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const initialButton = mockedComponent.find(''button'').text(); const enter18AndClickButton = () => { mockedComponent.find(''input'').simulate(''change'', {target: { value: ''18'' }}); mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find(''button'').text(); }); }; const enter35AndClickButton = () => { mockedComponent.find(''input'').simulate(''change'', {target: { value: ''35'' }}); mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find(''button'').text(); }); }; const userAge18 = await enter18AndClickButton(); const userAge35 = await enter35AndClickButton(); assert(initialButton === ''Submit'' && userAge18 === ''You May Enter'' && userAge35 === ''You May Enter''); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); + const initialButton = mockedComponent.find('button').text(); + const enter18AndClickButton = () => { + mockedComponent + .find('input') + .simulate('change', { target: { value: '18' } }); + mockedComponent.find('button').simulate('click'); + mockedComponent.update(); + return mockedComponent.find('button').text(); + }; + const enter35AndClickButton = () => { + mockedComponent + .find('input') + .simulate('change', { target: { value: '35' } }); + mockedComponent.find('button').simulate('click'); + mockedComponent.update(); + return mockedComponent.find('button').text(); + }; + const userAge18 = enter18AndClickButton(); + const userAge35 = enter35AndClickButton(); + assert( + initialButton === 'Submit' && + userAge18 === 'You May Enter' && + userAge35 === 'You May Enter' + ); + })(); + " - text: Once a number has been submitted, and the value of the input is once again changed, the button should return to reading Submit. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const enter18AndClickButton = () => { mockedComponent.find(''input'').simulate(''change'', {target: { value: ''18'' }}); mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find(''button'').text(); }); }; const changeInputDontClickButton = () => { mockedComponent.find(''input'').simulate(''change'', {target: { value: ''5'' }}); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find(''button'').text(); }); }; const enter10AndClickButton = () => { mockedComponent.find(''input'').simulate(''change'', {target: { value: ''10'' }}); mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find(''button'').text(); }); }; const userAge18 = await enter18AndClickButton(); const changeInput1 = await changeInputDontClickButton(); const userAge10 = await enter10AndClickButton(); const changeInput2 = await changeInputDontClickButton(); assert(userAge18 === ''You May Enter'' && changeInput1 === ''Submit'' && userAge10 === ''You Shall Not Pass'' && changeInput2 === ''Submit''); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); + const enter18AndClickButton = () => { + mockedComponent + .find('input') + .simulate('change', { target: { value: '18' } }); + mockedComponent.find('button').simulate('click'); + mockedComponent.update(); + return mockedComponent.find('button').text(); + }; + const changeInputDontClickButton = () => { + mockedComponent + .find('input') + .simulate('change', { target: { value: '5' } }); + mockedComponent.update(); + return mockedComponent.find('button').text(); + }; + const enter10AndClickButton = () => { + mockedComponent + .find('input') + .simulate('change', { target: { value: '10' } }); + mockedComponent.find('button').simulate('click'); + mockedComponent.update(); + return mockedComponent.find('button').text(); + }; + const userAge18 = enter18AndClickButton(); + const changeInput1 = changeInputDontClickButton(); + const userAge10 = enter10AndClickButton(); + const changeInput2 = changeInputDontClickButton(); + assert( + userAge18 === 'You May Enter' && + changeInput1 === 'Submit' && + userAge10 === 'You Shall Not Pass' && + changeInput2 === 'Submit' + ); + })(); + " - text: Your code should not contain any if/else statements. testString: assert(new RegExp(/(\s|;)if(\s|\()/).test(Enzyme.mount(React.createElement(CheckUserAge)).instance().render.toString()) === false); @@ -48,16 +141,16 @@ tests:
## Challenge Seed +
```jsx - const inputStyle = { width: 235, margin: 5 -} +}; class CheckUserAge extends React.Component { constructor(props) { @@ -88,26 +181,26 @@ class CheckUserAge extends React.Component {

Enter Your Age to Continue


- { - /* change code here */ - } + onChange={this.handleChange} + /> +
+ {/* change code here */}
); } -}; +} ``` - ### After Test +
```js -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')); ```
@@ -115,14 +208,14 @@ ReactDOM.render(, document.getElementById('root'))
## Solution -
+
```js const inputStyle = { width: 235, margin: 5 -} +}; class CheckUserAge extends React.Component { constructor(props) { @@ -130,7 +223,7 @@ class CheckUserAge extends React.Component { this.state = { userAge: '', input: '' - } + }; this.submit = this.submit.bind(this); this.handleChange = this.handleChange.bind(this); } @@ -154,20 +247,20 @@ class CheckUserAge extends React.Component {

Enter Your Age to Continue


- { - this.state.userAge === '' ? - buttonOne : - this.state.userAge >= 18 ? - buttonTwo : - buttonThree - } + onChange={this.handleChange} + /> +
+ {this.state.userAge === '' + ? buttonOne + : this.state.userAge >= 18 + ? buttonTwo + : buttonThree} ); } -}; +} ```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-advanced-javascript-in-react-render-method.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-advanced-javascript-in-react-render-method.english.md index a2caf192a2..d955402611 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/use-advanced-javascript-in-react-render-method.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/use-advanced-javascript-in-react-render-method.english.md @@ -8,17 +8,20 @@ forumTopicId: 301415 --- ## Description +
In previous challenges, you learned how to inject JavaScript code into JSX code using curly braces, { }, for tasks like accessing props, passing props, accessing state, inserting comments into your code, and most recently, styling your components. These are all common use cases to put JavaScript in JSX, but they aren't the only way that you can utilize JavaScript code in your React components. You can also write JavaScript directly in your render methods, before the return statement, without inserting it inside of curly braces. This is because it is not yet within the JSX code. When you want to use a variable later in the JSX code inside the return statement, you place the variable name inside curly braces.
## Instructions +
In the code provided, the render method has an array that contains 20 phrases to represent the answers found in the classic 1980's Magic Eight Ball toy. The button click event is bound to the ask method, so each time the button is clicked a random number will be generated and stored as the randomIndex in state. On line 52, delete the string "change me!" and reassign the answer const so your code randomly accesses a different index of the possibleAnswers array each time the component updates. Finally, insert the answer const inside the p tags.
## Tests +
```yml @@ -34,13 +37,89 @@ tests: - text: When MagicEightBall is first mounted to the DOM, it should return an empty p element. testString: assert(Enzyme.mount(React.createElement(MagicEightBall)).find('p').length === 1 && Enzyme.mount(React.createElement(MagicEightBall)).find('p').text() === ''); - text: When text is entered into the input element and the button is clicked, the MagicEightBall component should return a p element that contains a random element from the possibleAnswers array. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MagicEightBall)); const simulate = () => { comp.find(''input'').simulate(''change'', { target: { value: ''test?'' }}); comp.find(''button'').simulate(''click''); }; const result = () => comp.find(''p'').text(); const _1 = () => { simulate(); return waitForIt(() => result()) }; const _2 = () => { simulate(); return waitForIt(() => result()) }; const _3 = () => { simulate(); return waitForIt(() => result()) }; const _4 = () => { simulate(); return waitForIt(() => result()) }; const _5 = () => { simulate(); return waitForIt(() => result()) }; const _6 = () => { simulate(); return waitForIt(() => result()) }; const _7 = () => { simulate(); return waitForIt(() => result()) }; const _8 = () => { simulate(); return waitForIt(() => result()) }; const _9 = () => { simulate(); return waitForIt(() => result()) }; const _10 = () => { simulate(); return waitForIt(() => result()) }; const _1_val = await _1(); const _2_val = await _2(); const _3_val = await _3(); const _4_val = await _4(); const _5_val = await _5(); const _6_val = await _6(); const _7_val = await _7(); const _8_val = await _8(); const _9_val = await _9(); const _10_val = await _10(); const actualAnswers = [_1_val, _2_val, _3_val, _4_val, _5_val, _6_val, _7_val, _8_val, _9_val, _10_val]; const hasIndex = actualAnswers.filter((answer, i) => possibleAnswers.indexOf(answer) !== -1); const notAllEqual = new Set(actualAnswers); assert(notAllEqual.size > 1 && hasIndex.length === 10);}' + testString: "(() => { + const comp = Enzyme.mount(React.createElement(MagicEightBall)); + const simulate = () => { + comp.find('input').simulate('change', { target: { value: 'test?' } }); + comp.find('button').simulate('click'); + }; + const result = () => comp.find('p').text(); + const _1 = () => { + simulate(); + return result(); + }; + const _2 = () => { + simulate(); + return result(); + }; + const _3 = () => { + simulate(); + return result(); + }; + const _4 = () => { + simulate(); + return result(); + }; + const _5 = () => { + simulate(); + return result(); + }; + const _6 = () => { + simulate(); + return result(); + }; + const _7 = () => { + simulate(); + return result(); + }; + const _8 = () => { + simulate(); + return result(); + }; + const _9 = () => { + simulate(); + return result(); + }; + const _10 = () => { + simulate(); + return result(); + }; + const _1_val = _1(); + const _2_val = _2(); + const _3_val = _3(); + const _4_val = _4(); + const _5_val = _5(); + const _6_val = _6(); + const _7_val = _7(); + const _8_val = _8(); + const _9_val = _9(); + const _10_val = _10(); + const actualAnswers = [ + _1_val, + _2_val, + _3_val, + _4_val, + _5_val, + _6_val, + _7_val, + _8_val, + _9_val, + _10_val, + ]; + const hasIndex = actualAnswers.filter( + (answer, i) => possibleAnswers.indexOf(answer) !== -1 + ); + const notAllEqual = new Set(actualAnswers); + assert(notAllEqual.size > 1 && hasIndex.length === 10); + })(); + " ```
## Challenge Seed +
@@ -49,7 +128,7 @@ tests: const inputStyle = { width: 235, margin: 5 -} +}; class MagicEightBall extends React.Component { constructor(props) { @@ -57,7 +136,7 @@ class MagicEightBall extends React.Component { this.state = { userInput: '', randomIndex: '' - } + }; this.ask = this.ask.bind(this); this.handleChange = this.handleChange.bind(this); } @@ -90,45 +169,67 @@ class MagicEightBall extends React.Component { 'Better not tell you now', 'Cannot predict now', 'Concentrate and ask again', - 'Don\'t count on it', + "Don't count on it", 'My reply is no', 'My sources say no', 'Most likely', 'Outlook not so good', 'Very doubtful' ]; - const answer = 'change me!' // << change code here + const answer = 'change me!'; // << change code here return (

-
+ style={inputStyle} + /> +
+ +

Answer:

- { /* change code below this line */ } + {/* change code below this line */} - { /* change code above this line */ } + {/* change code above this line */}

); } -}; +} ```
- ### After Test +
```js -var possibleAnswers = [ 'It is certain', 'It is decidedly so', 'Without a doubt', 'Yes, definitely', 'You may rely on it', 'As I see it, yes', 'Outlook good', 'Yes', 'Signs point to yes', 'Reply hazy try again', 'Ask again later', 'Better not tell you now', 'Cannot predict now', 'Concentrate and ask again', 'Don\'t count on it', 'My reply is no', 'My sources say no', 'Outlook not so good','Very doubtful', 'Most likely' ]; -ReactDOM.render(, document.getElementById('root')) +var possibleAnswers = [ + 'It is certain', + 'It is decidedly so', + 'Without a doubt', + 'Yes, definitely', + 'You may rely on it', + 'As I see it, yes', + 'Outlook good', + 'Yes', + 'Signs point to yes', + 'Reply hazy try again', + 'Ask again later', + 'Better not tell you now', + 'Cannot predict now', + 'Concentrate and ask again', + "Don't count on it", + 'My reply is no', + 'My sources say no', + 'Outlook not so good', + 'Very doubtful', + 'Most likely' +]; +ReactDOM.render(, document.getElementById('root')); ```
@@ -136,14 +237,14 @@ ReactDOM.render(, document.getElementById('root'))
## Solution -
+
```js const inputStyle = { width: 235, margin: 5 -} +}; class MagicEightBall extends React.Component { constructor(props) { @@ -151,7 +252,7 @@ class MagicEightBall extends React.Component { this.state = { userInput: '', randomIndex: '' - } + }; this.ask = this.ask.bind(this); this.handleChange = this.handleChange.bind(this); } @@ -170,30 +271,45 @@ class MagicEightBall extends React.Component { } render() { const possibleAnswers = [ - "It is certain", "It is decidedly so", "Without a doubt", - "Yes, definitely", "You may rely on it", "As I see it, yes", - "Outlook good", "Yes", "Signs point to yes", "Reply hazy try again", - "Ask again later", "Better not tell you now", "Cannot predict now", - "Concentrate and ask again", "Don't count on it", "My reply is no", - "My sources say no", "Outlook not so good","Very doubtful", "Most likely" + 'It is certain', + 'It is decidedly so', + 'Without a doubt', + 'Yes, definitely', + 'You may rely on it', + 'As I see it, yes', + 'Outlook good', + 'Yes', + 'Signs point to yes', + 'Reply hazy try again', + 'Ask again later', + 'Better not tell you now', + 'Cannot predict now', + 'Concentrate and ask again', + "Don't count on it", + 'My reply is no', + 'My sources say no', + 'Outlook not so good', + 'Very doubtful', + 'Most likely' ]; const answer = possibleAnswers[this.state.randomIndex]; return (

-
+ style={inputStyle} + /> +
+ +

Answer:

-

- {answer} -

+

{answer}

); } -}; +} ```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-array.filter-to-dynamically-filter-an-array.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-array.filter-to-dynamically-filter-an-array.english.md index 15077ba701..787be00b93 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/use-array.filter-to-dynamically-filter-an-array.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/use-array.filter-to-dynamically-filter-an-array.english.md @@ -8,17 +8,20 @@ forumTopicId: 301416 --- ## Description +
The map array method is a powerful tool that you will use often when working with React. Another method related to map is filter, which filters the contents of an array based on a condition, then returns a new array. For example, if you have an array of users that all have a property online which can be set to true or false, you can filter only those users that are online by writing: let onlineUsers = users.filter(user => user.online);
## Instructions +
In the code editor, MyComponent's state is initialized with an array of users. Some users are online and some aren't. Filter the array so you see only the users who are online. To do this, first use filter to return a new array containing only the users whose online property is true. Then, in the renderOnline variable, map over the filtered array, and return a li element for each user that contains the text of their username. Be sure to include a unique key as well, like in the last challenges.
## Tests +
```yml @@ -28,9 +31,68 @@ tests: - text: MyComponent's state should be initialized to an array of six users.") testString: assert(Array.isArray(Enzyme.mount(React.createElement(MyComponent)).state('users')) === true && Enzyme.mount(React.createElement(MyComponent)).state('users').length === 6); - text: MyComponent should return a div, an h1, and then an unordered list containing li elements for every user whose online status is set to true. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MyComponent)); const users = (bool) => ({users:[ { username: ''Jeff'', online: bool }, { username: ''Alan'', online: bool }, { username: ''Mary'', online: bool }, { username: ''Jim'', online: bool }, { username: ''Laura'', online: bool } ]}); const result = () => comp.find(''li'').length; const _1 = result(); const _2 = () => { comp.setState(users(true)); return waitForIt(() => result()) }; const _3 = () => { comp.setState(users(false)); return waitForIt(() => result()) }; const _4 = () => { comp.setState({ users: [] }); return waitForIt(() => result()) }; const _2_val = await _2(); const _3_val = await _3(); const _4_val = await _4(); assert(comp.find(''div'').length === 1 && comp.find(''h1'').length === 1 && comp.find(''ul'').length === 1 && _1 === 4 && _2_val === 5 && _3_val === 0 && _4_val === 0); }; ' + testString: "(() => { + const comp = Enzyme.mount(React.createElement(MyComponent)); + const users = (bool) => ({ + users: [ + { username: 'Jeff', online: bool }, + { username: 'Alan', online: bool }, + { username: 'Mary', online: bool }, + { username: 'Jim', online: bool }, + { username: 'Laura', online: bool }, + ], + }); + const result = () => comp.find('li').length; + const _1 = result(); + const _2 = () => { + comp.setState(users(true)); + return result(); + }; + const _3 = () => { + comp.setState(users(false)); + return result(); + }; + const _4 = () => { + comp.setState({ users: [] }); + return result(); + }; + const _2_val = _2(); + const _3_val = _3(); + const _4_val = _4(); + assert( + comp.find('div').length === 1 && + comp.find('h1').length === 1 && + comp.find('ul').length === 1 && + _1 === 4 && + _2_val === 5 && + _3_val === 0 && + _4_val === 0 + ); + })(); + " - text: MyComponent should render li elements that contain the username of each online user. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MyComponent)); const users = (bool) => ({users:[ { username: ''Jeff'', online: bool }, { username: ''Alan'', online: bool }, { username: ''Mary'', online: bool }, { username: ''Jim'', online: bool }, { username: ''Laura'', online: bool } ]}); const ul = () => { comp.setState(users(true)); return waitForIt(() => comp.find(''ul'').html()) }; const html = await ul(); assert(html === ''
  • Jeff
  • Alan
  • Mary
  • Jim
  • Laura
''); }; ' + testString: "(() => { + const comp = Enzyme.mount(React.createElement(MyComponent)); + const users = (bool) => ({ + users: [ + { username: 'Jeff', online: bool }, + { username: 'Alan', online: bool }, + { username: 'Mary', online: bool }, + { username: 'Jim', online: bool }, + { username: 'Laura', online: bool }, + ], + }); + const ul = () => { + comp.setState(users(true)); + return comp.find('ul').html(); + }; + const html = ul(); + assert( + html === + '
  • Jeff
  • Alan
  • Mary
  • Jim
  • Laura
' + ); + })(); + " - text: Each list item element should have a unique key attribute. testString: assert((() => { const ul = Enzyme.mount(React.createElement(MyComponent)).find('ul'); console.log(ul.debug()); const keys = new Set([ ul.childAt(0).key(), ul.childAt(1).key(), ul.childAt(2).key(), ul.childAt(3).key() ]); return keys.size === 4; })()); @@ -39,6 +101,7 @@ tests:
## Challenge Seed +
@@ -74,31 +137,29 @@ class MyComponent extends React.Component { online: true } ] - } + }; } render() { const usersOnline = null; // change code here const renderOnline = null; // change code here return ( -
-

Current Online Users:

-
    - {renderOnline} -
-
+
+

Current Online Users:

+
    {renderOnline}
+
); } -}; +} ```
- ### After Test +
```js -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')); ```
@@ -106,8 +167,8 @@ ReactDOM.render(, document.getElementById('root'))
## Solution -
+
```js class MyComponent extends React.Component { @@ -140,27 +201,23 @@ class MyComponent extends React.Component { online: true } ] - } + }; } render() { const usersOnline = this.state.users.filter(user => { return user.online; }); const renderOnline = usersOnline.map(user => { - return ( -
  • {user.username}
  • - ); + return
  • {user.username}
  • ; }); return ( -
    -

    Current Online Users:

    -
      - {renderOnline} -
    -
    +
    +

    Current Online Users:

    +
      {renderOnline}
    +
    ); } -}; +} ```
    diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-array.map-to-dynamically-render-elements.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-array.map-to-dynamically-render-elements.english.md index 39572520ff..97ea81abf9 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/use-array.map-to-dynamically-render-elements.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/use-array.map-to-dynamically-render-elements.english.md @@ -8,12 +8,14 @@ forumTopicId: 301417 --- ## Description +
    Conditional rendering is useful, but you may need your components to render an unknown number of elements. Often in reactive programming, a programmer has no way to know what the state of an application is until runtime, because so much depends on a user's interaction with that program. Programmers need to write their code to correctly handle that unknown state ahead of time. Using Array.map() in React illustrates this concept. For example, you create a simple "To Do List" app. As the programmer, you have no way of knowing how many items a user might have on their list. You need to set up your component to dynamically render the correct number of list elements long before someone using the program decides that today is laundry day.
    ## Instructions +
    The code editor has most of the MyToDoList component set up. Some of this code should look familiar if you completed the controlled form challenge. You'll notice a textarea and a button, along with a couple of methods that track their states, but nothing is rendered to the page yet. Inside the constructor, create a this.state object and define two states: userInput should be initialized as an empty string, and toDoList should be initialized as an empty array. Next, delete the comment in the render() method next to the items variable. In its place, map over the toDoList array stored in the component's internal state and dynamically render a li for each item. Try entering the string eat, code, sleep, repeat into the textarea, then click the button and see what happens. @@ -21,6 +23,7 @@ Inside the constructor, create a this.state object and
    ## Tests +
    ```yml @@ -38,13 +41,62 @@ tests: - text: The state of MyToDoList should be initialized with userInput as an empty string. testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(MyToDoList)); const initialState = mockedComponent.state(); return typeof initialState.userInput === 'string' && initialState.userInput.length === 0; })()); - text: When the Create List button is clicked, the MyToDoList component should dynamically return an unordered list that contains a list item element for every item of a comma-separated list entered into the textarea element. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const mockedComponent = Enzyme.mount(React.createElement(MyToDoList)); const simulateChange = (el, value) => el.simulate(''change'', {target: {value}}); const state_1 = () => { return waitForIt(() => mockedComponent.find(''ul'').find(''li''))}; const setInput = () => { return waitForIt(() => simulateChange(mockedComponent.find(''textarea''), "testA, testB, testC"))}; const click = () => { return waitForIt(() => mockedComponent.find(''button'').simulate(''click''))}; const state_2 = () => { return waitForIt(() => { const nodes = mockedComponent.find(''ul'').find(''li''); return { nodes, text: nodes.reduce((t, n) => t + n.text(), '''') }; })}; const setInput_2 = () => { return waitForIt(() => simulateChange(mockedComponent.find(''textarea''), "t1, t2, t3, t4, t5, t6"))}; const click_1 = () => { return waitForIt(() => mockedComponent.find(''button'').simulate(''click''))}; const state_3 = () => { return waitForIt(() => { const nodes = mockedComponent.find(''ul'').find(''li''); return { nodes, text: nodes.reduce((t, n) => t + n.text(), '''') }; })}; const awaited_state_1 = await state_1(); const awaited_setInput = await setInput(); const awaited_click = await click(); const awaited_state_2 = await state_2(); const awaited_setInput_2 = await setInput_2(); const awaited_click_1 = await click_1(); const awaited_state_3 = await state_3(); assert(awaited_state_1.length === 0 && awaited_state_2.nodes.length === 3 && awaited_state_3.nodes.length === 6 && awaited_state_2.text === ''testA testB testC'' && awaited_state_3.text === ''t1 t2 t3 t4 t5 t6''); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(MyToDoList)); + const simulateChange = (el, value) => + el.simulate('change', { target: { value } }); + const state_1 = () => { + return mockedComponent.find('ul').find('li'); + }; + const setInput = () => { + return simulateChange( + mockedComponent.find('textarea'), + 'testA, testB, testC' + ); + }; + const click = () => { + return mockedComponent.find('button').simulate('click'); + }; + const state_2 = () => { + const nodes = mockedComponent.find('ul').find('li'); + return { nodes, text: nodes.reduce((t, n) => t + n.text(), '') }; + }; + const setInput_2 = () => { + return simulateChange( + mockedComponent.find('textarea'), + 't1, t2, t3, t4, t5, t6' + ); + }; + const click_1 = () => { + return mockedComponent.find('button').simulate('click'); + }; + const state_3 = () => { + const nodes = mockedComponent.find('ul').find('li'); + return { nodes, text: nodes.reduce((t, n) => t + n.text(), '') }; + }; + const awaited_state_1 = state_1(); + const awaited_setInput = setInput(); + const awaited_click = click(); + const awaited_state_2 = state_2(); + const awaited_setInput_2 = setInput_2(); + const awaited_click_1 = click_1(); + const awaited_state_3 = state_3(); + assert( + awaited_state_1.length === 0 && + awaited_state_2.nodes.length === 3 && + awaited_state_3.nodes.length === 6 && + awaited_state_2.text === 'testA testB testC' && + awaited_state_3.text === 't1 t2 t3 t4 t5 t6' + ); + })(); + " ```
    ## Challenge Seed +
    @@ -83,26 +135,26 @@ class MyToDoList extends React.Component { onChange={this.handleChange} value={this.state.userInput} style={textAreaStyles} - placeholder="Separate Items With Commas" /> + placeholder='Separate Items With Commas' + />

    My "To Do" List:

    -
      - {items} -
    +
      {items}
    ); } -}; +} ``` ### After Test +
    ```js -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')); ```
    @@ -110,8 +162,8 @@ ReactDOM.render(, document.getElementById('root'))
    ## Solution -
    +
    ```js const textAreaStyles = { @@ -125,7 +177,7 @@ class MyToDoList extends React.Component { this.state = { toDoList: [], userInput: '' - } + }; this.handleSubmit = this.handleSubmit.bind(this); this.handleChange = this.handleChange.bind(this); } @@ -141,8 +193,8 @@ class MyToDoList extends React.Component { }); } render() { - const items = this.state.toDoList.map( (item, i) => { - return
  • {item}
  • + const items = this.state.toDoList.map((item, i) => { + return
  • {item}
  • ; }); return (
    @@ -150,16 +202,16 @@ class MyToDoList extends React.Component { onChange={this.handleChange} value={this.state.userInput} style={textAreaStyles} - placeholder="Separate Items With Commas" />
    + placeholder='Separate Items With Commas' + /> +

    My "To Do" List:

    -
      - {items} -
    +
      {items}
    ); } -}; +} ```
    diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md index 60499a9554..f4a55dfcf6 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md @@ -8,8 +8,9 @@ forumTopicId: 301421 --- ## Description +
    -Sometimes you might need to know the previous state when updating the state. However, state updates may be asynchronous - this means React may batch multiple setState() calls into a single update. This means you can't rely on the previous value of this.state or this.props when calculating the next value. So, you should not use code like this: +Sometimes you might need to know the previous state when updating the state. However, state updates may be asynchronous - this means React may batch multiple setState() calls into a single update. This means you can't rely on the previous value of this.state or this.props when calculating the next value. So, you should not use code like this: ```js this.setState({ @@ -17,7 +18,7 @@ this.setState({ }); ``` -Instead, you should pass setState a function that allows you to access state and props. Using a function with setState guarantees you are working with the most current values of state and props. This means that the above should be rewritten as: +Instead, you should pass setState a function that allows you to access state and props. Using a function with setState guarantees you are working with the most current values of state and props. This means that the above should be rewritten as: ```js this.setState((state, props) => ({ @@ -34,9 +35,11 @@ this.setState(state => ({ ``` Note that you have to wrap the object literal in parentheses, otherwise JavaScript thinks it's a block of code. +
    ## Instructions +
    MyComponent has a visibility property which is initialized to false. The render method returns one view if the value of visibility is true, and a different view if it is false. Currently, there is no way of updating the visibility property in the component's state. The value should toggle back and forth between true and false. There is a click handler on the button which triggers a class method called toggleVisibility(). Pass a function to setState to define this method so that the state of visibility toggles to the opposite value when the method is called. If visibility is false, the method sets it to true, and vice versa. @@ -45,6 +48,7 @@ Finally, click the button to see the conditional rendering of the component base
    ## Tests +
    ```yml @@ -54,17 +58,35 @@ tests: - text: The state of MyComponent should initialize with a visibility property set to false. testString: assert.strictEqual(Enzyme.mount(React.createElement(MyComponent)).state('visibility'), false); - text: Clicking the button element should toggle the visibility property in state between true and false. - testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ visibility: false }); return waitForIt(() => mockedComponent.state(''visibility'')); }; const second = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent.state(''visibility'')); }; const third = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent.state(''visibility'')); }; const firstValue = await first(); const secondValue = await second(); const thirdValue = await third(); assert(!firstValue && secondValue && !thirdValue); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); + const first = () => { + mockedComponent.setState({ visibility: false }); + return mockedComponent.state('visibility'); + }; + const second = () => { + mockedComponent.find('button').simulate('click'); + return mockedComponent.state('visibility'); + }; + const third = () => { + mockedComponent.find('button').simulate('click'); + return mockedComponent.state('visibility'); + }; + const firstValue = first(); + const secondValue = second(); + const thirdValue = third(); + assert(!firstValue && secondValue && !thirdValue); + })();" - text: An anonymous function should be passed to setState. testString: const paramRegex = '[a-zA-Z$_]\\w*(,[a-zA-Z$_]\\w*)?'; const noSpaces = code.replace(/\s/g, ''); assert(new RegExp('this\\.setState\\((function\\(' + paramRegex + '\\){|([a-zA-Z$_]\\w*|\\(' + paramRegex + '\\))=>)').test(noSpaces)); - text: this should not be used inside setState testString: assert(!/this\.setState\([^}]*this/.test(code)); - ```
    ## Challenge Seed +
    @@ -99,17 +121,17 @@ class MyComponent extends React.Component { ); } } -}; +} ```
    - ### After Test +
    ```js -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')); ```
    @@ -117,8 +139,8 @@ ReactDOM.render(, document.getElementById('root'))
    ## Solution -
    +
    ```js class MyComponent extends React.Component { @@ -128,7 +150,7 @@ class MyComponent extends React.Component { visibility: false }; this.toggleVisibility = this.toggleVisibility.bind(this); - } + } toggleVisibility() { this.setState(state => ({ visibility: !state.visibility @@ -138,19 +160,19 @@ class MyComponent extends React.Component { if (this.state.visibility) { return (
    - +

    Now you see me!

    ); } else { return (
    - +
    ); } } -}; +} ```
    diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-the-lifecycle-method-componentdidmount.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-the-lifecycle-method-componentdidmount.english.md index 6811e6f183..ec4ce76edd 100644 --- a/curriculum/challenges/english/03-front-end-libraries/react/use-the-lifecycle-method-componentdidmount.english.md +++ b/curriculum/challenges/english/03-front-end-libraries/react/use-the-lifecycle-method-componentdidmount.english.md @@ -8,17 +8,20 @@ forumTopicId: 301422 --- ## Description +
    Most web developers, at some point, need to call an API endpoint to retrieve data. If you're working with React, it's important to know where to perform this action. The best practice with React is to place API calls or any calls to your server in the lifecycle method componentDidMount(). This method is called after a component is mounted to the DOM. Any calls to setState() here will trigger a re-rendering of your component. When you call an API in this method, and set your state with the data that the API returns, it will automatically trigger an update once you receive the data.
    ## Instructions +
    There is a mock API call in componentDidMount(). It sets state after 2.5 seconds to simulate calling a server to retrieve data. This example requests the current total active users for a site. In the render method, render the value of activeUsers in the h1. Watch what happens in the preview, and feel free to change the timeout to see the different effects.
    ## Tests +
    ```yml @@ -28,13 +31,25 @@ tests: - text: Component state should be updated with a timeout function in componentDidMount. testString: assert((() => { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return new RegExp('setTimeout(.|\n)+setState(.|\n)+activeUsers').test(String(mockedComponent.instance().componentDidMount)); })()); - text: The h1 tag should render the activeUsers value from MyComponent's state. - testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ activeUsers: 1237 }); return mockedComponent.find(''h1'').text(); }; const second = () => { mockedComponent.setState({ activeUsers: 1000 }); return mockedComponent.find(''h1'').text(); }; assert(new RegExp(''1237'').test(first()) && new RegExp(''1000'').test(second())); }; ' + testString: "(() => { + const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); + const first = () => { + mockedComponent.setState({ activeUsers: 1237 }); + return mockedComponent.find('h1').text(); + }; + const second = () => { + mockedComponent.setState({ activeUsers: 1000 }); + return mockedComponent.find('h1').text(); + }; + assert(new RegExp('1237').test(first()) && new RegExp('1000').test(second())); + })();" ```
    ## Challenge Seed +
    @@ -48,7 +63,7 @@ class MyComponent extends React.Component { }; } componentDidMount() { - setTimeout( () => { + setTimeout(() => { this.setState({ activeUsers: 1273 }); @@ -57,21 +72,21 @@ class MyComponent extends React.Component { render() { return (
    -

    Active Users: { /* change code here */ }

    +

    Active Users: {/* change code here */}

    ); } -}; +} ```
    - ### After Test +
    ```js -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')); ```
    @@ -79,8 +94,8 @@ ReactDOM.render(, document.getElementById('root'))
    ## Solution -
    +
    ```js class MyComponent extends React.Component { @@ -91,7 +106,7 @@ class MyComponent extends React.Component { }; } componentDidMount() { - setTimeout( () => { + setTimeout(() => { this.setState({ activeUsers: 1273 }); @@ -104,7 +119,7 @@ class MyComponent extends React.Component { ); } -}; +} ```