Home > other >  How can I "decouple" a state var from its value so that in certain operations the value no
How can I "decouple" a state var from its value so that in certain operations the value no

Time:07-18

I am, just for fun, writing a simple paint program. It has a <Canvas> that creates [color, setColor] with useState and passes them to its children, a <ColorSelector> and a grid of <Cell>s. Clicking a color in the ColorSelector updates color, and clicking a Cell sets its background to color.

The problem is of course that when you click a different color in ColorSelector and it updates color, it cascades down and changes all the currently painted pixels to the new color too. Normally that's the point of state vars, so maybe state isn't what I actually want here.

How can I let Cells always know the current color value but, once applied, keep the background color unchanged until the next click?

CodePudding user response:

maybe try to pass the new color in a deep copy if I understand what you mean to

CodePudding user response:

Yonatan's answer got me on the right track to find the solution.

What I'd been doing: As described in my comment there, each <Cell> had an isPainted boolean and rendered a <CellContents> child which got an onClick that sets isPainted to true, and a className that maps to the selected color iff isPainted is true. Written that way, it didn't matter if I used color or a copy with e.g. color.concat(). Either way, when color changed, the Cell rerendered and got the new color.

What ended up working:

I eliminated the boolean middle-man and made onClick set isPainted to color (don't even need to make a copy) and made CellContent use the value of isPainted (which I should probably rename now) directly to get its the className.

Why it worked

I think this is what's going on, but encourage anyone more knowledgeable to chime in!

Previously I had if (isPainted) { className = color } in the body of Cell, so whenever a cell got re-rendered, its className would change. Now coloris only accessed in event handlers, so now when a Cell is re-rendered, it gets new handlers that use the new colors, but they don't actually do anything until the associated event happens.

Code

Here's the complete working solution

(NOTE: the state var I've been calling color has been renamed to selectedColor, and isPainted has been changed to colorClass)

import React, { useState } from 'react';
import styled, { css, ThemeProps } from 'styled-components';

const CellContent = styled.div<{ selectedColor?: string; size?: string, theme?: ThemeProps<{ background?: string; primary?: string; selected?: string }> }>`
  width: ${({ size }) => size || '1vmin'};
  height: ${({ size }) => size || '1vmin'};
  border: thin solid white;

  background-color:  ${({ theme }) => theme.primary || 'lightgrey'};
  &:hover {
    background-color: ${({ selectedColor }) => selectedColor } ;
  }
  &.red {
    background-color: red;
  }
  &.blue {
    background-color: blue;
  }
`;

interface CellProps {
  selectedColor: string;
  painting: boolean;
  setPainting: React.Dispatch<React.SetStateAction<boolean>>;
}

const Cell = ({ selectedColor, painting, setPainting }: CellProps) => {
  const [colorClass, setColorClass] = useState('')
  const onm ouseDown = () => setPainting(true);
  const onm ouseUp = () => setPainting(false);;
  const onClick = () => setColorClass(selectedColor);
  const onm ouseOver = () => {
    if (painting) {
      setColorClass(selectedColor);
    }
  }

  return <CellContent {...{selectedColor, className: colorClass, onClick, onm ouseOver, onm ouseDown, onm ouseUp}} />
}
  • Related