1

I am adding a component onclick and keeping track of the components using useState Array. However when I go to remove one of the added components, it doesn't recognize the full component Array size, only the state that was there when that component was initially added.

Is there a way to have the current state recognized within that delete function?

https://codesandbox.io/s/twilight-water-jxnup

import React, { useState } from "react";

export default function App() {
  const Span = props => {
    return (
      <div>
        <span>{props.index}</span>
        <button onClick={() => deleteSpan(props.index)}>DELETE</button>
        Length: {spans.length}
      </div>
    );
  };
  //set initial span w/ useState
  const [spans, setSpans] = useState([<Span key={0} index={Math.random()} />]);
  //add new span
  const addSpan = () => {
    let key = Math.random();
    setSpans([...spans, <Span key={key} index={key} />]);
  };
  //delete span
  const deleteSpan = index => {
    console.log(spans);
    console.log(spans.length);
  };
  //clear all spans
  const clearInputs = () => {
    setSpans([]);
  };
  return (
    <>
      {spans}
      <button onClick={() => addSpan()}>add</button>
      <button onClick={() => clearInputs()}>clear</button>
    </>
  );
}
Michalis Garganourakis
  • 2,872
  • 1
  • 11
  • 24
Kamau
  • 53
  • 2
  • 7
  • 1
    You should not create component inside the rendering flow, it will mess up React's lifecycle. – Emile Bergeron Jan 07 '20 at 20:21
  • 1
    Also, storing React elements (rendered JSX, like ``) inside the state is a code smell which could cause other bugs down the line. – Emile Bergeron Jan 07 '20 at 20:22
  • 1
    Store only the raw data, and re-render according to it with JSX. Pass all the necessary data as props to avoid creating components on the fly, or use simple helper function instead of a component. – Emile Bergeron Jan 07 '20 at 20:23
  • 1
    Does this answer your question? [Rendering React Components from Array of Objects](https://stackoverflow.com/questions/32157286/rendering-react-components-from-array-of-objects) – Emile Bergeron Jan 07 '20 at 20:25
  • 1
    Couldn't find documentation or a good answer specifically about dynamically created components in a render function, so [I wrote one myself](https://stackoverflow.com/a/59636503/1218980). – Emile Bergeron Jan 07 '20 at 21:54
  • Thanks Emile, ok I see, i was going down the wrong path w/ it (new to react)... I think that should get me on track! Yea span was just an example, but i see what u mean – Kamau Jan 07 '20 at 22:40

1 Answers1

0

UPDATE - Explaining why you are facing the issue descibed on your question

When you are adding your new span on your state, it's like it captures an image of the current values around it, including the value of spans. That is why logging spans on click returns you a different value. It's the value spans had when you added your <Span /> into your state.

This is one of the benefits of Closures. Every <Span /> you added, created a different closure, referencing a different version of the spans variable.


Is there a reason why you are pushing a Component into your state? I would suggest you to keep your state plain and clean. In that way, it's also reusable.

You can, for instance, use useState to create an empty array, where you will push data related to your spans. For the sake of the example, I will just push a timestamp, but for you might be something else.

export default function App() {
  const Span = props => {
    return (
      <div>
        <span>{props.index}</span>
        <button onClick={() => setSpans(spans.filter(span => span !== props.span))}>DELETE</button>
        Length: {spans.length}
      </div>
    );
  };

  const [spans, setSpans] = React.useState([]);

  return (
    <>
      {spans.length
        ? spans.map((span, index) => (
            <Span key={span} index={index} span={span} />
          ))
        : null}
      <button onClick={() => setSpans([
        ...spans,
        new Date().getTime(),
      ])}>add</button>
      <button onClick={() => setSpans([])}>clear</button>
    </>
  );
}

I hope this helps you find your way.

Michalis Garganourakis
  • 2,872
  • 1
  • 11
  • 24
  • Thanks Michalis, yes this explains exactly where I was going wrong, I didn't realize it was using a closure when creating the new component. – Kamau Jan 07 '20 at 23:01