Add React Query

alex-chats
Justin 2022-08-02 09:20:07 -04:00
rodzic b7abb11ca2
commit f16da850fd
6 zmienionych plików z 172 dodań i 48 usunięć

Wyświetl plik

@ -202,9 +202,7 @@ export const rememberAuthAccount = (accountUrl: string) =>
export const loadCredentials = (token: string, accountUrl: string) =>
(dispatch: AppDispatch) => dispatch(rememberAuthAccount(accountUrl))
.then(() => {
dispatch(verifyCredentials(token, accountUrl));
})
.then(() => dispatch(verifyCredentials(token, accountUrl)))
.catch(() => dispatch(verifyCredentials(token, accountUrl)));
/** Trim the username and strip the leading @. */

Wyświetl plik

@ -17,6 +17,7 @@ import * as BuildConfig from 'soapbox/build_config';
import GdprBanner from 'soapbox/components/gdpr-banner';
import Helmet from 'soapbox/components/helmet';
import LoadingScreen from 'soapbox/components/loading-screen';
import { AuthProvider, useAuth } from 'soapbox/contexts/auth-context';
import AuthLayout from 'soapbox/features/auth_layout';
import EmbeddedStatus from 'soapbox/features/embedded-status';
import PublicLayout from 'soapbox/features/public_layout';
@ -40,6 +41,7 @@ import {
} from 'soapbox/hooks';
import MESSAGES from 'soapbox/locales/messages';
import { queryClient } from 'soapbox/queries/client';
import { useAxiosInterceptors } from 'soapbox/queries/client';
import { useCachedLocationHandler } from 'soapbox/utils/redirect';
import { generateThemeCss } from 'soapbox/utils/theme';
@ -63,7 +65,7 @@ const loadInitial = () => {
// @ts-ignore
return async(dispatch, getState) => {
// Await for authenticated fetch
await dispatch(fetchMe());
const account = await dispatch(fetchMe());
// Await for feature detection
await dispatch(loadInstance());
// Await for configuration
@ -76,12 +78,15 @@ const loadInitial = () => {
if (pepeEnabled && !state.me) {
await dispatch(fetchVerificationConfig());
}
return account;
};
};
/** Highest level node with the Redux store. */
const SoapboxMount = () => {
useCachedLocationHandler();
const me = useAppSelector(state => state.me);
const instance = useAppSelector(state => state.instance);
const account = useOwnAccount();
@ -204,6 +209,9 @@ interface ISoapboxLoad {
/** Initial data loader. */
const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
const dispatch = useAppDispatch();
const { setAccount, token, baseApiUri } = useAuth();
useAxiosInterceptors(token, baseApiUri);
const me = useAppSelector(state => state.me);
const account = useOwnAccount();
@ -233,7 +241,8 @@ const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
// Load initial data from the API
useEffect(() => {
dispatch(loadInitial()).then(() => {
dispatch(loadInitial()).then((account) => {
setAccount(account);
setIsLoaded(true);
}).catch(() => {
setIsLoaded(true);
@ -292,13 +301,15 @@ const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
const Soapbox: React.FC = () => {
return (
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<SoapboxHead>
<SoapboxLoad>
<SoapboxMount />
</SoapboxLoad>
</SoapboxHead>
</QueryClientProvider>
<AuthProvider>
<QueryClientProvider client={queryClient}>
<SoapboxHead>
<SoapboxLoad>
<SoapboxMount />
</SoapboxLoad>
</SoapboxHead>
</QueryClientProvider>
</AuthProvider>
</Provider>
);
};

Wyświetl plik

@ -0,0 +1,77 @@
import React, {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { localState } from 'soapbox/reducers/auth';
import { parseBaseURL } from 'soapbox/utils/auth';
const AuthContext = createContext(null as any);
interface IAccount {
acct: string
avatar: string
avatar_static: string
bot: boolean
created_at: string
discoverable: boolean
display_name: string
emojis: string[]
fields: any // not sure
followers_count: number
following_count: number
group: boolean
header: string
header_static: string
id: string
last_status_at: string
location: string
locked: boolean
note: string
statuses_count: number
url: string
username: string
verified: boolean
website: string
}
export const AuthProvider: React.FC<any> = ({ children }: { children: React.ReactNode }) => {
const [account, setAccount] = useState<IAccount>();
const [token, setToken] = useState<string>();
const [baseApiUri, setBaseApiUri] = useState<string>();
const value = useMemo(() => ({
account,
baseApiUri,
setAccount,
token,
}), [account]);
useEffect(() => {
const cachedAuth: any = localState?.toJS();
if (cachedAuth?.me) {
setToken(cachedAuth.users[cachedAuth.me].access_token);
setBaseApiUri(parseBaseURL(cachedAuth.users[cachedAuth.me].url));
}
}, []);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
interface IAuth {
account: IAccount
baseApiUri: string
setAccount(account: IAccount): void
token: string
}
export const useAuth = (): IAuth => useContext(AuthContext);

Wyświetl plik

@ -1,5 +1,6 @@
'use strict';
import { QueryClientProvider } from '@tanstack/react-query';
import debounce from 'lodash/debounce';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { HotKeys } from 'react-hotkeys';
@ -119,6 +120,7 @@ import { WrappedRoute } from './util/react_router_helpers';
// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import 'soapbox/components/status';
import { queryClient } from 'soapbox/queries/client';
const EmptyPage = HomePage;
@ -648,51 +650,53 @@ const UI: React.FC = ({ children }) => {
};
return (
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
<div ref={node} style={style}>
<BackgroundShapes />
<QueryClientProvider client={queryClient}>
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
<div ref={node} style={style}>
<BackgroundShapes />
<div className='z-10 flex flex-col'>
<Navbar />
<div className='z-10 flex flex-col'>
<Navbar />
<Layout>
<Layout.Sidebar>
{!standalone && <SidebarNavigation />}
</Layout.Sidebar>
<Layout>
<Layout.Sidebar>
{!standalone && <SidebarNavigation />}
</Layout.Sidebar>
<SwitchingColumnsArea>
{children}
</SwitchingColumnsArea>
</Layout>
<SwitchingColumnsArea>
{children}
</SwitchingColumnsArea>
</Layout>
{me && floatingActionButton}
{me && floatingActionButton}
<BundleContainer fetchComponent={UploadArea}>
{Component => <Component active={draggingOver} onClose={closeUploadModal} />}
</BundleContainer>
<BundleContainer fetchComponent={UploadArea}>
{Component => <Component active={draggingOver} onClose={closeUploadModal} />}
</BundleContainer>
{me && (
<BundleContainer fetchComponent={SidebarMenu}>
{me && (
<BundleContainer fetchComponent={SidebarMenu}>
{Component => <Component />}
</BundleContainer>
)}
{me && features.chats && !mobile && (
<BundleContainer fetchComponent={ChatPanes}>
{Component => <Component />}
</BundleContainer>
)}
<ThumbNavigation />
<BundleContainer fetchComponent={ProfileHoverCard}>
{Component => <Component />}
</BundleContainer>
)}
{me && features.chats && !mobile && (
<BundleContainer fetchComponent={ChatPanes}>
<BundleContainer fetchComponent={StatusHoverCard}>
{Component => <Component />}
</BundleContainer>
)}
<ThumbNavigation />
<BundleContainer fetchComponent={ProfileHoverCard}>
{Component => <Component />}
</BundleContainer>
<BundleContainer fetchComponent={StatusHoverCard}>
{Component => <Component />}
</BundleContainer>
</div>
</div>
</div>
</HotKeys>
</HotKeys>
</QueryClientProvider>
);
};

Wyświetl plik

@ -1,4 +1,38 @@
import { QueryClient } from '@tanstack/react-query';
import axios, { AxiosRequestConfig } from 'axios';
import * as BuildConfig from 'soapbox/build_config';
import { isURL } from 'soapbox/utils/auth';
const maybeParseJSON = (data: string) => {
try {
return JSON.parse(data);
} catch (Exception) {
return data;
}
};
const API = axios.create({
transformResponse: [maybeParseJSON],
});
const useAxiosInterceptors = (token: string, baseApiUri: string) => {
API.interceptors.request.use(
async (config: AxiosRequestConfig) => {
if (token) {
config.baseURL = isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : baseApiUri;
// eslint-disable-next-line no-param-reassign
config.headers = {
...config.headers,
Authorization: (token ? `Bearer ${token}` : null) as string | number | boolean | string[] | undefined,
} as any;
}
return config;
},
(error) => Promise.reject(error),
);
};
const queryClient = new QueryClient({
defaultOptions: {
@ -10,4 +44,4 @@ const queryClient = new QueryClient({
},
});
export { queryClient };
export { API as default, queryClient, useAxiosInterceptors };

Wyświetl plik

@ -38,7 +38,7 @@ const getSessionUser = () => {
};
const sessionUser = getSessionUser();
const localState = fromJS(JSON.parse(localStorage.getItem(STORAGE_KEY)));
export const localState = fromJS(JSON.parse(localStorage.getItem(STORAGE_KEY)));
// Checks if the user has an ID and access token
const validUser = user => {