user13914915
user13914915

Reputation:

React hooks accordion toggles all blocks instead of one

With a React Accordion I wanted to create a function that will only show the information of the belonging episode. But now it seems that all blocks are doing the same thing. How can I build that function so one block will be toggling instead of all?

Link to CodeSandbox

export const HomePage: React.FC<IProps> = () => {
const [open, setOpen] = useState(false);
  
  return (
    <div>{data.characters.results.map((character: { id: number, name: string, image: string; episode: any; }, index: number) => (
      <div key={character.id}>
        <CharacterHeading>{character.name}</CharacterHeading>
        <CharacterImage src={character.image} alt={character.name} />
        {character.episode.map((char: { name: string; air_date: string; episode: string; characters: any, id: number; }) => (
          <div key={char.id}>

            {char.name}
            {char.air_date}
            {char.episode}
            <AccordionButton open={open}
              onClick={() => setOpen(!open)}>
              Check all characters
            </AccordionButton>
            <EpisodeListSection open={open}>
              {char.characters.map((ep: { id: number, name: string; image: string; }, index: number) => (
                <EpisodeInfo key={ep.id}>
                  <EpisodeInfoBlock>
                    <EpisodeName>{ep.name}</EpisodeName>
                    <EpisodeImage src={ep.image} alt={ep.name} />
                  </EpisodeInfoBlock>
                </EpisodeInfo>
              ))}
            </EpisodeListSection>
          </div>
        ))}
      </div>
    ))
    }</div>
  );
};

Upvotes: 1

Views: 254

Answers (2)

Dipan Sharma
Dipan Sharma

Reputation: 1133

Here is the updated code that you can use:

import { useState } from "react";
import { gql, useQuery } from "@apollo/client";
import {
  AccordionButton,
  CharacterImage,
  CharacterHeading,
  EpisodeInfo,
  EpisodeImage,
  EpisodeInfoBlock,
  EpisodeListSection,
  EpisodeName
} from "./HomePage.styles";

export const GET_CHARACTER = gql`
  query {
    characters(page: 2, filter: { name: "rick" }) {
      results {
        name
        image
        gender
        episode {
          id
          name
          air_date
          episode
          characters {
            id
            name
            image
          }
        }
      }
    }
    location(id: 1) {
      id
    }
    episodesByIds(ids: [1, 2]) {
      id
    }
  }
`;

export interface Episodes {
  name: string;
  air_data: string;
  episode: string;
  characters: Array<any>;
}

export interface IProps {
  episodeList?: Episodes[];
}

export const HomePage: React.FC<IProps> = () => {
  const { data, loading, error } = useQuery(GET_CHARACTER);
  const [inputValue, setInputValue] = useState("");
  const [open, setOpen] = useState(false);
  const [selectedId, setSelectedId] = useState(0);

  const onChangeHandler = (text: string) => {
    setInputValue(text);
  };

  const onSelectItem = (selectedItemId: number) => {
    if (selectedId !== selectedItemId) {
      setSelectedId(selectedItemId);
    } else {
      setSelectedId(-1);
    }
  };

  if (loading) return <p>loading</p>;
  if (error) return <p>ERROR: {error.message}</p>;
  if (!data) return <p>Not found</p>;

  return (
    <div>
      <input
        type="text"
        name="name"
        onChange={(event) => onChangeHandler(event.target.value)}
        value={inputValue}
      />
      <div>
        {data.characters.results.map(
          (
            character: {
              id: number;
              name: string;
              image: string;
              episode: any;
            },
            index: number
          ) => (
            <div key={character.id}>
              <CharacterHeading>{character.name}</CharacterHeading>
              <CharacterImage src={character.image} alt={character.name} />
              {character.episode.map(
                (char: {
                  name: string;
                  air_date: string;
                  episode: string;
                  characters: any;
                  id: number;
                }) => (
                  <div key={char.id}>
                    {char.name}
                    {char.air_date}
                    {char.episode}
                    <AccordionButton
                      onClick={() => onSelectItem(char.id)}
                      open={char.id === selectedId ? true : false}
                    >
                      Check all characters
                    </AccordionButton>
                    <EpisodeListSection
                      open={char.id === selectedId ? false : true}
                    >
                      {char.characters.map(
                        (
                          ep: { id: number; name: string; image: string },
                          index: number
                        ) => (
                          <EpisodeInfo key={ep.id}>
                            <EpisodeInfoBlock>
                              <EpisodeName>{ep.name}</EpisodeName>
                              <EpisodeImage src={ep.image} alt={ep.name} />
                            </EpisodeInfoBlock>
                          </EpisodeInfo>
                        )
                      )}
                    </EpisodeListSection>
                  </div>
                )
              )}
            </div>
          )
        )}
      </div>
    </div>
  );
};

Upvotes: 0

samuei
samuei

Reputation: 2102

You only have one open variable that you are passing to every accordion. Thus, when one accordion changes that value with setOpen, it changes for all of them.

If AccordionButton is a component you built, I would suggest letting that component handle its own open/closed state. Your parent doesn't seem like it needs to control that. Otherwise, you will need open to be an array of values, you will need to only pass the correct open value to each accordion, and you will need to have your onClick function tell the parent which open value it needs to change.

Again, that seems like a lot of work for not a lot of utility. Better to just let the accordions keep track of whether they're open.

Upvotes: 1

Related Questions