fix(curriculum): use function form of this.setState (#36623)
* Fix solution * Update description * Add test for this.setState function syntax * Update toggle element instructions * Fix simple-counter solutions * Fix bind-this challenge seed and solution * Fix && challenge seed and solution * Fix ternary challenge seed and solution * Use the function form -> Pass a function Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * Mention "passing a function" instead of "alternative form" Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * Rephrase explanation Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * clarify why functions should be used Co-Authored-By: Randell Dawson <5313213+RandellDawson@users.noreply.github.com> * Improve tests * Fix \s escaping in [] * Improve regex Co-Authored-By: Randell Dawson <5313213+RandellDawson@users.noreply.github.com> * Add test for `this` * Use block code * Update bind-this challenge * Fix last part of instructions Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -15,9 +15,9 @@ One common way is to explicitly bind <code>this</code> in the constructor so <co
|
|||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
<section id='instructions'>
|
<section id='instructions'>
|
||||||
The code editor has a component with a <code>state</code> that keeps track of an item count. It also has a method which allows you to increment this item count. However, the method doesn't work because it's using the <code>this</code> keyword that is undefined. Fix it by explicitly binding <code>this</code> to the <code>addItem()</code> method in the component's constructor.
|
The code editor has a component with a <code>state</code> that keeps track of the text. It also has a method which allows you to set the text to <code>"You clicked!"</code>. However, the method doesn't work because it's using the <code>this</code> keyword that is undefined. Fix it by explicitly binding <code>this</code> to the <code>handleClick()</code> method in the component's constructor.
|
||||||
Next, add a click handler to the <code>button</code> element in the render method. It should trigger the <code>addItem()</code> method when the button receives a click event. Remember that the method you pass to the <code>onClick</code> handler needs curly braces because it should be interpreted directly as JavaScript.
|
Next, add a click handler to the <code>button</code> element in the render method. It should trigger the <code>handleClick()</code> method when the button receives a click event. Remember that the method you pass to the <code>onClick</code> handler needs curly braces because it should be interpreted directly as JavaScript.
|
||||||
Once you complete the above steps you should be able to click the button and see the item count increment in the HTML.
|
Once you complete the above steps you should be able to click the button and see <code>You clicked!</code>.
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
@ -27,10 +27,10 @@ Once you complete the above steps you should be able to click the button and see
|
|||||||
tests:
|
tests:
|
||||||
- text: <code>MyComponent</code> should return a <code>div</code> element which wraps two elements, a button and an <code>h1</code> element, in that order.
|
- text: <code>MyComponent</code> should return a <code>div</code> element which wraps two elements, a button and an <code>h1</code> element, in that order.
|
||||||
testString: assert(Enzyme.mount(React.createElement(MyComponent)).find('div').length === 1 && Enzyme.mount(React.createElement(MyComponent)).find('div').childAt(0).type() === 'button' && Enzyme.mount(React.createElement(MyComponent)).find('div').childAt(1).type() === 'h1');
|
testString: assert(Enzyme.mount(React.createElement(MyComponent)).find('div').length === 1 && Enzyme.mount(React.createElement(MyComponent)).find('div').childAt(0).type() === 'button' && Enzyme.mount(React.createElement(MyComponent)).find('div').childAt(1).type() === 'h1');
|
||||||
- text: 'The state of <code>MyComponent</code> should initialize with the key value pair <code>{ itemCount: 0 }</code>.'
|
- text: 'The state of <code>MyComponent</code> should initialize with the key value pair <code>{ text: "Hello" }</code>.'
|
||||||
testString: 'assert(Enzyme.mount(React.createElement(MyComponent)).state(''itemCount'') === 0);'
|
testString: 'assert(Enzyme.mount(React.createElement(MyComponent)).state(''text'') === ''Hello'');'
|
||||||
- text: Clicking the <code>button</code> element should run the <code>addItem</code> method and increment the state <code>itemCount</code> by <code>1</code>.
|
- text: Clicking the <code>button</code> element should run the <code>handleClick</code> method and set the state <code>text</code> to <code>"You clicked!"</code>.
|
||||||
testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ itemCount: 0 }); return waitForIt(() => mockedComponent.state(''itemCount'')); }; const second = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent.state(''itemCount'')); }; const firstValue = await first(); const secondValue = await second(); assert(firstValue === 0 && secondValue === 1); };'
|
testString: 'async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ text: ''Hello'' }); return waitForIt(() => mockedComponent.state(''text'')); }; const second = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent.state(''text'')); }; const firstValue = await first(); const secondValue = await second(); assert(firstValue === ''Hello'' && secondValue === ''You clicked!''); };'
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -46,15 +46,15 @@ class MyComponent extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
itemCount: 0
|
text: "Hello"
|
||||||
};
|
};
|
||||||
// change code below this line
|
// change code below this line
|
||||||
|
|
||||||
// change code above this line
|
// change code above this line
|
||||||
}
|
}
|
||||||
addItem() {
|
handleClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
itemCount: this.state.itemCount + 1
|
text: "You clicked!"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
@ -63,7 +63,7 @@ class MyComponent extends React.Component {
|
|||||||
{ /* change code below this line */ }
|
{ /* change code below this line */ }
|
||||||
<button>Click Me</button>
|
<button>Click Me</button>
|
||||||
{ /* change code above this line */ }
|
{ /* change code above this line */ }
|
||||||
<h1>Current Item Count: {this.state.itemCount}</h1>
|
<h1>{this.state.text}</h1>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -93,20 +93,20 @@ class MyComponent extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
itemCount: 0
|
text: "Hello"
|
||||||
};
|
};
|
||||||
this.addItem = this.addItem.bind(this);
|
this.handleClick = this.handleClick.bind(this);
|
||||||
}
|
}
|
||||||
addItem() {
|
handleClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
itemCount: this.state.itemCount + 1
|
text: "You clicked!"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button onClick = {this.addItem}>Click Me</button>
|
<button onClick = {this.handleClick}>Click Me</button>
|
||||||
<h1>Current Item Count: {this.state.itemCount}</h1>
|
<h1>{this.state.text}</h1>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,9 +51,9 @@ class MyComponent extends React.Component {
|
|||||||
this.toggleDisplay = this.toggleDisplay.bind(this);
|
this.toggleDisplay = this.toggleDisplay.bind(this);
|
||||||
}
|
}
|
||||||
toggleDisplay() {
|
toggleDisplay() {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
display: !this.state.display
|
display: !state.display
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
// change code below this line
|
// change code below this line
|
||||||
@ -95,9 +95,9 @@ class MyComponent extends React.Component {
|
|||||||
this.toggleDisplay = this.toggleDisplay.bind(this);
|
this.toggleDisplay = this.toggleDisplay.bind(this);
|
||||||
}
|
}
|
||||||
toggleDisplay() {
|
toggleDisplay() {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
display: !this.state.display
|
display: !state.display
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
// change code below this line
|
// change code below this line
|
||||||
|
@ -74,9 +74,9 @@ class CheckUserAge extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
submit() {
|
submit() {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
userAge: this.state.input
|
userAge: state.input
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const buttonOne = <button onClick={this.submit}>Submit</button>;
|
const buttonOne = <button onClick={this.submit}>Submit</button>;
|
||||||
@ -140,9 +140,9 @@ class CheckUserAge extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
submit() {
|
submit() {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
userAge: this.state.input
|
userAge: state.input
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const buttonOne = <button onClick={this.submit}>Submit</button>;
|
const buttonOne = <button onClick={this.submit}>Submit</button>;
|
||||||
|
@ -8,13 +8,37 @@ forumTopicId: 301421
|
|||||||
|
|
||||||
## Description
|
## Description
|
||||||
<section id='description'>
|
<section id='description'>
|
||||||
You can use <code>state</code> in React applications in more complex ways than what you've seen so far. One example is to monitor the status of a value, then render the UI conditionally based on this value. There are several different ways to accomplish this, and the code editor shows one method.
|
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 <code>setState()</code> calls into a single update. This means you can't rely on the previous value of <code>this.state</code> or <code>this.props</code> when calculating the next value. So, you should not use code like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
this.setState({
|
||||||
|
counter: this.state.counter + this.props.increment
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead, you should pass <code>setState</code> a function that allows you to access state and props. Using a function with <code>setState</code> 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) => ({
|
||||||
|
counter: state.counter + props.increment
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use a form without `props` if you need only the `state`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
this.setState(state => ({
|
||||||
|
counter: state.counter + 1
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that you have to wrap the object literal in parentheses, otherwise JavaScript thinks it's a block of code.
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
<section id='instructions'>
|
<section id='instructions'>
|
||||||
<code>MyComponent</code> has a <code>visibility</code> property which is initialized to <code>false</code>. The render method returns one view if the value of <code>visibility</code> is true, and a different view if it is false.
|
<code>MyComponent</code> has a <code>visibility</code> property which is initialized to <code>false</code>. The render method returns one view if the value of <code>visibility</code> is true, and a different view if it is false.
|
||||||
Currently, there is no way of updating the <code>visibility</code> property in the component's <code>state</code>. 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 <code>toggleVisibility()</code>. Define this method so the <code>state</code> of <code>visibility</code> toggles to the opposite value when the method is called. If <code>visibility</code> is <code>false</code>, the method sets it to <code>true</code>, and vice versa.
|
Currently, there is no way of updating the <code>visibility</code> property in the component's <code>state</code>. 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 <code>toggleVisibility()</code>. Pass a function to <code>setState</code> to define this method so that the <code>state</code> of <code>visibility</code> toggles to the opposite value when the method is called. If <code>visibility</code> is <code>false</code>, the method sets it to <code>true</code>, and vice versa.
|
||||||
Finally, click the button to see the conditional rendering of the component based on its <code>state</code>.
|
Finally, click the button to see the conditional rendering of the component based on its <code>state</code>.
|
||||||
<strong>Hint:</strong> Don't forget to bind the <code>this</code> keyword to the method in the constructor!
|
<strong>Hint:</strong> Don't forget to bind the <code>this</code> keyword to the method in the constructor!
|
||||||
</section>
|
</section>
|
||||||
@ -30,6 +54,10 @@ tests:
|
|||||||
testString: assert.strictEqual(Enzyme.mount(React.createElement(MyComponent)).state('visibility'), false);
|
testString: assert.strictEqual(Enzyme.mount(React.createElement(MyComponent)).state('visibility'), false);
|
||||||
- text: Clicking the button element should toggle the <code>visibility</code> property in state between <code>true</code> and <code>false</code>.
|
- text: Clicking the button element should toggle the <code>visibility</code> property in state between <code>true</code> and <code>false</code>.
|
||||||
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: '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); }; '
|
||||||
|
- text: An anonymous function should be passed to <code>setState</code>.
|
||||||
|
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: <code>this</code> should not be used inside <code>setState</code>
|
||||||
|
testString: assert(!/this\.setState\([^}]*this/.test(code));
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -101,9 +129,9 @@ class MyComponent extends React.Component {
|
|||||||
this.toggleVisibility = this.toggleVisibility.bind(this);
|
this.toggleVisibility = this.toggleVisibility.bind(this);
|
||||||
}
|
}
|
||||||
toggleVisibility() {
|
toggleVisibility() {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
visibility: !this.state.visibility
|
visibility: !state.visibility
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
if (this.state.visibility) {
|
if (this.state.visibility) {
|
||||||
|
@ -104,14 +104,14 @@ class Counter extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
increment() {
|
increment() {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
count: this.state.count + 1
|
count: state.count + 1
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
decrement() {
|
decrement() {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
count: this.state.count - 1
|
count: state.count - 1
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
Reference in New Issue
Block a user