Home > Software design >  Filter dropdown only works once - React
Filter dropdown only works once - React

Time:03-21

I'm trying to create a dropdown filter based on the Line Code (a value from API). The filter works but only once. When trying to filter for the second time, the list goes empty. I would like to have the filter work on other options as well.

CodeSandbox

export default function App() {
  const [trains, setTrains] = useState([]);
  const [color, setColor] = useState("");

  const handleSelect = (val) => {
    setColor(val);

    let filteredColors = trains.filter((item) => {
      return item.LineCode === val;
    });

    setTrains(filteredColors);
  };

  const fetchTrains = () => {
    let api =
      "https://api.wmata.com/TrainPositions/TrainPositions?contentType=json&api_key=e13626d03d8e4c03ac07f95541b3091b";

    fetch(api)
      .then((response) => response.json())
      .then((data) => setTrains(data.TrainPositions))
      .catch((error) => {
        console.log("Error fetching and parsing data", error);
      });
  };

  useEffect(() => {
    fetchTrains();
  }, []);

  return (
    <div className="App">
      <h1>Trains</h1>
      <LineColor trains={trains} changeOption={handleSelect} />
      <div className="card-container">
        {trains.map((item, index) => {
          return (
            <div className="card" key={index}>
              <p>Line Code:{item.LineCode}</p>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function LineColor(props) {
  const [color, setColor] = useState("");

  const handleSelect = (e) => {
    setColor(e.target.value);
    props.changeOption(e.target.value);
  };

  return (
    <div>
      <select value={color} onChange={handleSelect}>
        <option value="RD">RD</option>
        <option value="BL">BL</option>
        <option value="YL">YL</option>
        <option value="OR">OR</option>
        <option value="GR">GR</option>
        <option value="SV">SV</option>
        <option value="null">NULL</option>
      </select>
    </div>
  );
}

Please any help would be appreciated.

CodePudding user response:

Please try this.

useEffect(() => {
    fetchTrains();
  }, [trains]);

CodePudding user response:

I think it might actually be

  useEffect(() => {
    fetchTrains();
  }, [color]);

useEffect needs to know when to run.

CodePudding user response:

Use effect takes a dependencies which is mention in [] , so if any of this color or trains changes than useEffect will render the page . There can be more than a dependencies, [trains,color].

CodePudding user response:

For this, you should useMemo so that you can use the single source of truth trains and then compute adjustments to it (filtering, in this case).

For example, by changing your handleSelect to just set the color:

  const handleSelect = (val) => {
    setColor(val);
  };

You can then filter off of that color state, without losing your data in your trains state.


  const filteredTrains = useMemo(
    () =>
      color
        ? trains.filter((item) => {
            if (color === "null") {
              return item.LineCode === null;
            }
            return item.LineCode === color;
          })
        : trains,
    [color, trains]
  );

Then you can map using the new filteredTrains value to set up your cards:

        {filteredTrains.map((item, index) => {
          return (
            <div className="card" key={index}>
              <p>Line Code:{item.LineCode}</p>
            </div>
          );
        })}

Working example:

const { useEffect, useMemo, useState } = React;

function App() {
  const [trains, setTrains] = useState([]);
  useEffect(() => {
    const fetchTrains = () => {
      let api =
        "https://api.wmata.com/TrainPositions/TrainPositions?contentType=json&api_key=e13626d03d8e4c03ac07f95541b3091b";
      fetch(api)
        .then((response) => response.json())
        .then((data) => setTrains(data.TrainPositions))
        .catch((error) => {
          console.log("Error fetching and parsing data", error);
        });
    };
    fetchTrains();
  }, []);
  const [color, setColor] = useState("");

  const filteredTrains = useMemo(
    () =>
      color
        ? trains.filter((item) => {
            if (color === "null") {
              return item.LineCode === null;
            }
            return item.LineCode === color;
          })
        : trains,
    [color, trains]
  );
  const lines = useMemo(
    () => Array.from(new Set(trains.map((train) => train.LineCode))),
    [trains]
  );
  return (
    <div className="App">
      <h1>Trains</h1>
      <select value={color} onChange={e=>setColor(e.target.value)}>
        {lines.map(line=>(<option key={line} value={line||'null'}>{line||'NULL'}</option>))}
      </select>
      <div className="card-container">
        {filteredTrains.map((item, index) => {
          return (
            <div className="card" key={index}>
              <p>Line Code:{item.LineCode}</p>
            </div>
          );
        })}
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App />,
  rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"/>

  • Related