5

I'd like to be able to increase opacity of a DOM element on click of a button.

Wrapping my head around React's concepts, I think this should be done through using state and a functional setState() function.

However, I keep running into an error with the following code:

import React, { Component } from 'react'

class ThoughtfulThoughts extends Component {

  state = {
    opacity: 0.4,
    thoughts:
      [
        "live and let leave",
        "just pee yourself",
        "who knows what 'laf' is?"
      ]
  }

  makeAppear = () => {
    // TODO: this causes a "RangeError: Maximum call stack size exceeded"
    this.setState(prevState => ({
      opacity: prevState.opacity + 0.2
    }))
  }

  render() {
    return (
      <div className="thoughtful-thoughts">
        <div className="current-thought" style={{opacity: this.state.opacity}}>
          {this.state.thoughts.map((thought, i) => (<p>{thought}</p>))}
        </div>
        <button onClick={this.makeAppear()}>Think for me</button>
      </div>
    )

  }
}

export default ThoughtfulThoughts

I don't want to use jQuery, nor direct DOM manipulation, nor CSS transitions, but would like to do this just in JS in "the React way".

Any pointers would be greatly appreciated!

martin-martin
  • 3,274
  • 1
  • 33
  • 60
  • 6
    Change `onClick={this.makeAppear()}` into `onClick={this.makeAppear}` – Chris Sep 08 '17 at 10:59
  • 3
    ^ What Chris said. To expand upon it, the reason your stack size is exceeded is because every time you render, you're calling that function `onClick={this.makeAppear()}`. And that function changes the state, which causes a re-render. This re-render calls that function, which changes the state--and so on. It's an infinite loop. – Jayce444 Sep 08 '17 at 11:01
  • 1
    also you need to wrap `{ opacity: prevState.opacity + 0.2 }` by `()` otherwise use return. – Mayank Shukla Sep 08 '17 at 11:05
  • Possible duplicate of [Maximum call stack size exceeded - Connected React Component](https://stackoverflow.com/questions/45823050/maximum-call-stack-size-exceeded-connected-react-component) – Mayank Shukla Sep 08 '17 at 11:09

1 Answers1

2

You have a couple of minor issues in your code:

  1. On your button component, change onClick={this.makeAppear()} into onClick={this.makeAppear}.

  2. Nicely done on using the function approach instead of the object approach for updating a state variable based on a previous state variable. However, you have a minor syntax. Either do:

    this.setState(prevState => (
      {opacity: prevState.opacity + 0.2}
    ));
    

    or

    this.setState(prevState => {
      return {opacity: prevState.opacity + 0.2}
    });
    

    whichever you prefer.

  3. Add a key property to each item you return from your map(): So basically:

    {this.state.thoughts.map((thought, i) => (<p key={i}>{thought}</p>))}
    

    You can probably use i safely as the key here because the order of the items in thoughts array will remain static. For more info about keys and how to use them properly, take a look here.


Demo:

class ThoughtfulThoughts extends React.Component {
  constructor() {
    super();
    this.state = {
      opacity: 0.4,
      thoughts:
        [
          "live and let leave",
          "just pee yourself",
          "who knows what 'laf' is?"
        ]
    }
  }

  makeAppear = () => {
    this.setState(
      prevState => ({opacity: prevState.opacity + 0.2})
    );
  }

  render() {
    return (
      <div className="thoughtful-thoughts">
        <div className="current-thought" style={{opacity: this.state.opacity}}>
          {this.state.thoughts.map((thought, i) => <p key={i}>{thought}</p>)}
        </div>
        <button onClick={this.makeAppear}>Think for me</button>
      </div>
    );
  }
}

ReactDOM.render(<ThoughtfulThoughts />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Chris
  • 57,622
  • 19
  • 111
  • 137