Home > database >  React component not passing value to correct component
React component not passing value to correct component

Time:01-31

I have a simple React app that uses react-dnd to build a grid of draggable squares that have an initial color and text value and two components to change the color and change the text.

The color change (via ColorPicker2, using react-color library) works okay. The text change (using TextInput from @carbon/react) doesn't work as desired.

I thought I was applying the same logic with both components, but whilst the color-picker updates the color and retains that color when the square is moved, the text seems to render inside the TextInput and not the square itself and I can't figure out the logical difference.

The Code Sandbox is here: https://codesandbox.io/s/wild-waterfall-z8s1de?file=/src/App.js

This is the current code:

ColorPicker2.js

import "./styles.css";
import React from "react";
import { BlockPicker } from "react-color";

const presetColors = ["#9E9E9E", "#4CAF50", "#FFEB3B", "#F44336", "#2196F3"];

const ColorPicker2 = (props) => {
  const handleChangeComplete = (color) => {
    if (props.updateColor) {
      props.updateColor(color.hex);
    }
  };

  return (
    <div className="palette">
      <BlockPicker
        className="palette"
        colors={presetColors}
        onChangeComplete={handleChangeComplete}
        presetColors={Object.values(presetColors)}
        color={props.currentColor}
      />
    </div>
  );
};

export default ColorPicker2;

App.js

import "./styles.css";
import React, { useState } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import edit from "./edit.svg";
import palette from "./palette.svg";
import ColorPicker2 from "./ColorPicker2";
import { TextInput } from "@carbon/react";

const DndWrapper = (props) => {
  return <DndProvider backend={HTML5Backend}>{props.children}</DndProvider>;
};

const DraggableSquare = ({ index, text, color, moveSquare }) => {
  const [{ isDragging }, drag] = useDrag({
    type: "square",
    item: { index },
    collect: (monitor) => ({
      isDragging: monitor.isDragging()
    })
  });

  const [isHovered, setIsHovered] = useState(false);
  const [, drop2] = useDrop({
    accept: "square",
    drop: (item, monitor) => {
      const didDrop = monitor.didDrop();
      if (!didDrop) {
        moveSquare(item.index, index);
      }
    },
    hover: (item, monitor) => {
      setIsHovered(monitor.isOver());
    },
    collect: (monitor) => {
      setIsHovered(monitor.isOver());
    }
  });

  const [isPaletteOpen, setIsPaletteOpen] = useState(false);
  const [isTextInputOpen, setIsTextInputOpen] = useState(false);
  const [newText, setNewText] = useState(text);

  const opacity = isDragging ? 0.5 : 1;

  return (
    <div className="square-div" ref={drop2}>
      <div
        className="grey-square"
        ref={drag}
        style={{
          opacity,
          backgroundColor: color,
          width: "200px",
          height: "200px",
          textAlign: "center",
          paddingTop: "30px",
          position: "relative",
          border: isHovered ? "3px solid blue" : "none",
          borderRadius: "5px",
        }}
        onm ouseOver={() => setIsHovered(true)}
        onm ouseLeave={() => setIsHovered(false)}
      >
        <img
          src={edit}
          onClick={() => {
            setIsTextInputOpen(!isTextInputOpen);
            if (!isTextInputOpen) {
              moveSquare(index, newText, color, undefined);
            }
          }}
          style={{
            width: "15px",
            height: "15px",
            position: "absolute",
            right: "5px",
            top: "5px"
          }}
          alt="edit icon"
        />

        {isTextInputOpen && (
          <TextInput
          id="newtext"
          labelText=""  
            value={newText}
            onChange={(e) => setNewText(e.target.value)}
          />
        )}
        <img
          src={palette}
          onClick={() => setIsPaletteOpen(!isPaletteOpen)}
          style={{
            width: "15px",
            height: "15px",
            position: "absolute",
            right: "25px",
            top: "5px"
          }}
          alt="palette icon"
        />

        {isPaletteOpen && (
          <ColorPicker2
          className="palette"
            currentColor={color}
            updateColor={(newColor) =>
              moveSquare(index, index, newText, newColor)
            }
          />
        )}
      </div>
    </div>
  );
};
const Grid = () => {
  const [grid, setGrid] = useState([
    { text: "1", color: "grey" },
    { text: "2", color: "grey" },
    { text: "3", color: "grey" },
    { text: "4", color: "grey" },
    { text: "5", color: "grey" },
    { text: "6", color: "grey" },
    { text: "7", color: "grey" },
    { text: "8", color: "grey" },
    { text: "9", color: "grey" },
    { text: "10", color: "grey" },
    { text: "11", color: "grey" },
    { text: "12", color: "grey" },
    { text: "13", color: "grey" },
    { text: "14", color: "grey" },
    { text: "15", color: "grey" }
  ]);

  const moveSquare = (fromIndex, toIndex, newText, newColor) => {
    setGrid((grid) => {
      const newGrid = [...grid];
      const item = newGrid[fromIndex];
      newGrid.splice(fromIndex, 1);
      newGrid.splice(toIndex, 0, {
        text: newText || item.text,
        color: newColor || item.color
      });
      return newGrid;
    });
  };

  return (
    <>
      <DndWrapper>
        <div
          className="grid"
          style={{
            display: "grid",
            gridTemplateColumns: "repeat(5, 190px)",
            gridGap: "15px",
            gridColumnGap: "20px",
            gridRowGap: "10px",
            position: "absolute"
          }}
        >
          {grid.map((square, index) => (
            <DraggableSquare
              key={index}
              index={index}
              text={square.text}
              color={square.color}
              moveSquare={moveSquare}
              //grid={grid}
              //setGrid={setGrid}
            />
          ))}
        </div>
      </DndWrapper>
    </>
  );
};

export default Grid;

Any thoughts from fresh eyes would be helpful.

CodePudding user response:

I think this is just a simple issue with using index as the key while mapping. Adjusting your code pen to have a unique key fixed it for me but the input text is not being saved anywhere so returned to the default text={square.text} when moved as expected.

Unique Id in objects:

  const [grid, setGrid] = useState([
    { text: "1", color: "grey", id: crypto.randomUUID() },
    { text: "2", color: "grey", id: crypto.randomUUID() },
    { text: "3", color: "grey", id: crypto.randomUUID() },...])

Adding key to mapped object:

 {grid.map((square, index) => (
            <DraggableSquare
              key={square.id}
              index={index}
              text={square.text}
              color={square.color}
              moveSquare={moveSquare}
              //grid={grid}
              //setGrid={setGrid}
            />}
  • Related