Kamil Janowski
Kamil Janowski

Reputation: 2025

React state does not get updated

I know there's been many questions with this topic asked already, but it really feels like each and every one of them is different and I cannot find one that matches my issue closely enough.

I have a grid with draggable ItemComponents. Once selected, additional action icons show up (ItemActionIcon). I would very much like to unselect the component (and effectively hide the action icons) once one of the actions is clicked.

enter image description here

and so in line 77 <div onClick={() => this.setState({selected: false})} key={index}> I'm attempting to update the state of selected to false. It already works just fine in all other cases mentioned in the file. But not in this case. When I click the icon, I can see with a debugger (or with a console.log when I tried it) that the onClick action is triggered as expected and the ItemComponent even gets another call to the render method, but the this.state.selected is still set to true.

import React, {Component} from "react";
import Draggable, {DraggableBounds} from "react-draggable";
import ItemComponentAction from "./ItemComponentAction";
import ItemActionIcon from "./ItemActionIcon";

export interface Position {
    x: number;
    y: number;
}

export interface ItemComponentProps {
    gridSquareSize: number;
    canvasBounds: DraggableBounds;
    margin: number;
    position: Position;
}

interface ItemComponentState {
    gridSquareSize: number;
    canvasBounds: DraggableBounds;
    margin: number;
    selected: boolean;
}

export default abstract class ItemComponent extends Component<ItemComponentProps> {

    protected abstract readonly icon: string;
    protected abstract readonly actions: ItemComponentAction[];

    state: ItemComponentState;

    protected constructor(props: ItemComponentProps) {
        super(props);
        this.state = {
            gridSquareSize: props.gridSquareSize,
            canvasBounds: props.canvasBounds,
            margin: props.margin,
            selected: false
        };
    }

    render() {
        return (
            <Draggable grid={[this.state.gridSquareSize / 2, this.state.gridSquareSize / 2]}
                       defaultPosition={{
                           x: this.state.margin + this.props.position.x * this.state.gridSquareSize,
                           y: this.state.margin + this.props.position.y * this.state.gridSquareSize
                       }}
                       handle=".handle"
                       bounds={this.state.canvasBounds}
                       onDrag={() => this.setState({selected: false})}
            >
                <div tabIndex={0}
                     className="handle"
                     onClick={() => this.setState({selected: true})}
                     onBlur={() => this.setState({selected: false})}
                     style={{
                        position: 'absolute',
                        backgroundColor: 'red',
                        width: this.state.gridSquareSize,
                        height: this.state.gridSquareSize,
                        cursor: "move"
                     }}
                >
                    {this.icon}

                    {
                        !this.state.selected || !this.actions.length
                           ? null
                           : (
                                <div style={{
                                    position: 'absolute',
                                    bottom: "0"
                                }}>
                                    {
                                        this.actions.map((action, index) => (
                                            <div onClick={() => this.setState({selected: false})} key={index}>
                                                <ItemActionIcon {...action}/>
                                            </div>
                                        ))
                                    }
                                </div>
                            )
                    }
                </div>
            </Draggable>
        );
    }
}

so what's the deal?

Upvotes: 0

Views: 195

Answers (1)

jered
jered

Reputation: 11591

The outer <div> of your component has its own onClick handler which is setting the value of your state back to false. Try using stopPropagation() on the inner onClick handled event. That will prevent the event from propagating to the outer parent <div>, and only the inner onClick handler will execute when it is clicked on.

{
  !this.state.selected || !this.actions.length ? null : (
    <div
      style={{
        position: "absolute",
        bottom: "0"
      }}
    >
      {this.actions.map((action, index) => (
        <div
          onClick={e => {
            e.stopPropagation();
            this.setState({ selected: false });
          }}
          key={index}
        >
          <ItemActionIcon {...action} />
        </div>
      ))}
    </div>
  );
}

Upvotes: 1

Related Questions