Select only a card at a time on click in reactjs

Viewed 586

I have a react component which has some cards, When I click the plus icon in the card, it would expand and show some data for 30sec and then the data will disappear and on click it will reappear again, here is the component

import React from "react";
import { FaPlus } from "react-icons/fa";
import useTimeout from "../../../common/useTimeout";
import { Modal } from "../../../common/Modal";
import { ToggleState } from "../../../common/Toggle";
import { BsSearch } from "react-icons/bs";
import AdvancedFilter from "./AdvancedFilter";

export default function CitiesList({ cities }: { cities: any }): JSX.Element {
  const [filter, setFilter] = React.useState("");
  const [sortType, setSortType] = React.useState("");
  const [selectedCity, setSelectedCity] = React.useState<any | null>(null);

  console.log(filter);

  const sorted = cities.sort((a: { name: string }, b: { name: any }) => {
    const isReversed = sortType === "asc" ? 1 : -1;
    return isReversed * a.name.localeCompare(b.name);
  });

  const onSort = (sortType: React.SetStateAction<string>) => {
    console.log("changed");
    setSortType(sortType);
  };

  const [showMeta, setShowMeta] = React.useState(false);
  const handleClick = () => setShowMeta(true);

  const getSelectedCity = (selectedCity: any) => {
    setSelectedCity(selectedCity);
    console.log("SELECTED CITY", selectedCity);
  };

  const [visible, setVisible] = React.useState(true);

  const hide = () => setVisible(false);

  useTimeout(hide, 30000);

  console.log("CITIES", cities);

  console.log({ selectedCity });

  return (
    <div style={{ marginTop: "3rem" }}>
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          marginBottom: "20px",
        }}
      >
        <div>List of cities</div>
        <div style={{ display: "flex", alignItems: "center" }}>
          <div style={{ marginRight: "1rem" }}>
            <ToggleState
              render={({ isOpen, open, close }) => {
                return (
                  <>
                    <button
                      type="button"
                      className="btn btn-primary"
                      onClick={() => {
                        isOpen ? close() : open();
                      }}
                    >
                      Advanced Filter
                    </button>
                    <Modal
                      isActive={isOpen}
                      modalContentWidth={"30%"}
                      header={() => "Advanced Filter"}
                      close={() => close()}
                      renderBody={() => {
                        return <AdvancedFilter close={() => close()} />;
                      }}
                    ></Modal>
                  </>
                );
              }}
            />
          </div>

          <div style={{ position: "relative", marginRight: "1rem" }}>
            <input
              type="text"
              placeholder="Filter"
              name="namePrefix"
              style={{ padding: "0.35rem" }}
              onChange={(e: any) => {
                setFilter(e.target.value);
              }}
            />
            <div style={{ position: "absolute", top: "5px", right: "5px" }}>
              <BsSearch size="16" />
            </div>
          </div>

          <div style={{ width: "8rem" }}>
            <div className="btn-group">
              <button
                type="button"
                className="btn dropdown-toggle sort-button"
                data-bs-toggle="dropdown"
                aria-expanded="false"
              >
                {sortType === "asc"
                  ? "Ascending"
                  : sortType === "desc"
                  ? "Descending"
                  : "Select"}
              </button>
              <ul className="dropdown-menu sort-button">
                <li>
                  <button
                    className="dropdown-item"
                    type="button"
                    onClick={() => onSort("asc")}
                  >
                    Ascending
                  </button>
                </li>
                <li>
                  <button
                    className="dropdown-item"
                    type="button"
                    onClick={() => onSort("desc")}
                  >
                    Descending
                  </button>
                </li>
              </ul>
            </div>
          </div>
        </div>
      </div>
      <div>
        <div>
          <div className="row">
            {cities &&
              sorted.map((item: any, index: number) => (
                <div className="col-lg-3" key={index}>
                  <div
                    className="card"
                    style={{
                      textAlign: "center",
                      display: "flex",
                      justifyContent: "center",
                      paddingBottom: "1rem",
                      marginBottom: "1rem",
                      marginRight: "1rem",
                    }}
                  >
                    <div className="card-body">
                      <h5 className="card-title">{item.name}</h5>
                    </div>
                    {visible && showMeta ? (
                      <div>
                        <p>Longitude: {item.longitude}</p>
                        <p>Latitude: {item.latitude}</p>
                        <p>Population: {item.population}</p>
                        {/* <p>Time Zone: America</p> */}
                      </div>
                    ) : (
                      <div
                        onClick={() => {
                          handleClick();
                          getSelectedCity(item.id);
                        }}
                        style={{ cursor: "pointer" }}
                      >
                        <FaPlus size="18" />
                      </div>
                    )}
                  </div>
                </div>
              ))}
          </div>
        </div>
      </div>
      <div
        style={{ marginTop: "30px", display: "flex", justifyContent: "center" }}
      >
        {cities && cities.length > 10 ? (
          <button className="secondary-button">Load More</button>
        ) : (
          <p>There are no more cities</p>
        )}
      </div>
    </div>
  );
}

here is the useTimeout function

import { useEffect, useRef } from "react";

function useTimeout(callback: () => void, delay: number | null) {
  const savedCallback = useRef(callback);

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay === null) {
      return;
    }

    const id = setTimeout(() => savedCallback.current(), delay);

    return () => clearTimeout(id);
  }, [delay]);
}

export default useTimeout;

Now currently if I click on one card, all the cards opens and also after when the data disappears after 30sec it does not reappear on button click. I need to reload the page to do reappear data again.I need to solve 2 issues here: 1. how can I open one card only at a time on clicking on the icon, 2. how can I reappear data on button click without refreshing the page.

1 Answers

As I understand you, some information is

<p>Longitude: {item.longitude}</p>
<p>Latitude: {item.latitude}</p>
<p>Population: {item.population}</p>

You have global statement of showing this info for all cards. To reduce it, make new component which will have local state of this info:

import React from 'react'

const timeout = 15000

export const CityCard = ({item, visible, getSelectedCity}) => {

  const [showMeta, setShowMeta] = React.useState(false)
  
  const handleClick = React.useCallback(()=>{
    setShowMeta(true)
    setTimeout(()=>{
      setShowMeta(false)
    },timeout )
  }, [showMeta])

  return(
  <div className="col-lg-3" key={index}>
                  <div
                    className="card"
                    style={{
                      textAlign: "center",
                      display: "flex",
                      justifyContent: "center",
                      paddingBottom: "1rem",
                      marginBottom: "1rem",
                      marginRight: "1rem",
                    }}
                  >
                    <div className="card-body">
                      <h5 className="card-title">{item.name}</h5>
                    </div>
                    {visible && showMeta ? (
                      <div>
                        <p>Longitude: {item.longitude}</p>
                        <p>Latitude: {item.latitude}</p>
                        <p>Population: {item.population}</p>
                        {/* <p>Time Zone: America</p> */}
                      </div>
                    ) : (
                      <button
                        onClick={() => {
                          handleClick();
                          getSelectedCity(item.id);
                        }}
                        type='button'
                        disabled={showMeta}
                        style={{ cursor: "pointer" }}
                      >
                        <FaPlus size="18" />
                      </button>
                    )}
                  </div>
                </div>
  )
}

At the end, add this component to the CityList

Related