How do I set the result of an API call as the default value for a Recoil atom?
Asked Answered
B

3

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?

Barbershop answered 4/8, 2020 at 22:7 Comment(0)
O
17

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
})
Oval answered 15/9, 2020 at 14:56 Comment(3)
here is a stackblitz example: stackblitz.com/edit/react-ts-56yusb?file=App.tsx – Atrice
This is so wonderful that this works, recoil really seems to have gotten a lot right about what's needed for state management – Kanal
This works great on a simple example I tried. React is throwing an error ( looks more of warning ) however: preview-70b26ed60029a.js:2 Warning: Can't perform a React state update on a component that hasn't mounted yet. This indicates that you have a side-effect in your render function that asynchronously later calls tries to update the component. Move this work to useEffect instead. – Cheeks
T
2

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.

Tang answered 9/6, 2022 at 17:16 Comment(0)
S
-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 πŸ™‚

Simoniac answered 9/8, 2020 at 14:15 Comment(1)
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)? – Yacketyyak

© 2022 - 2024 β€” McMap. All rights reserved.