Pavel Fedorovsky
Pavel Fedorovsky

Reputation: 57

How to use useState with dynamically added elements?

I have a canvas and when i click on it, it draws a line.

Line is a function:

const line = (x1, y1, x2, y2) => {
  return (
    <line id="lineD" x1={x1} y1={y1} x2={x2} y2={y2} style={{ stroke: "black", strokeWidth: 3, strokeLinecap: "round" }}></line>
    )
}

It works fine, except when I scale the canvas. When I do so, I change a scale state.

So I want line attributes to be like this: x1={x1 * scale}, but it only sets it once.

This is how I add lines, (content is another state):

content.push(line(100, 100, 200, 200))

(these are just random numbers)
And svg is: <svg>{content}</svg>

Upvotes: 0

Views: 92

Answers (2)

RubenSmn
RubenSmn

Reputation: 4672

As mentioned in the comments it is not a good practice to store components in state.

This SO answer explains why.

Storing elements for later use directly breaks this core concept. You risk having stale elements that do not correspond to the current set of properties.

Instead you could store the data points of the lines.

const [content, setContent] = [
  {
    x1: 100,
    y1: 100,
    x2: 100,
    y2: 100,
  },
];

const [scale, setScale] = useState(1);

Your Line component

const Line = ({ x1, y1, x2, y2, scale }) => {
  return (
    <line
      id="lineD"
      x1={x1 * scale}
      y1={y1 * scale}
      x2={x2 * scale}
      y2={y2 * scale}
      style={{ stroke: "black", strokeWidth: 3, strokeLinecap: "round" }}
    ></line>
  );
};

When rendering the lines you can simply map over the content and add the props to the Line component

<svg>
  {content.map((lineData) => {
    return <Line {...lineData} scale={scale} />;
  })}
</svg>

Upvotes: 0

rocambille
rocambille

Reputation: 15996

React is "reacting" only to state or props update. Since your line function returns JSX, you should turn it into a real React component and pass your data as props to make the rendering dynamic. Something like that:

const Line = (props) => {
  const {x1, y1, x2, y2, scale} = props;
  return (
    <line id="lineD" x1={x1 * scale} y1={y1 * scale} x2={x2 * scale} y2={y2 * scale} style={{ stroke: "black", strokeWidth: 3, strokeLinecap: "round" }}></line>
  );
}

Note the uppercase L in Line: this is mandatory for React to see it as a component. Then you may use it to produce your array:

content.push(<Line x1={100} y2={100} x2={200} y2={200} scale={scale} />) 

But a state should not be muted. You should use the set function you get from useState:

const [content, setContent] = useState([]);

...

// do the following in an event handler, or in a useEffect
setContent([...content, <Line x1={100} y2={100} x2={200} y2={200} scale={scale} />]);

This will update the state and trigger a new rendering

Upvotes: 1

Related Questions