最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to use react hook useEffect only once in my case? - Stack Overflow

programmeradmin3浏览0评论

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
Add a ment  | 

2 Answers 2

Reset to default 3

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 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>
}
发布评论

评论列表(0)

  1. 暂无评论