React Hooks - Modified state not reflecting immediately
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])
Greg Forel
Updated on July 19, 2022Comments
-
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
versionHere'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 over 5 yearsThanks Shubham, I can see the value updating now. However, since
isVisible
becomestrue
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 sinceisVisible
does change totrue
? -
Shubham Khatri over 5 yearsyou dont need to use useEffect. That was just an additional information
-
DJ2 over 4 years@ShubhamKhatri does this mean the change handler should be moved inside the
useEffect
hook as well? -
Jeethesh Kotian about 3 yearsI too am facing a similar problem but didn't find the solution.