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.
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"/>