recoil#atomFamily TypeScript Examples

The following examples show how to use recoil#atomFamily. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: groups.ts    From abrechnung with GNU Affero General Public License v3.0 6 votes vote down vote up
groupById = atomFamily({
    key: "groupById",
    default: selectorFamily({
        key: "groupById/default",
        get:
            (groupID) =>
            async ({ get }) => {
                const groups = get(groupList);
                return groups.find((group) => group.id === groupID);
            },
    }),
})
Example #2
Source File: groups.ts    From abrechnung with GNU Affero General Public License v3.0 6 votes vote down vote up
groupMembers = atomFamily<Array<GroupMember>, number>({
    key: "groupMembers",
    effects_UNSTABLE: (groupID) => [
        ({ setSelf }) => {
            setSelf(
                fetchMembers({ groupID: groupID })
                    .then((members) => sortMembers(members))
                    .catch((err) => toast.error(`error when fetching group members: ${err}`))
            );

            ws.subscribe("group_member", groupID, (subscription_type, { user_id, element_id }) => {
                if (element_id === groupID) {
                    fetchMembers({ groupID: element_id })
                        .then((result) => setSelf(sortMembers(result)))
                        .catch((err) => toast.error(`error when updating group members: ${err}`));
                }
            });
            // TODO: handle registration errors

            return () => {
                ws.unsubscribe("group_member", groupID);
            };
        },
    ],
})
Example #3
Source File: groups.ts    From abrechnung with GNU Affero General Public License v3.0 6 votes vote down vote up
groupInvites = atomFamily<Array<GroupInvite>, number>({
    key: "groupInvites",
    effects_UNSTABLE: (groupID) => [
        ({ setSelf }) => {
            setSelf(
                fetchInvites({ groupID: groupID }).catch((err) =>
                    toast.error(`error when fetching group invites: ${err}`)
                )
            );

            ws.subscribe("group_invite", groupID, (subscription_type, { invite_id, element_id }) => {
                if (element_id === groupID) {
                    fetchInvites({ groupID: element_id }).then((result) => setSelf(result));
                }
            });
            // TODO: handle registration errors

            return () => {
                ws.unsubscribe("group_invite", groupID);
            };
        },
    ],
})
Example #4
Source File: groups.ts    From abrechnung with GNU Affero General Public License v3.0 6 votes vote down vote up
groupLog = atomFamily<Array<GroupLog>, number>({
    key: "groupLog",
    effects_UNSTABLE: (groupID) => [
        ({ setSelf }) => {
            setSelf(
                fetchLog({ groupID: groupID }).catch((err) => toast.error(`error when fetching group log: ${err}`))
            );

            ws.subscribe("group_log", groupID, (subscription_type, { log_id, element_id }) => {
                if (element_id === groupID) {
                    fetchLog({ groupID: element_id }).then((result) => setSelf(result));
                }
            });
            // TODO: handle registration errors

            return () => {
                ws.unsubscribe("group_log", groupID);
            };
        },
    ],
})
Example #5
Source File: transactions.ts    From abrechnung with GNU Affero General Public License v3.0 6 votes vote down vote up
pendingTransactionPositionChanges = atomFamily<LocalPositionChanges, number>({
    // transaction id -> pending changes
    key: "pendingTransactionPositionChanges",
    default: {
        modified: {}, // map of positions with server given ids
        added: {}, // map of positions with local id to content
        empty: {
            id: -1,
            name: "",
            price: 0,
            communist_shares: 0,
            usages: {},
            deleted: false,
        },
    },
    effects_UNSTABLE: (transactionID) => [localStorageEffect(`localTransactionPositionChanges-${transactionID}`)],
})
Example #6
Source File: customHooks.ts    From frames with Mozilla Public License 2.0 5 votes vote down vote up
hookChangeAtomFamily = atomFamily<any, string>({
    key: 'hookChangeAtomFamily',
    default: undefined
})
Example #7
Source File: mod-progress-state.ts    From ow-mod-manager with MIT License 5 votes vote down vote up
modProgressState = atomFamily<number, string | undefined>({
  key: 'ModProgress',
  default: 0,
})
Example #8
Source File: mod-progress-state.ts    From ow-mod-manager with MIT License 5 votes vote down vote up
modIsLoadingState = atomFamily<boolean, string | undefined>({
  key: 'ModIsLoading',
  default: false,
})
Example #9
Source File: accounts.ts    From abrechnung with GNU Affero General Public License v3.0 5 votes vote down vote up
groupAccounts = atomFamily<Array<Account>, number>({
    key: "groupAccounts",
    effects_UNSTABLE: (groupID) => [
        ({ setSelf, getPromise, node }) => {
            // TODO: handle fetch error
            setSelf(
                fetchAccounts({ groupID: groupID }).catch((err) => toast.error(`error when fetching accounts: ${err}`))
            );

            const fetchAndUpdateAccount = (currAccounts: Array<Account>, accountID: number, isNew: boolean) => {
                fetchAccount({ accountID: accountID })
                    .then((account) => {
                        if (isNew) {
                            // new account
                            setSelf([...currAccounts, account]);
                        } else {
                            setSelf(currAccounts.map((t) => (t.id === account.id ? account : t)));
                        }
                    })
                    .catch((err) => toast.error(`error when fetching account: ${err}`));
            };

            ws.subscribe(
                "account",
                groupID,
                (subscription_type, { account_id, element_id, revision_committed, revision_version }) => {
                    if (element_id === groupID) {
                        getPromise(node).then((currAccounts) => {
                            const currAccount = currAccounts.find((a) => a.id === account_id);
                            if (
                                currAccount === undefined ||
                                (revision_committed === null && revision_version > currAccount.version) ||
                                (revision_committed !== null &&
                                    (currAccount.last_changed === null ||
                                        DateTime.fromISO(revision_committed) >
                                            DateTime.fromISO(currAccount.last_changed)))
                            ) {
                                fetchAndUpdateAccount(currAccounts, account_id, currAccount === undefined);
                            }
                        });
                    }
                }
            );
            // TODO: handle registration errors

            return () => {
                ws.unsubscribe("account", groupID);
            };
        },
    ],
})
Example #10
Source File: transactions.ts    From abrechnung with GNU Affero General Public License v3.0 5 votes vote down vote up
groupTransactions = atomFamily<Array<TransactionBackend>, number>({
    key: "groupTransactions",
    effects_UNSTABLE: (groupID) => [
        ({ setSelf, node, getPromise }) => {
            const fullFetchPromise = () => {
                return fetchTransactions({ groupID: groupID })
                    .then((result) => {
                        return result;
                    })
                    .catch((err) => {
                        toast.error(`error when fetching transactions: ${err}`);
                        return [];
                    });
            };

            // TODO: handle fetch error more properly than just showing error, e.g. through a retry or something
            setSelf(fullFetchPromise());

            const fetchAndUpdateTransaction = (
                currTransactions: Array<TransactionBackend>,
                transactionID: number,
                isNew: boolean
            ) => {
                fetchTransaction({ transactionID: transactionID })
                    .then((transaction) => {
                        if (isNew) {
                            setSelf([...currTransactions, transaction]);
                        } else {
                            setSelf(currTransactions.map((t) => (t.id === transaction.id ? transaction : t)));
                        }
                    })
                    .catch((err) => toast.error(`error when fetching transaction: ${err}`));
            };

            ws.subscribe(
                "transaction",
                groupID,
                (
                    subscription_type,
                    { element_id, transaction_id, revision_started, revision_committed, revision_version }
                ) => {
                    if (element_id === groupID) {
                        getPromise(node).then((currTransactions) => {
                            const currTransaction = currTransactions.find((t) => t.id === transaction_id);
                            if (
                                currTransaction === undefined ||
                                (revision_committed === null && revision_version > currTransaction.version) ||
                                (revision_committed !== null &&
                                    (currTransaction.last_changed === null ||
                                        DateTime.fromISO(revision_committed) >
                                            DateTime.fromISO(currTransaction.last_changed)))
                            ) {
                                fetchAndUpdateTransaction(
                                    currTransactions,
                                    transaction_id,
                                    currTransaction === undefined
                                );
                            }
                        });
                    }
                }
            );
            // TODO: handle registration errors

            return () => {
                ws.unsubscribe("transaction", groupID);
            };
        },
    ],
})
Example #11
Source File: transactions.ts    From abrechnung with GNU Affero General Public License v3.0 5 votes vote down vote up
pendingTransactionDetailChanges = atomFamily<LocalTransactionDetailChanges, number>({
    // transaction id -> pending changes
    key: "pendingTransactionDetailChanges",
    default: {},
    effects_UNSTABLE: (transactionID) => [localStorageEffect(`localTransactionChanges-${transactionID}`)],
})
Example #12
Source File: results.state.ts    From nextclade with MIT License 5 votes vote down vote up
analysisResultInternalAtom = atomFamily<NextcladeResult, string>({
  key: 'analysisResultSingle',
})
Example #13
Source File: index.spec.tsx    From recoil-persist with MIT License 4 votes vote down vote up
function testPersistWith(storage: TestableStorage) {
  describe(`Storage: ${storage.name}`, () => {
    const testKey = 'test-key'
    const { persistAtom } = recoilPersist({ key: testKey, storage })

    const getStateValue = () => {
      const value = storage.getState()[testKey]
      if (value == undefined) {
        return {}
      }
      return JSON.parse(value)
    }

    const getAtomKey = (key: string) => {
      return `${storage.name}_${key}`
    }

    const counterState = atom({
      key: getAtomKey('count'),
      default: 0,
      effects_UNSTABLE: [persistAtom],
    })

    const counterFamily = atomFamily({
      key: getAtomKey('countFamily'),
      default: 0,
      effects_UNSTABLE: [persistAtom],
    })

    const counterState4 = atom({
      key: getAtomKey('count4'),
      default: 0,
    })

    function Demo() {
      const [count, setCount] = useRecoilState(counterState)
      const [count2, setCount2] = useRecoilState(counterFamily('2'))
      const [count3, setCount3] = useRecoilState(counterFamily('3'))
      const [count4, setCount4] = useRecoilState(counterState4)
      const resetCounter3 = useResetRecoilState(counterFamily('3'))
      const updateMultiple = useRecoilCallback(({ set }) => () => {
        set(counterState, 10)
        set(counterFamily('2'), 10)
      })
      return (
        <div>
          <p data-testid="count-value">{count}</p>
          <p data-testid="count2-value">{count2}</p>
          <p data-testid="count3-value">{count3}</p>
          <p data-testid="count4-value">{count4}</p>
          <button
            data-testid="count-increase"
            onClick={() => setCount(count + 1)}
          >
            Increase
          </button>
          <button
            data-testid="count2-increase"
            onClick={() => setCount2(count2 + 1)}
          >
            Increase 2
          </button>
          <button
            data-testid="count3-increase"
            onClick={() => setCount3(count3 + 1)}
          >
            Increase 3
          </button>
          <button
            data-testid="count4-increase"
            onClick={() => setCount4(count4 + 1)}
          >
            Increase 4
          </button>
          <button
            data-testid="count3-null-value"
            onClick={() => setCount3(null)}
          >
            Set value to null
          </button>
          <button
            data-testid="count3-undefined-value"
            onClick={() => setCount3(undefined)}
          >
            Set value to undefined
          </button>
          <button data-testid="count3-reset" onClick={() => resetCounter3()}>
            Reset count 3
          </button>
          <button
            data-testid="update-multiple"
            onClick={() => updateMultiple()}
          >
            Update multiple
          </button>
        </div>
      )
    }

    beforeEach(() => {
      console.error = jest.fn()
    })

    afterEach(() => {
      storage.clear()
      jest.restoreAllMocks()
    })

    it('should be removed from storage on reset', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('count3-increase'))
      await waitFor(() =>
        expect(getByTestId('count3-value').innerHTML).toBe('1'),
      )

      expect(getStateValue()).toStrictEqual({
        [getAtomKey('countFamily__"3"')]: 1,
      })

      fireEvent.click(getByTestId('count3-reset'))
      await waitFor(() =>
        expect(getByTestId('count3-value').innerHTML).toBe('0'),
      )

      expect(getStateValue()).toStrictEqual({})
    })

    it('should handle reset atom with default value', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('count3-reset'))
      await waitFor(() =>
        expect(getByTestId('count3-value').innerHTML).toBe('0'),
      )

      expect(getStateValue()).toStrictEqual({})
    })

    it('should update storage with null', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('count3-null-value'))
      await waitFor(() =>
        expect(getByTestId('count3-value').innerHTML).toBe(''),
      )

      expect(getStateValue()).toStrictEqual({
        [getAtomKey('countFamily__"3"')]: null,
      })
    })

    it('should update storage with undefined', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('count3-undefined-value'))
      await waitFor(() =>
        expect(getByTestId('count3-value').innerHTML).toBe(''),
      )

      expect(getStateValue()).toStrictEqual({})
    })

    it('should update storage', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('count-increase'))
      await waitFor(() =>
        expect(getByTestId('count-value').innerHTML).toBe('1'),
      )

      expect(getStateValue()).toStrictEqual({
        [getAtomKey('count')]: 1,
      })
    })

    it('should update storage if using atomFamily', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )
      fireEvent.click(getByTestId('count2-increase'))
      await waitFor(() =>
        expect(getByTestId('count2-value').innerHTML).toBe('1'),
      )
      fireEvent.click(getByTestId('count3-increase'))
      await waitFor(() =>
        expect(getByTestId('count3-value').innerHTML).toBe('1'),
      )

      expect(getStateValue()).toStrictEqual({
        [getAtomKey('countFamily__"2"')]: 1,
        [getAtomKey('countFamily__"3"')]: 1,
      })
    })

    it('should not persist atom with no effect', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('count4-increase'))
      await waitFor(() =>
        expect(getByTestId('count4-value').innerHTML).toBe('1'),
      )

      expect(storage.getState()[testKey]).toBeUndefined()
    })

    it('should read state from storage', async () => {
      await storage.setItem(
        testKey,
        JSON.stringify({
          [getAtomKey('count')]: 1,
          [getAtomKey('countFamily__"2"')]: 1,
        }),
      )

      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      await waitFor(() =>
        expect(getByTestId('count-value').innerHTML).toBe('1'),
      )
      await waitFor(() =>
        expect(getByTestId('count2-value').innerHTML).toBe('1'),
      )
    })

    it('should use default value if not in storage', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      expect(getByTestId('count3-value').innerHTML).toBe('0')
    })

    it('should handle non jsonable object in storage', async () => {
      storage.setItem(testKey, 'test string')

      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('count-increase'))
      await waitFor(() =>
        expect(getByTestId('count-value').innerHTML).toBe('1'),
      )

      expect(getStateValue()).toStrictEqual({
        [getAtomKey('count')]: 1,
      })
    })

    it('should handle non jsonable object in state', async () => {
      let mock = jest.spyOn(JSON, 'stringify').mockImplementation(() => {
        throw Error('mock error')
      })

      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('count-increase'))
      await waitFor(() =>
        expect(getByTestId('count-value').innerHTML).toBe('1'),
      )
      expect(mock).toHaveBeenCalledTimes(1)
      expect(console.error).toHaveBeenCalledTimes(1)
    })

    it('should handle non-existent atom name stored in storage', async () => {
      storage.setItem(
        testKey,
        JSON.stringify({
          notExist: 'test value',
        }),
      )

      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      await waitFor(() =>
        expect(getByTestId('count-value').innerHTML).toBe('0'),
      )
    })

    it.skip('should handle updating multiple atomes', async () => {
      const { getByTestId } = render(
        <RecoilRoot>
          <Demo />
        </RecoilRoot>,
      )

      fireEvent.click(getByTestId('update-multiple'))
      await waitFor(() =>
        expect(getByTestId('count-value').innerHTML).toBe('10'),
      )

      await waitFor(() =>
        expect(getByTestId('count2-value').innerHTML).toBe('10'),
      )

      expect(getStateValue()).toStrictEqual({
        [getAtomKey('count')]: 10,
        [getAtomKey('countFamily__"2"')]: 10,
      })
    })
  })
}