input
and textarea
, which makes them controlled components. This applies to other form elements as well, including the regular HTML form
element.
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
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');
+ })();
+ "
```
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.
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.
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 === ''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');
+ })();
+ "
```
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.
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
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);
+ })();
+ "
```
{'Turn: ' + this.state.counter}
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;
```
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
.
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:
{ }
, 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.
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.
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);
+ })();
+ "
```
- { /* change code below this line */ } + {/* change code below this line */} - { /* change code above this line */ } + {/* change code above this line */}
- {answer} -
+{answer}
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);
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.
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 === ''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:
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.
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
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'
+ );
+ })();
+ "
```
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.
+
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
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));
-
```
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.
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.
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()));
+ })();"
```