[React] Rendering dynamic list of content
Published: 2021-11-02 07:23:19

OK so we have a project and in that project, we want to render some list of data that is dynamic and for example changes when the user clicks a button. Lets see how this is done in react, it's quite straightforward and you can probably already guess that it involves the use of states.
![Dynamic list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5soep76l2pguuqvndk8.gif)

I'm going to use a very basic project I prepared to illustrate this topic. A simple react app that lets the user to input some text and it gets rendered on the screen for the user to see.

<b>Take a look at the full source code [here](https://codesandbox.io/s/goals-project-l8xkl) because I'm not going through how to build the entire project, only the rendering of the dynamic list of data.</b>

<h3>Project Structure</h3>
<ul>
    <li>Body
       <ul>
           <li>Body.js</li>
           <li>Body.css</li>
       </ul>
    </li>
    <li>Card
        <ul>
           <li>Card.js</li>
           <li>Card.css</li>
        </ul>
    </li>
    <li>App.js</li>
    <li>index.js</li>
    <li>styles.css</li>
</ul>

<b>Card - </b> A component which is meant to house other components and give them a sort of cover.(Bad naming on my part I guess)
```JavaScript
import "./Card.css";

const Card = (props) => {
  const classes = "card " + props.className;

  return <div className={classes}>{props.children}</div>;
};

export default Card;
```
> The `children` prop is a default prop that returns whatever is the component encapsulates. In this case it will return the other components that are encapsulated by the `Card` component and put them in a div


<b>Goal - </b>The component which handles the dynamic list of data.
```JavaScript
import Card from "../Card/Card";
import "./Goal.css";

const Goal = (props) => {
  return (
    <div>
      {props.items.map((indiv) => (
        <Card className="set-goal" key={indiv.key}>
          {indiv._output}
        </Card>
      ))}
    </div>
  );
};

export default Goal;
```
> This component takes the list of dynamic content as a prop(`items` which will be a state) and encapsulates the all of the items in the list with the `Card` component.

<b>Body - </b> The main component where the other components are brought together to form the app.
```JavaScript
const goalsList = [
  {
    key: 0,
    _output: ""
  }
];

// TESTING STYLED COMPONENTS LIB, GOAL INPUT IS A COMPONENT WITH ITS OWN UNIQUE STYLING
const GoalInput = styled.input`
  background-color: ${(props) => (props.invalid ? "bisque" : "transparent")};
  border-color: ${(props) => (props.invalid ? "red" : "beige")};
  border-width: 1px;
  width: 85%;
  height: 1.5rem;

  &:focus {
    outline: none;
  }
`;

const Body = () => {
  // STATES USED IN THE APP
  const [goals, setGoals] = useState(goalsList);
  const [isValid, setIsValid] = useState(true);

  // CALLED WHEN THE TEXT IN THE INPUT ELEMENT CHANGES
  const validGoalsInput = (event) => {
    if (event.target.value.trim().length > 1) {
      setIsValid(true);
    } else {
      setIsValid(false);
    }
  };

  // CALLED WHEN THE USER CLICKS THE ADD BUTTON
  const addHandler = () => {
    let goalText = document.getElementById("goalText");
    // UPDATE THE LIST OF GOALS STATE IF THE INPUT ISNT EMPTY/WHITESPACE OR JUST A CHARACTER
    if (goalText.value.trim().length > 1) {
      if (isValid === false) {
        setIsValid(true);
      }
      setGoals((prevGoals) => {
        if (prevGoals.length === 1 && prevGoals[0]._output === "") {
          return [
            {
              key: 0,
              _output: goalText.value
            }
          ];
        } else {
          return [
            {
              key: prevGoals.length,
              _output: goalText.value
            },
            ...prevGoals
          ];
        }
      });
      goalText.value = "";
    } else {
      setIsValid(false);
    }
  };

  return (
    <div>
      <Card className="card-body">
        <div className="goals-text">My Goals</div>
        <div>
          <GoalInput
            invalid={!isValid}
            type="text"
            id="goalText"
            onChange={validGoalsInput}
          />
        </div>
        <div className="goals-button">
          <button onClick={addHandler}>Add Goal</button>
        </div>
      </Card>
      <Goal items={goals} />
    </div>
  );
};

export default Body;
```
We want to render a 'list' of content so we create a dummy array `goalsList` which has one js object in it. The array will serve as the initial value of the state which holds the dynamic list of content. It has a `key` and `_output` attribute.

The `key` attribute is just there as a best practice, we will use it to allow React to render our list efficiently.

The `_output` attribute will contain the text which the user inputs in the react app.

The next piece of code isn't important to the topic; just me getting to know how to use the `styled components` external lib.

So straight into our `Body` component, we define two state variables;
> goals - The state which holds our dynamic list

> isValid - This state will be a boolean which tells us if what the user inputted is valid

<b>Lets skip the other code for now and jump straight to the structure of the `Body` component.</b>

```JavaScript
return (
    <div>
      <Card className="card-body">
        <div className="goals-text">My Goals</div>
        <div>
          <GoalInput
            invalid={!isValid}
            type="text"
            id="goalText"
            onChange={validGoalsInput}
          />
        </div>
        <div className="goals-button">
          <button onClick={addHandler}>Add Goal</button>
        </div>
      </Card>
      <Goal items={goals} />
    </div>
  );
```
The structure should be clear from just looking at the code itself, `GoalInput` is just a styled component(created with `styled component` lib) encapsulated in a `Card`.

It takes a prop `invalid` which is used for dynamic styling.
It determines when we add a different styling to the input element based on the value of the `isValid` state which tells us whether what the user inputted is valid or not.

The `onChange` event triggers when the value our input element changes.

```Javascript
const validGoalsInput = (event) => {
    if (event.target.value.trim().length > 1) {
      setIsValid(true);
    } else {
      setIsValid(false);
    }
  };
```
We're calling an event handler which just sets the boolean value of the `isValid` state based on whether the user input is empty, one character, or whitespace.

Then there's the `Goal` component which handles our dynamic list and a simple button with an event handler `addHandler` set for when it is clicked.

```Javascript
const addHandler = () => {
    let goalText = document.getElementById("goalText");
    // UPDATE THE LIST OF GOALS STATE IF THE INPUT ISNT EMPTY/WHITESPACE OR JUST A CHARACTER
    if (isValid === true) {
      setGoals((prevGoals) => {
        if (prevGoals.length === 1 && prevGoals[0]._output === "") {
          return [
            {
              key: 0,
              _output: goalText.value
            }
          ];
        } else {
          return [
            {
              key: prevGoals.length,
              _output: goalText.value
            },
            ...prevGoals
          ];
        }
      });
      goalText.value = "";
    }
  };
```
First we get the input element through its id and keep that in a variable, then we check if the `isValid` prop is set to true, that would indicate that what is currently in the input element is valid.

If it's valid, we update the `goals` state; We check if we're currently adding the first actual content apart from the dummy value in the state, if yes then we simply return an array which contains only one item effectively overwriting the dummy value. 
```JavaScript
return [
            {
              key: 0,
              _output: goalText.value
            }
          ];
```

If no, we return an array which contains a new item and the previous values in the state, thereby updating our `goals` state with new data. The value in the input element is cleared after that.
```JavaScript
return [
            {
              key: prevGoals.length,
              _output: goalText.value
            },
            ...prevGoals
          ];
```

> When you're updating a state and you're depending on the current value of the state before it is updated, you should wrap the entire update process in a function which takes a parameter; the parameter being the current value of the state.

```JavaScript
setGoals((prevGoals) => {
        if (prevGoals.length === 1 && prevGoals[0]._output === "") {
          return [
            {
              key: 0,
              _output: goalText.value
            }
          ];
        } else {
          return [
            {
              key: prevGoals.length,
              _output: goalText.value
            },
            ...prevGoals
          ];
        }
      });
```
<b>Notice how the entire updating process is in a function which takes a parameter `prevGoals`</b>

The `goals` state is passed to the `Goals` Component as a prop `item`.
 
The component uses `map()` to apply a `Card` component with a unique class that sets the styling.

The `key` prop is a default prop that is available to Components by default. It's used in a scenario like this where we're rendering an array/list of content. It enables React to render the list effectively, giving each item in the array a sort of unique identity.

Without the key prop everything will still work fine but there can be some performance loss(in large scale apps, ours is too small to notice any performance hiccups)

<b>A more in-depth explanation of the key prop can be gotten from in the article below</b>
{% link https://dev.to/francodalessio/understanding-the-importance-of-the-key-prop-in-react-3ag7 %}

{% codesandbox goals-project-l8xkl %}