123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- import React, {
- createContext,
- useCallback,
- useContext,
- useEffect,
- useMemo,
- useState,
- } from 'react';
- import useApi from 'sentry/utils/useApi';
- import useOrganization from 'sentry/utils/useOrganization';
- import {OnboardingState} from 'sentry/views/onboarding/types';
- import OrganizationStore from './organizationStore';
- import {useLegacyStore} from './useLegacyStore';
- type PersistedStore = Readonly<{
- onboarding: Partial<OnboardingState> | null;
- }>;
- export const DefaultPersistedStore: PersistedStore = {
- onboarding: null,
- };
- export const DefaultLoadedPersistedStore: PersistedStore = {
- onboarding: {},
- };
- type PersistedStoreContextValue = [
- PersistedStore,
- React.Dispatch<React.SetStateAction<PersistedStore>>
- ];
- export const PersistedStoreContext = createContext<PersistedStoreContextValue | null>(
- null
- );
- function usePersistedStore(): PersistedStoreContextValue {
- const context = useContext(PersistedStoreContext);
- if (!context) {
- throw new Error('usePersistedStore was called outside of PersistedStoreProvider');
- }
- return context;
- }
- // Client-only state with TTL persisted on the server side in a redis store.
- export function PersistedStoreProvider(props: {children: React.ReactNode}) {
- const [state, setState] = useState<PersistedStore>(DefaultPersistedStore);
- const api = useApi();
- const {organization} = useLegacyStore(OrganizationStore);
- useEffect(() => {
- if (!organization) {
- return undefined;
- }
- let shouldCancelRequest = false;
- api
- .requestPromise(`/organizations/${organization.slug}/client-state/`)
- .then((response: PersistedStore) => {
- if (shouldCancelRequest) {
- return;
- }
- setState({...DefaultLoadedPersistedStore, ...response});
- })
- .catch(() => {
- setState(DefaultPersistedStore);
- });
- return () => {
- shouldCancelRequest = true;
- };
- }, [api, organization]);
- return (
- <PersistedStoreContext.Provider value={[state, setState]}>
- {props.children}
- </PersistedStoreContext.Provider>
- );
- }
- type UsePersistedCategory<T> = [T | null, (nextState: T | null) => void];
- export function usePersistedStoreCategory<C extends keyof PersistedStore>(
- category: C
- ): UsePersistedCategory<PersistedStore[C]> {
- const api = useApi({persistInFlight: true});
- const organization = useOrganization();
- const [state, setState] = usePersistedStore();
- const setCategoryState = useCallback(
- (val: PersistedStore[C] | null) => {
- setState(oldState => ({...oldState, [category]: val}));
- // If a state is set with null, we can clear it from the server.
- const endpointLocation = `/organizations/${organization.slug}/client-state/${category}/`;
- if (val === null) {
- api.requestPromise(endpointLocation, {
- method: 'DELETE',
- });
- return;
- }
- // Else we want to sync our state with the server
- api.requestPromise(endpointLocation, {
- method: 'PUT',
- data: val,
- });
- },
- [setState, category, organization, api]
- );
- const result = state[category];
- const stableState: UsePersistedCategory<PersistedStore[C]> = useMemo(() => {
- return [result ?? null, setCategoryState];
- }, [result, setCategoryState]);
- return stableState;
- }
|