8

I'm trying to set the default value for an atom in RecoilJS as the value of an asynchronous API call. I'm doing this so that when a certain React component renders it will have all of the current sets in the database without having to call useEffect(). Here's my code...

const sets = (async () => await setService.getAll())()

export const setsState = atom({
  key: 'sets',
  default: sets
})

Here's the error I'm getting...

index.js:1 Warning: Cannot update a component (`Batcher`) while rendering a different component (`App`). To locate the bad setState() call inside `App`, follow the stack trace as described in https://facebook.com/setstate-in-render
    in App (at src/index.js:10)
    in Router (created by BrowserRouter)
    in BrowserRouter (at src/index.js:9)
    in RecoilRoot (at src/index.js:8)

Is there something fundamentally wrong in the approach that I am taking? Should I be accomplishing this another way? Am I doing something wrong, or is this just a broken feature of Recoil?

jackpalaia
  • 81
  • 1
  • 3

3 Answers3

16

You can try creating a async selector and put into atom's default, like this:

const mySelector = selector({
  key: 'mySelector',
  get: async ({get}) => {
    return await setService.getAll()
  }
})

const myAtom = atom({
  key:'myAtom',
  default: mySelector
})
codebarz
  • 327
  • 1
  • 5
  • 28
  • here is a stackblitz example: https://stackblitz.com/edit/react-ts-56yusb?file=App.tsx – B45i Jun 20 '22 at 14:57
  • This is so wonderful that this works, recoil really seems to have gotten a lot right about what's needed for state management – chrismarx Jan 20 '23 at 14:45
1

Here is a Typescript example, inspired by this answer.

// Inside your React component

const val = useRecoilValue(myAtom);
const updateVal = useSetRecoilState(mySelector);

// Update data with API call on form submit, and append new data to atom state
const onFormSubmit: SubmitHandler<DataFormValues> = async (values) => {
    const createdData = await createDataWithApiCall();
    await updateData([createdData]);
};
// Inside recoil state file

export const mySelector = selector({
    key: 'mySelector',
    get: async (): Promise<Array<Data>> => {
        // Set default value to API result
        const apiData = await callApi();
        return apiData;
    },
    set: ({ set, get }, newData) => {
        // Update state w/ new appended values
        const currentState = get(myAtom);
        const newState = [...currentState, ...newData as Data[]];
        set(myAtom, newState);
    },
});

export const myAtom = atom({
    key: "myAtom",
    default: mySelector,
});

Context: Don't be required to refetch entire to-do list from API each time a user adds an item to a to-do list. Call the API to add the new item, and append the new item to recoil state. Useful when user wants to make edits as well.

Emre
  • 831
  • 11
  • 13
-1

the warning you're reporting is known by the Recoil maintainers and it will be fixed in the next releases.

NOT SUGGESTED: If you want to hide it temporarily you could downgrade React to v16.12.0, it's up to you if you could live with this warning until the next Recoil release

NoriSte
  • 3,589
  • 1
  • 19
  • 18
  • I get this error and the component stop the render (error is thrown). Isn't there any workaround for such a simple requirement (fetch api)? – Naor Mar 30 '21 at 09:57