I have buttons Apply Filters
and Reset Filters
. When I click Apply Filters
, it applies filters in the UI immediately, but when I click Reset Filters
, it only is clicked after being clicked two times. Here's the code:
import { useState, useEffect } from 'react';
import { getBusinesses } from '../api/DBRequests';
import categories from '../constants/categories';
const BusinessListPage = () => {
const [businesses, setBusinesses] = useState([]);
const [filters, setFilters] = useState({
category: '',
state: '',
minPrice: '',
maxPrice: '',
minRevenue: '',
maxRevenue: '',
});
const [sortBy, setSortBy] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchBusinesses();
}, []);
const fetchBusinesses = async () => {
setLoading(true);
try {
const adjustedSortBy =
sortBy === 'asc' ? 'asc' : sortBy === 'desc' ? 'desc' : '';
const businesses = await getBusinesses(adjustedSortBy, filters);
setBusinesses(businesses);
} catch (error) {
console.error('Error fetching businesses:', error.message);
} finally {
setLoading(false);
}
};
const handleInputChange = (e) => {
const { name, value } = e.target;
setFilters((prevFilters) => ({ ...prevFilters, [name]: value }));
};
const handleResetFilters = () => {
setFilters({
category: '',
state: '',
minPrice: '',
maxPrice: '',
minRevenue: '',
maxRevenue: '',
});
setSortBy('');
fetchBusinesses();
};
return (
<div className="container-fluid mt-4">
<div className="row">
<div className="col-md-3">
<div className="bg-light border p-3">
<h5>Filters</h5>
<div className="mb-3">
<label htmlFor="category" className="form-label">
Category
</label>
<select
id="category"
name="category"
className="form-control"
value={filters.category}
onChange={handleInputChange}
>
<option value="">All Categories</option>
{categories.map((category, index) => (
<option key={index} value={category}>
{category}
</option>
))}
</select>
</div>
<button
className="btn btn-primary btn-block"
onClick={fetchBusinesses}
>
Apply Filters
</button>
<button
className="btn btn-secondary btn-block mt-2"
onClick={handleResetFilters}
>
Reset Filters
</button>
</div>
</div>
{/* Businesses List Section */}
<div className="col-md-9">
{loading && <p className="d-block mx-auto">Loading</p>}
<div className="row">
{businesses.length === 0 && !loading && (
<div className="col-12">
<p className="mt-4 text-center">No businesses found.</p>
</div>
)}
{businesses.map((business) => (
<div className="col-md-4 mb-4" key={business._id}>
<div className="card h-100">
<div className="card-body">
<h5 className="card-title">{business.name}</h5>
</div>
<div className="card-footer">
<a
href={`/business/${business._id}`}
className="btn btn-primary btn-sm btn-block"
>
View Details
</a>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default BusinessListPage;
I have buttons Apply Filters
and Reset Filters
. When I click Apply Filters
, it applies filters in the UI immediately, but when I click Reset Filters
, it only is clicked after being clicked two times. Here's the code:
import { useState, useEffect } from 'react';
import { getBusinesses } from '../api/DBRequests';
import categories from '../constants/categories';
const BusinessListPage = () => {
const [businesses, setBusinesses] = useState([]);
const [filters, setFilters] = useState({
category: '',
state: '',
minPrice: '',
maxPrice: '',
minRevenue: '',
maxRevenue: '',
});
const [sortBy, setSortBy] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchBusinesses();
}, []);
const fetchBusinesses = async () => {
setLoading(true);
try {
const adjustedSortBy =
sortBy === 'asc' ? 'asc' : sortBy === 'desc' ? 'desc' : '';
const businesses = await getBusinesses(adjustedSortBy, filters);
setBusinesses(businesses);
} catch (error) {
console.error('Error fetching businesses:', error.message);
} finally {
setLoading(false);
}
};
const handleInputChange = (e) => {
const { name, value } = e.target;
setFilters((prevFilters) => ({ ...prevFilters, [name]: value }));
};
const handleResetFilters = () => {
setFilters({
category: '',
state: '',
minPrice: '',
maxPrice: '',
minRevenue: '',
maxRevenue: '',
});
setSortBy('');
fetchBusinesses();
};
return (
<div className="container-fluid mt-4">
<div className="row">
<div className="col-md-3">
<div className="bg-light border p-3">
<h5>Filters</h5>
<div className="mb-3">
<label htmlFor="category" className="form-label">
Category
</label>
<select
id="category"
name="category"
className="form-control"
value={filters.category}
onChange={handleInputChange}
>
<option value="">All Categories</option>
{categories.map((category, index) => (
<option key={index} value={category}>
{category}
</option>
))}
</select>
</div>
<button
className="btn btn-primary btn-block"
onClick={fetchBusinesses}
>
Apply Filters
</button>
<button
className="btn btn-secondary btn-block mt-2"
onClick={handleResetFilters}
>
Reset Filters
</button>
</div>
</div>
{/* Businesses List Section */}
<div className="col-md-9">
{loading && <p className="d-block mx-auto">Loading</p>}
<div className="row">
{businesses.length === 0 && !loading && (
<div className="col-12">
<p className="mt-4 text-center">No businesses found.</p>
</div>
)}
{businesses.map((business) => (
<div className="col-md-4 mb-4" key={business._id}>
<div className="card h-100">
<div className="card-body">
<h5 className="card-title">{business.name}</h5>
</div>
<div className="card-footer">
<a
href={`/business/${business._id}`}
className="btn btn-primary btn-sm btn-block"
>
View Details
</a>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default BusinessListPage;
Calling setFilters()
is asynchronous. Setting it to empty values and immediately calling fetchBusinesses()
doesn't give the filters state time to update.
I'd change fetchBusinesses()
to accept the filters
and sortBy
as parameters instead of relying on state. Then you can directly control the values; create an "empty filters" object then pass it to both fetchBusinesses()
and setFilters()
const initFilters = {
category: '',
state: '',
minPrice: '',
maxPrice: '',
minRevenue: '',
maxRevenue: '',
};
const initSortBy = '';
const BusinessListPage = () => {
const [filters, setFilters] = useState(initFilters);
const [sortBy, setSortBy] = useState(initSortBy);
// etc ...
// added params with default values coming from state
const fetchBusinesses = async (f = filters, s = sortBy) => {
setLoading(true);
try {
const adjustedSortBy =
s === 'asc' ? 'asc' : s === 'desc' ? 'desc' : '';
setBusinesses(await getBusinesses(adjustedSortBy, f));
} catch (error) {
console.error('Error fetching businesses:', error.message);
} finally {
setLoading(false);
}
};
const handleResetFilters = () => {
setFilters(initFilters);
setSortBy('');
fetchBusinesses(initFilters, ''); // pass in init values
};
https://react.dev/reference/react/StrictMode
React strict mode renders your object twice to catch potential bugs early on. This is why you need to "Reset Filters" twice to see it clear up.
Additionally, in useState, if you use strict mode "React will call your initializer function twice in order to help you find accidental impurities." - https://react.dev/reference/react/useState
Another possibility is that your function all "Reset filters" encounters an asynchronous process and waits for it to finish before setting the business.
const businesses = await getBusinesses(adjustedSortBy, filters);
setFilters()
is asynchronous. Setting it to empty values and immediately callingfetchBusinesses()
doesn't give thefilters
state time to update – Phil Commented Jan 30 at 3:37fetchBusinesses()
to accept thefilters
andsortBy
as parameters instead of relying on state. Then you can directly control the values; create an "empty filters" object then pass it to bothfetchBusinesses()
andsetFilters()
– Phil Commented Jan 30 at 3:47useEffect(fetchBusinesses, [appliedFilters])
– Phil Commented Jan 30 at 4:10