Jschriemer
Jschriemer

Reputation: 625

Dynamic States with React Hooks

I am trying to render an array using the map() function while giving each element its own unique className (based on the index value) using some established states. An element should change colour when clicked using hooks. I've run into a problem where className = {"header" + {index}.index} gives the correct state name (header0, header1, etc.) but corresponds to a string rather than the class names established with the same names.

const data = ["James", "John", "Jessica", "Jamie"];

export default function App() {
  const [header0, setHeader0] = useState("visable");
  const [header1, setHeader1] = useState("visable");
  const [header2, setHeader2] = useState("visable");
  const [header3, setHeader3] = useState("visable");

  const clicked = (index) => {
    if (index === 0) {
      setHeader0("invisible");
    } else if (index === 1) {
      setHeader1("invisible");
    }
    /*Is there an alternative like {setHeader + index} instead of this loop?*/
  };

  return (
    <div className="App">
      <h1>Sample Project</h1>
      {data.map((value, index) => (
        <h1
          className={"header" + { index }.index}
          onClick={() => {
            clicked(index);
          }}
        >
          {/* classname should be the state "header0" but right now its just a string */}
          Hello {value}!
        </h1>
      ))}
    </div>
  );
}

Here is a code sandbox of what I am trying, with a few comments where things are going wrong. Am I going about this problem the correct way? https://codesandbox.io/s/wispy-star-38qvw?fontsize=14&hidenavigation=1&theme=dark

Edit wispy-star-38qvw

Any help is greatly appreciated!

Upvotes: 3

Views: 4030

Answers (4)

AKX
AKX

Reputation: 168843

You could use a single state array for visibilities.

In addition, if you stash the index in the clicked element's HTML dataset (data-index), you don't need a separate closure/function for each index.

const data = ["James", "John", "Jessica", "Jamie"];

function App() {
  const [visibilities, setVisibilities] = React.useState(() => data.map((x) => true));

  const handleClick = (event) => {
    const index = parseInt(event.currentTarget.dataset.index, 10);
    const newVisibilities = [...visibilities];
    newVisibilities[index] = !newVisibilities[index];
    setVisibilities(newVisibilities);
  };

  return (
    <div className="App">
      {data.map((value, index) => (
        <h1 data-index={index} onClick={handleClick} className={visibilities[index] ? "selected" : undefined}>
          Hello {value}, you are {visibilities[index] ? "visible" : "hidden"}!
        </h1>
      ))}
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector("main"));
.selected {
  background: lightgreen;
}

h1 {
  cursor: pointer;
  user-select: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<main></main>

Upvotes: 3

uke
uke

Reputation: 891

You can change the way you are structing the state to an array, then the click function will turn it invisible by index

const data = ["James", "John", "Jessica", "Jamie"];

export default function App() {
    const [headersVisible, setHeaderVisible] = useState(
        data.map(() => "visible")
    );

    const clicked = (index) => {
        setHeaderVisible(
            headersVisible.map((val, ix) => (index === ix ? "invisible" : val))
        );
    };

    return (
        <div className="App">
            <h1>Sample Project</h1>
            {data.map((value, index) => (
                <h1
                    key={index}
                    className={headersVisible[index]}
                    onClick={()=>clicked(index)}
                >
                    {/* classname should be the state "header0" but right now its just a string */}
                    Hello {value}!
                </h1>
            ))}
        </div>
    );
}

Upvotes: 0

Sarabadu
Sarabadu

Reputation: 597

I'm afraid that is not a nice approach, but you can try this:

import React, { useState, useEffect } from "react";
import "./styles.css";

const data = ["James", "John", "Jessica", "Jamie"];

export default function App() {
  const headers = [];

  [headers[0], headers[1], headers[2], headers[3]] = [
    useState("visable"),
    useState("visable"),
    useState("visable"),
    useState("visable")
  ]; // each headers[index] is an array with [state, setState function]

  const clicked = (index) => {
    alert("clicked " + index);
    headers[index][1]("invisible");
    /*Is there an alternative like {setHeader + index} instead of this loop?*/
  };

  return (
    <div className="App">
      <h1>Sample Project</h1>
      {data.map((value, index) => (
        <h1
          className={headers[index][0]}
          onClick={() => {
            clicked(index);
          }}
        >
          {/* classname should be the state "header0" but right now its just a string */}
          Hello {value}!
        </h1>
      ))}
    </div>
  );
}

the code pen: https://codesandbox.io/s/agitated-rain-o31ms

Upvotes: 2

uke
uke

Reputation: 891

You are declaring 2 arguments in clicked function so e is the actual index.

Shoud be

const clicked = (index) => {

Upvotes: 2

Related Questions