3

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!

4 Answers 4

3

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>

Sign up to request clarification or add additional context in comments.

3 Comments

Wow, this looks really clean. Would this solution allow the updating of CSS as well? For instance, if James is clicked, can I set the colour to red?
Certainly, let me update the snippet to reflect that.
Done deal, @Jschriemer :)
2

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

Shoud be

const clicked = (index) => {

1 Comment

Ah I see thanks! Okay fixed that so I get the proper index onClick. However, the state still is not updating.. It must have something to do with the line className = {"header" + {index}.index} but I cant figure out what an alternative is
2

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

Comments

0

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>
    );
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.