I think my question for a while seems to be a duplicate but i guess it is not, i have checked many questions that have same title but with different case
The problem is that i was forced to use react hook twice in my code and i even think to make them three but i feel that a bad practice and i am sure there is a work around or a solution for that
I have data that i want to initially fetch and i implemented that by first useEffect call, the data can be filtered or sorted later and i have filters checkboxes and a select for sort queries, checking a checkbox will filter data and selecting an option will sort it, i am using redux and react-redux connect method to create the state tree which includes filters and sortBy
//the first call
useEffect(() => {
loadProducts();
}, []);
//the second call
useEffect(() => {
loadProducts(filters, sortBy)
}, [filters, sortBy])
const mapStateToProps = (state) => ({
filters: state.filters,
sortBy: state.sortBy
});
const mapDispatchToProps = (dispatch) => ({
loadProducts: (filters, sortBy) => {
fetch('certain API').then(res => res.json()).then(json => {
dispatch(fetchProducts(json.products, filters, sortBy));
}).catch(err => 'error fetching data');
}
});
Now the first call is to initially retrieve data from API, loadProducts function doesn't need any arguments, and the second one is when doing filtering or sorting and the function in this case needs the arguments and it works when any of filters or sortBy get changed
I even think to make them three calls by dividing the second call into two calls like this:
useEffect(() => {
loadProducts(filters)
}, [filters])
useEffect(() => {
loadProducts(undefined, sortBy)
}, [sortBy])
and that's because i do not want to make filtering and sorting happening every time even when only one of them should work, because i think sorting will work also when the user only fiters data and vice versa.
This is my fetchProducts action:
import { filterProducts, sortProducts } from './../../util/util';
export const fetchProducts = (products, filters, sortBy) => {
if (filters && filters.length) {
products = filterProducts(products, filters);
}
if (sortBy) {
sortProducts(products, sortBy);
}
return {
type: FETCH_PRODUCTS,
payload: products
}
}
I think my question for a while seems to be a duplicate but i guess it is not, i have checked many questions that have same title but with different case
The problem is that i was forced to use react hook twice in my code and i even think to make them three but i feel that a bad practice and i am sure there is a work around or a solution for that
I have data that i want to initially fetch and i implemented that by first useEffect call, the data can be filtered or sorted later and i have filters checkboxes and a select for sort queries, checking a checkbox will filter data and selecting an option will sort it, i am using redux and react-redux connect method to create the state tree which includes filters and sortBy
//the first call
useEffect(() => {
loadProducts();
}, []);
//the second call
useEffect(() => {
loadProducts(filters, sortBy)
}, [filters, sortBy])
const mapStateToProps = (state) => ({
filters: state.filters,
sortBy: state.sortBy
});
const mapDispatchToProps = (dispatch) => ({
loadProducts: (filters, sortBy) => {
fetch('certain API').then(res => res.json()).then(json => {
dispatch(fetchProducts(json.products, filters, sortBy));
}).catch(err => 'error fetching data');
}
});
Now the first call is to initially retrieve data from API, loadProducts function doesn't need any arguments, and the second one is when doing filtering or sorting and the function in this case needs the arguments and it works when any of filters or sortBy get changed
I even think to make them three calls by dividing the second call into two calls like this:
useEffect(() => {
loadProducts(filters)
}, [filters])
useEffect(() => {
loadProducts(undefined, sortBy)
}, [sortBy])
and that's because i do not want to make filtering and sorting happening every time even when only one of them should work, because i think sorting will work also when the user only fiters data and vice versa.
This is my fetchProducts action:
import { filterProducts, sortProducts } from './../../util/util';
export const fetchProducts = (products, filters, sortBy) => {
if (filters && filters.length) {
products = filterProducts(products, filters);
}
if (sortBy) {
sortProducts(products, sortBy);
}
return {
type: FETCH_PRODUCTS,
payload: products
}
}
Share
Improve this question
edited Oct 26, 2019 at 6:11
Saher Elgendy
asked Oct 26, 2019 at 5:32
Saher ElgendySaher Elgendy
1,6194 gold badges17 silver badges32 bronze badges
3
-
1
seems like filtering/sorting happens on UI side. so what if make it part of
mapStateToProps
instead of action creator? – skyboyer Commented Oct 26, 2019 at 6:09 -
1
btw your
useEffect
without cleanup by now may run into race condition and may also lead UI to blink if you are able to send multiple requests one by one. – skyboyer Commented Oct 26, 2019 at 6:11 - 1 @skyboyer can you please explain more about how can i improve my code!! – Saher Elgendy Commented Oct 26, 2019 at 17:06
2 Answers
Reset to default 3In fetchProducts you are mutating products and that's not a good idea try products = sortProducts([...products], sortBy);
instead.
If the initial value of state.filters is []
and the initial value of sortBy is false or a working default sort then you only need one effect.
As skyboyer mented; if you do asynchronous processing when user changes filter or sorting then you should avoid race condition.
Here is how you could write the effect:
useEffect(() => {
const cancel = {current:false};
//add cancel, only dispatch fetchProducts when it's
// the latest result from user interaction
loadProducts(filters, sortBy, cancel);
return ()=>cancel.current=true;
//added loadProducts as an effect dependency
// the linter should have warned you about it
}, [filters, loadProducts, sortBy])
const mapDispatchToProps = (dispatch) => ({
loadProducts: (filters, sortBy, cancel) => {
fetch('certain API').then(res => res.json()).then(json => {
//only dispatch if this is the latest list of products
// if user changed something during fetching then don't
// set products
cancel.current || dispatch(fetchProducts(json.products, filters, sortBy));
}).catch(err => 'error fetching data');
}
});
If filters is initially []
and sortBy has initially a working value or is false then that should just work.
If you want to handle all the logic in a single useEffect
call, you can take out all the prop dependencies [filters, sortBy]
from useEffect.
useEffect(() => {
//This runs all the time a ponent (re)renders
});
Now inside the useEffect
you have to conditionally call the fetch actions, depending on the prop changes. You can use useRef
to track the previous value of props. A rough draft below.
function App(props){
const {products = [], filter, sortBy} = props;
const filterRef = useRef();
const sortRef = useRef();
const loadedRef = useRef(false);
// The effect runs all the time
useEffect(() => {
if(!loadedRef.current){
console.log("Dispatch the loadProducts action");
//Update ref
loadedRef.current = true;
}else{
if(sortBy !== sortRef.current){
console.log("Dispatch the filter action");
// Update ref
sortRef.current = sortBy;
// return after this line if you want to avoid next if condition;
}
if(filter !== filterRef.current){
console.log("Dispatch the sort action");
//Update ref
filterRef.current = filter;
}
}
});
return <div>{products.map(product => <h1>{product}</h1>)}</div>
}