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 color
is 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}} />
}