最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React - toggle in stateless component - Stack Overflow

programmeradmin5浏览0评论

I am trying to toggle visiblity of a div in a stateless component like this:

    const playerInfo = (props) => {
      let isPanelOpen = false;
      return (
          <div onClick={() => isPanelOpen = !isPanelOpen }>Toggle</div>
          {isPanelOpen && <div className="info-panel">
            {props.children}
          </div>}
      );
    };

I see that the value of isPanelOpen changes to true, but the panel is not being shown. I assume that is because this is the stateless function that doesn't get called again, so once we return the jsx it will have the value of false, and won't update it later. Is there a way of fixing this, and avoiding of pasing this single variable as props through 4 more parent stateless components?

I am trying to toggle visiblity of a div in a stateless component like this:

    const playerInfo = (props) => {
      let isPanelOpen = false;
      return (
          <div onClick={() => isPanelOpen = !isPanelOpen }>Toggle</div>
          {isPanelOpen && <div className="info-panel">
            {props.children}
          </div>}
      );
    };

I see that the value of isPanelOpen changes to true, but the panel is not being shown. I assume that is because this is the stateless function that doesn't get called again, so once we return the jsx it will have the value of false, and won't update it later. Is there a way of fixing this, and avoiding of pasing this single variable as props through 4 more parent stateless components?

Share Improve this question edited Sep 25, 2019 at 17:09 haz111 7641 gold badge6 silver badges17 bronze badges asked Mar 19, 2018 at 16:07 LeffLeff 1,32031 gold badges111 silver badges224 bronze badges
Add a comment  | 

6 Answers 6

Reset to default 9

You can't tell React to re-render the UI by assigning new value directly to the variable (in your case you did isPanelOpen = !isPanelOpen).
The correct method is to use setState.

But you cannot do it in a stateless component, you must do it in a stateful component, so your code should looks like this

import React, {Component} from 'react';
class playerInfo extends Component {
    constructor(props){
        super(props);
        this.state = {
            isPanelOpen: false
        }
    }
    render() {
        return (
            <div onClick={() => this.setState({isPanelOpen: !this.state.isPanelOpen})}>Toggle</div>
            {this.state.isPanelOpen && <div className="info-panel">
              {this.props.children}
          </div>}
        );
    }
}

Explanation

Remember two things:
1) Your UI should only bind to this.state.XXXX (for stateful component) or props.XXX (for stateless component).
2) The only way to update UI is by calling setState() method, no other way will trigger React to re-render the UI.

But... how do I update stateless component since it doesn't have the setState method?

ONE ANSWER:The stateless component should be contained in another stateful component.

Example

Let's say your stateless component is called Kid, and you have another stateful component called Mum.

import React, {Component} from 'react';
class Mum extends Component {
    constructor(props){
        super(props);
        this.state = {
            isHappy: false
        }
    }
    render() {
        return (
           <div>
                <button onClick={() => this.setState({isHappy: true})}>Eat</button>
                <Kid isHappy={this.state.isHappy}/>
           </div>
        );
    }
}

const Kid = (props) => (props.isHappy ? <span>I'm happy</span> : <span>I'm sad</span>);

You can do this by using useState hooks like this:

import { useState } from "react";

            function playerInfo () {

        const [panel, setPanel] = useState(false);

        function toggleButton () {
            if(!panel) setPanel(true);
            else setPanel(false);
            }

        return (
            <div>
            <button onClick={toggleButton}>Toggle</div>
            panel ? {this.props.children} : null;
            </div>}
              );
            };

    export default playerInfo;

I assume that is because this is the stateless function that doesn't get called again

Basically, the only way to re-render component is to change state or props. :)

So when you change a local variable, React doesn't get notified about it and doesn't start reconcilation.

You can do this with native Javascipt otherwise in React you can not do this with stateless Component :)

const playerInfo = (props) => {
    let isPanelOpen = false;
    return ( <
        div onClick = {
            () => {
                if (document.getElementsByClassName("info-panel")[0].style.display == 'none') {
                    isPanelOpen = true;
                    document.getElementsByClassName("info-panel")[0].style.display = '';
                } else {
                    isPanelOpen = false;
                    document.getElementsByClassName("info-panel")[0].style.display = 'none';
                }
            }
        } > Toggle < /div> <
        div className = "info-panel" > {
            this.props.children
        } <
        /div>
    );
};

As of version 16.8 of React you can handle this for stateless components using a hook - in this case useState. In it's simplest form it can be implemented like this:

import React, { useState } from 'react';

const PlayerInfo = (props) => {
  const [showPanel, togglePanel] = useState(false);

  return (
    <div onClick={() => togglePanel(!showPanel) }>Toggle</div>
   {showPanel && (
     <div className="info-panel">
        {props.children}
      </div>
   )}
   </div>
  );
};

export default PlayerInfo; 

A functional component will not re-render unless a previous parent's state changes and passes down updated properties that propagate down to your functional component. You can pass an onClick handler in from a stateful parent and call this from your stateless component when its onClick is triggered. The parent will control the toggling of the display and pass it in as a prop to the child (see snippet below).

To architect this, you should determine if your HOC (higher order component) should be in charge of UI state. If so, then it can make the determination if its child component should be in an open state or not and then pass that as a property to the child state. If this is a component that should open and close independent of the world around it, then it should probably have its own state. For example, if you are making a tabbed widget, it should probably be controlling its own open and closed states.

class App extends React.Component {

  state= {
    isOpen: false
  }
  
  handleClick = () => {
    this.setState({
      isOpen: !this.state.isOpen
    })
  }
  
  render() {
    return (
      <div>
        <YourComponent isOpen={this.state.isOpen} handleClick={this.handleClick} />
      </div>
    )
  }
}


const YourComponent = ({isOpen, handleClick} = props) => {
  const onClick = () => {
    if (handleClick) {
      handleClick();
    }
  }
  
  return (
    <div onClick={onClick}>
      {isOpen ? 
        <h2>it is open</h2>
       :
        <h2>it is closed</h2>
      }
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));
<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="root"></div>

If your major concern is about passing properties/methods down to too many components, you could create a pure component which will give you access to state but not all the overhead of a React Component subclass. You could also look into using a state management tool like Redux.

发布评论

评论列表(0)

  1. 暂无评论