Accepted answer

In 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 commented; 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 component (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(() => {
      console.log("Dispatch the loadProducts action");
      //Update ref
      loadedRef.current =  true;
      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>{ => <h1>{product}</h1>)}</div>

Related Query

More Query from same tag