freeCodeCamp/curriculum/challenges/english/03-front-end-libraries/react/use-state-to-toggle-an-element.english.md
Oliver Eyton-Williams bd68b70f3d
Feat: hide blocks not challenges (#39504)
* fix: remove isHidden flag from frontmatter

* fix: add isUpcomingChange

Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>

* feat: hide blocks not challenges

Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>

Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>
2020-09-03 15:07:40 -07:00

5.6 KiB

id, title, challengeType, isRequired, forumTopicId
id title challengeType isRequired forumTopicId
5a24c314108439a4d4036176 Use State to Toggle an Element 6 false 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:
this.setState({
  counter: this.state.counter + this.props.increment
});

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:

this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

You can also use a form without props if you need only the state:

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.

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. Finally, click the button to see the conditional rendering of the component based on its state. Hint: Don't forget to bind the this keyword to the method in the constructor!

Tests

tests:
  - text: <code>MyComponent</code> should return a <code>div</code> element which contains a <code>button</code>.
    testString: assert.strictEqual(Enzyme.mount(React.createElement(MyComponent)).find('div').find('button').length, 1);
  - text: The state of <code>MyComponent</code> should initialize with a <code>visibility</code> property set to <code>false</code>.
    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>.
    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 <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));

Challenge Seed

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      visibility: false
    };
    // change code below this line

    // change code above this line
  }
  // change code below this line

  // change code above this line
  render() {
    if (this.state.visibility) {
      return (
        <div>
          <button onClick={this.toggleVisibility}>Click Me</button>
          <h1>Now you see me!</h1>
        </div>
      );
    } else {
      return (
        <div>
          <button onClick={this.toggleVisibility}>Click Me</button>
        </div>
      );
    }
  }
}

After Test

ReactDOM.render(<MyComponent />, document.getElementById('root'));

Solution

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      visibility: false
    };
    this.toggleVisibility = this.toggleVisibility.bind(this);
  }
  toggleVisibility() {
    this.setState(state => ({
      visibility: !state.visibility
    }));
  }
  render() {
    if (this.state.visibility) {
      return (
        <div>
          <button onClick={this.toggleVisibility}>Click Me</button>
          <h1>Now you see me!</h1>
        </div>
      );
    } else {
      return (
        <div>
          <button onClick={this.toggleVisibility}>Click Me</button>
        </div>
      );
    }
  }
}