React Hooks - Modified state not reflecting immediately

17,911

Much like setState, the state update behaviour using hooks will also require a re-render and update and hence the change will not be immedialtely visible. If however you try to log state outside of the handleVisibleChange method, you will see the update state

export const NodeMenu: React.SFC<NodeMenuProps> = props => {
  const [isVisible, setIsVisible] = useState(false)      

  const hide = () => {
    setIsVisible(false)
  }

  const handleVisibleChange = (visible: boolean) => {
    console.log(visible) // visible is `true` when user clicks. It works
    setIsVisible(visible) // This does not set isVisible to `true`.
  }      

  console.log({ isVisible });
  return (
    <div className={props.className}>
      <div className={styles.requestNodeMenuIcon}>
        <Popover
          content={props.content}              
          title={props.title}
          trigger="click"
          placement="bottom"
          visible={isVisible}
          onVisibleChange={handleVisibleChange}
        >
          {props.button}
        </Popover>
      </div>
    </div>
  )
}

Any action that you need to take on the basis of whether the state was update can be done using the useEffect hook like

useEffect(() => {
   // take action when isVisible Changed
}, [isVisible])
Share:
17,911
Greg Forel
Author by

Greg Forel

Updated on July 19, 2022

Comments

  • Greg Forel
    Greg Forel almost 2 years

    I'm trying to refactor a class into a stateless component using React hooks.

    The component itself is very simple and I don't see where I'm making a mistake, as it's almost a copy paste from the react docs.

    The component is showing a popup when the user clicks on a button (button is passed through props to my component). I'm using typescript.

    I commented the line that fails to do what I want in the hooks version

    Here's my original class:

    export interface NodeMenuProps extends PropsNodeButton {
      title?: string
      content?: JSX.Element
      button?: JSX.Element
    }
    export interface NodeMenuState {
      visible: boolean
    }
    export class NodeMenu extends React.Component<NodeMenuProps, NodeMenuState> {
      state = {
        visible: false
      }
    
      hide = () => {
        this.setState({
          visible: false
        })
      }
    
      handleVisibleChange = (visible: boolean) => {
        this.setState({ visible })
      }
    
      render() {        
        return (
          <div className={this.props.className}>
            <div className={styles.requestNodeMenuIcon}>
              <Popover
                content={this.props.content}
                title={this.props.title}
                trigger="click"
                placement="bottom"
                visible={this.state.visible}
                onVisibleChange={this.handleVisibleChange}
              >
                {this.props.button}
              </Popover>
            </div>
          </div>
        )
      }
    }
    

    Here's the React hooks version:

    export interface NodeMenuProps extends PropsNodeButton {
      title?: string
      content?: JSX.Element
      button?: JSX.Element
    }    
    export const NodeMenu: React.SFC<NodeMenuProps> = props => {
      const [isVisible, setIsVisible] = useState(false)      
    
      const hide = () => {
        setIsVisible(false)
      }
    
      const handleVisibleChange = (visible: boolean) => {
        console.log(visible) // visible is `true` when user clicks. It works
        setIsVisible(visible) // This does not set isVisible to `true`.
        console.log(isVisible) // is always `false` despite `visible` being true.
      }      
    
      return (
        <div className={props.className}>
          <div className={styles.requestNodeMenuIcon}>
            <Popover
              content={props.content}              
              title={props.title}
              trigger="click"
              placement="bottom"
              visible={isVisible}
              onVisibleChange={handleVisibleChange}
            >
              {props.button}
            </Popover>
          </div>
        </div>
      )
    }
    
  • Greg Forel
    Greg Forel over 5 years
    Thanks Shubham, I can see the value updating now. However, since isVisible becomes true at re-render, I don't understand why the popover doesn't show up. I read the docs about useEffect, but I can't see why I need to use this for the popup to show up since isVisible does change to true?
  • Shubham Khatri
    Shubham Khatri over 5 years
    you dont need to use useEffect. That was just an additional information
  • DJ2
    DJ2 over 4 years
    @ShubhamKhatri does this mean the change handler should be moved inside the useEffect hook as well?
  • Jeethesh Kotian
    Jeethesh Kotian about 3 years
    I too am facing a similar problem but didn't find the solution.