问题
I just started learning React 2 weeks ago so I am still finding some concepts difficult. I went from building a CRA to converting it to NextJS and opting for using context API with hooks for state management. I am working on filtering right now for a list of articles using a dropdown. When I select a certain type of article, the list filters correctly, but as I go back to the dropdown, only the original option is there along with the current option. When I click on the original option, all the list items disappear. I want to prevent the options in the dropdown from changing while also allowing the filtering to work at the same time as other filters are being set (e.g. sort filter by city AND filter by article feature).
In my code, I am struggling with getting the market/markets dropdown to function correctly.
This is my context.js
import React, { useState, useReducer, createContext } from "react";
import { articles } from "./data";
// Create Context Object
const StateContext = React.createContext([{}, () => {}]);
// Create a provider for components to consume and subscribe to changes
const StateProvider = (props) => {
const [state, setState] = useState({
list: articles,
market: "All",
isLoading: true,
});
return (
<StateContext.Provider value={[state, setState]}>
{props.children}
</StateContext.Provider>
);
};
export { StateContext, StateProvider };
FilterControls.js:
import React from "react";
import useFilter from "../hooks/useFilter";
const Controls = () => {
const { allEntries, market, handleChange } = useFilter();
// get all unique values
const getUnique = (items, value) => {
return [...new Set(items.map((item) => item[value]))];
};
let markets = getUnique(allEntries, "market");
// add all
markets = ["All", ...markets];
// map to jsx
markets = markets.map((item, index) => {
return (
<option value={item} key={index}>
{item}
</option>
);
});
return (
<>
<div>
<section className="filter-container">
{/* select market */}
<div className="form-group">
<label htmlFor="market">Housing Market</label>
<select
name="market"
id="market"
value={market}
className="form-control drop-down"
onChange={handleChange}
>
{markets}
</select>
</div>
{/* end of select market */}
</section>
useFilter.js:
import { useContext } from "react";
import { StateContext } from "../context";
const StateFilter = () => {
const [state, setState] = useContext(StateContext);
let allEntries = formatData(state.list);
let featuredEntries = allEntries.filter((e) => e.featured === true);
let percentageOffMax = Math.max(
...allEntries.map((item) => item.percentageOff)
);
let avgppMax = Math.max(...allEntries.map((item) => item.avgpp));
function formatData(items) {
let tempItems = items.map((e) => {
let id = e.id;
let images = e.images;
let singleEntry = { ...e, images, id };
return singleEntry;
});
return tempItems.sort((a, b) => a.avgpp - b.avgpp);
}
function sortFromHighestToLowestPrices(items) {
let tempItems = items.map((e) => {
let id = e.id;
let images = e.images;
let singleEntry = { ...e, images, id };
return singleEntry;
});
return tempItems.sort((a, b) => b.avgpp - a.avgpp);
}
let highestToLowest = sortFromHighestToLowestPrices(state.list);
// console.log(state.market);
// console.log(state);
// console.log(allEntries.filter((e) => e.market === "Homeowners"));
function handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = event.target.name;
console.log(value);
let ftest = allEntries.filter((e) => e.market === value);
setState((state) => ({ ...state, list: ftest, [name]: value }));
// state.filterItems();
// setState((state) =>({...state.market}))
// {
// [name]: value,
// },
// this.filterItems
// );
console.log(state);
}
// allEntries.filter((e) => e.market === value);
function filterItems() {
console.log(state.list);
let { allEntries } = state.list;
// let tempEntries = [...allEntries].sort((a, b) => b.avgpp - a.avgpp);
// if (market !== "All") {
// tempEntries = tempEntries.filter((e) => e.market === market);
// }
}
console.log(filterItems());
return {
isLoading: state.isLoading,
allEntries,
featuredEntries,
sortedEntries: allEntries,
percentageOff: percentageOffMax,
percentageOffMax,
avgppMax,
handleChange,
filterItems,
};
};
export default StateFilter;
And articles.js where everything is displayed:
import React, { useContext } from "react";
import Entry from "../components/Entry";
import Title from "../components/Title";
import { StateContext } from "../context";
import FilterControls from "../components/FilterControls";
import useFilter from "../hooks/useFilter";
const FullList = () => {
const { allEntries, showAdelphi, isLoading } = useFilter();
return (
<>
<section className="all-entries">
<Title title="Featured Entries" />
<FilterControls />
<div className="all-entries-center">
{allEntries.map((e) => (
<Entry key={e.id} entry={e} />
))}
</div>
</section>
回答1:
The reason this was not working was because there was no temporary list created. Therefore, when an option item was selected, the state was being replaced with one that didn't have the other item(s). Since the dropdown is feeding off of the state, the options disappear as the number of items in the original list are reduced.
To resolve this issue, I instead created a temporary list that would be adjusted based on the filters set while preserving the original list for reference.
The sorted list is now the one that displays the list in my articles page:
<section className="all-entries">
<Title title="Featured Entries" />
<FilterControls />
<div className="all-entries-center">
{state.sortedList.map((e) => (
<Entry key={e.id} entry={e} />
))}
</div>
</section>
And now, in my filter control, the markets are pulled from the original list:
let markets = getUnique(list, "market");
// add all
markets = ["All", ...markets];
// map to jsx
markets = markets.map((item, index) => {
return (
<option value={item} key={index}>
{item}
</option>
);
});
来源:https://stackoverflow.com/questions/61111923/losing-dropdown-select-options-upon-filtering-using-react-context-api