Search code examples
reactjsreact-hooksrecoiljs

Recoil updating state when nothing has changed


So I recently started using my API endpoints through recoil using useRecoilStateLoadable, it works great but the one issue I am having is on my data table, I want it to do another API call when a user selects for example page 2 or searches something, currently this is what my atom and selector looks like:

const entriesPerPage : EntriesPerPage = {defaultValue: 10};

export const filterScorecard = atom<ScorecardSearchRequest>({
    key: 'filterScorecard',
    default: {
        Draw: 2,
        PageNumber: 1,
        PageSize: entriesPerPage.defaultValue!,
        SortColumnNames: [],
        SortDirections: [],
        SearchValue: '',
        ConsolidationValues: null,
        IsGroupDataManagement: false,
        OnlySearchLockedScorecards: null,
        ScorecardEndDate: null,
        ScorecardTypes: ["43"],
        Usernames: null,
    }
})

export const allScorecardSelector = selector<ScorecardLicense>({
    key: 'allScorecardSelector',
    get: async ({get}) => {
        
        const filterScorecardData = get(filterScorecard);
        console.log(filterScorecard)
        try{
            const scorecardSearchResult = await BEEScorecardService.getUserScorecards(filterScorecardData);
            return scorecardSearchResult.data;
        }catch (error){
            console.error(error);
            return defaultObject;
        }
    }
})

export const allScorecardAtom = atom<ScorecardLicense>({
    key: 'allScorecardAtom',
    default: allScorecardSelector
})

I created the filterScorecard method to re trigger my API if state changes, and in my component, in the useEffect my code looks like this:

    const [entriesPerPage, setEntriesPerPage] = useState<EntriesPerPage>({ defaultValue: 10 });

    const [pageNumber, setPageNumber] = useState<number>(1);
    const [searchValue, setSearchValue] = useState<string>('');
    const [sortColumnNames, setSortColumnNames] = useState<string[]>([]);
    const [sortDirections, setSortDirections] = useState<SortDirections[]>([]);
    useEffect(() => {
            setFilterScorecard(prevState => ({
                ...prevState,
                pageNumber,
                entriesPerPage,
                searchValue,
                sortColumnNames,
                sortDirections
            }))
        }, [entriesPerPage, pageNumber, searchValue, sortColumnNames, sortDirections]);

But What I am noticing is that every time I navigate away and come back the API is getting triggered when its not supposed to be, if I remove the use effect and I navigate away and come back then the API doesnt load and it shows the correct data, but I am unable to search or go to the next page because of course the filterScorecard method isnt being called anymore. I am trying to figure out what I need to do in order for it not to do API calls if nothing in the state has changed.

UPDATE

I have Created a code Sandbox to show you the issue, if you click on about then it triggers the API, but if you click on home and about again it still triggers the API request and it should not be because nothing has changed in state. Only if I click Next page should it actually call the API again

Code Sandbox


Solution

  • The reason the API request is sent every time you navigate to the component is the async selector dependency is changing every time you mount the component.

    If any dependencies change, the selector will be re-evaluated and execute a new query.

    https://recoiljs.org/docs/guides/asynchronous-data-queries#asynchronous-example

    What is a dependency of a selector?

    const selector = selector({
      key: "getAllSelector",
      get: async ({ get }) => {
        const filterScorecardData = get(filterScorecard);  <-- this is a dependency
    
        const res = await fetch(`https://my-api`);
      }
    });
    

    Every time your component mounts, you update the filterScorecard state.

    ...
    useEffect(() => {
      setFilterScorecard(prevState => ({
        ...prevState,
        pageNumber,
        etc.,
      }));
    

    Because you are updating the dependency state on every component mounting, the allScorecardSelector selector will be re-evaluated and executed on every component mounting.

    To prevent the API request from firing on ever component mount, you need to avoid updating the filterScorecard state on every component mount.