Refactor hooks with useEntityRequest

This commit is contained in:
Alex Gleason 2023-03-23 16:22:15 -05:00
parent 45c12e9b65
commit aa7e2f6965
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
4 changed files with 26 additions and 41 deletions

View file

@ -1,10 +1,10 @@
import { useState } from 'react';
import { z } from 'zod'; import { z } from 'zod';
import { useApi, useAppDispatch } from 'soapbox/hooks'; import { useAppDispatch } from 'soapbox/hooks';
import { importEntities } from '../actions'; import { importEntities } from '../actions';
import { useEntityRequest } from './useEntityRequest';
import { parseEntitiesPath, toAxiosRequest } from './utils'; import { parseEntitiesPath, toAxiosRequest } from './utils';
import type { Entity } from '../types'; import type { Entity } from '../types';
@ -16,21 +16,18 @@ interface UseCreateEntityOpts<TEntity extends Entity = Entity> {
function useCreateEntity<TEntity extends Entity = Entity, Data = any>( function useCreateEntity<TEntity extends Entity = Entity, Data = any>(
expandedPath: ExpandedEntitiesPath, expandedPath: ExpandedEntitiesPath,
request: EntityRequest, entityRequest: EntityRequest,
opts: UseCreateEntityOpts<TEntity> = {}, opts: UseCreateEntityOpts<TEntity> = {},
) { ) {
const api = useApi();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [isLoading, setIsLoading] = useState<boolean>(false);
const { request, isLoading } = useEntityRequest();
const { entityType, listKey } = parseEntitiesPath(expandedPath); const { entityType, listKey } = parseEntitiesPath(expandedPath);
async function createEntity(data: Data, callbacks: EntityCallbacks<TEntity> = {}): Promise<void> { async function createEntity(data: Data, callbacks: EntityCallbacks<TEntity> = {}): Promise<void> {
setIsLoading(true);
try { try {
const result = await api.request({ const result = await request({
...toAxiosRequest(request), ...toAxiosRequest(entityRequest),
data, data,
}); });
@ -48,8 +45,6 @@ function useCreateEntity<TEntity extends Entity = Entity, Data = any>(
callbacks.onError(error); callbacks.onError(error);
} }
} }
setIsLoading(false);
} }
return { return {

View file

@ -1,9 +1,8 @@
import { useState } from 'react'; import { useAppDispatch, useGetState } from 'soapbox/hooks';
import { useApi, useAppDispatch, useGetState } from 'soapbox/hooks';
import { deleteEntities, importEntities } from '../actions'; import { deleteEntities, importEntities } from '../actions';
import { useEntityRequest } from './useEntityRequest';
import { toAxiosRequest } from './utils'; import { toAxiosRequest } from './utils';
import type { EntityCallbacks, EntityRequest } from './types'; import type { EntityCallbacks, EntityRequest } from './types';
@ -15,16 +14,13 @@ import type { EntityCallbacks, EntityRequest } from './types';
*/ */
function useDeleteEntity( function useDeleteEntity(
entityType: string, entityType: string,
request: EntityRequest, entityRequest: EntityRequest,
) { ) {
const api = useApi();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const getState = useGetState(); const getState = useGetState();
const [isLoading, setIsLoading] = useState<boolean>(false); const { request, isLoading } = useEntityRequest();
async function deleteEntity(entityId: string, callbacks: EntityCallbacks<string> = {}): Promise<void> { async function deleteEntity(entityId: string, callbacks: EntityCallbacks<string> = {}): Promise<void> {
setIsLoading(true);
// Get the entity before deleting, so we can reverse the action if the API request fails. // Get the entity before deleting, so we can reverse the action if the API request fails.
const entity = getState().entities[entityType]?.store[entityId]; const entity = getState().entities[entityType]?.store[entityId];
@ -33,10 +29,10 @@ function useDeleteEntity(
try { try {
// HACK: replace occurrences of `:id` in the URL. Maybe there's a better way? // HACK: replace occurrences of `:id` in the URL. Maybe there's a better way?
const axiosReq = toAxiosRequest(request); const axiosReq = toAxiosRequest(entityRequest);
axiosReq.url?.replaceAll(':id', entityId); axiosReq.url?.replaceAll(':id', entityId);
await api.request(axiosReq); await request(axiosReq);
// Success - finish deleting entity from the state. // Success - finish deleting entity from the state.
dispatch(deleteEntities([entityId], entityType)); dispatch(deleteEntities([entityId], entityType));
@ -54,8 +50,6 @@ function useDeleteEntity(
callbacks.onError(e); callbacks.onError(e);
} }
} }
setIsLoading(false);
} }
return { return {

View file

@ -2,13 +2,14 @@ import { useEffect } from 'react';
import z from 'zod'; import z from 'zod';
import { getNextLink, getPrevLink } from 'soapbox/api'; import { getNextLink, getPrevLink } from 'soapbox/api';
import { useApi, useAppDispatch, useAppSelector, useGetState } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector, useGetState } from 'soapbox/hooks';
import { filteredArray } from 'soapbox/schemas/utils'; import { filteredArray } from 'soapbox/schemas/utils';
import { realNumberSchema } from 'soapbox/utils/numbers'; import { realNumberSchema } from 'soapbox/utils/numbers';
import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess, invalidateEntityList } from '../actions'; import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess, invalidateEntityList } from '../actions';
import { parseEntitiesPath, toAxiosRequest } from './utils'; import { useEntityRequest } from './useEntityRequest';
import { parseEntitiesPath } from './utils';
import type { Entity, EntityListState } from '../types'; import type { Entity, EntityListState } from '../types';
import type { EntitiesPath, EntityRequest, EntitySchema, ExpandedEntitiesPath } from './types'; import type { EntitiesPath, EntityRequest, EntitySchema, ExpandedEntitiesPath } from './types';
@ -32,11 +33,11 @@ function useEntities<TEntity extends Entity>(
/** Tells us where to find/store the entity in the cache. */ /** Tells us where to find/store the entity in the cache. */
expandedPath: ExpandedEntitiesPath, expandedPath: ExpandedEntitiesPath,
/** API route to GET, eg `'/api/v1/notifications'`. If undefined, nothing will be fetched. */ /** API route to GET, eg `'/api/v1/notifications'`. If undefined, nothing will be fetched. */
request: EntityRequest, entityRequest: EntityRequest,
/** Additional options for the hook. */ /** Additional options for the hook. */
opts: UseEntitiesOpts<TEntity> = {}, opts: UseEntitiesOpts<TEntity> = {},
) { ) {
const api = useApi(); const { request } = useEntityRequest();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const getState = useGetState(); const getState = useGetState();
@ -61,7 +62,7 @@ function useEntities<TEntity extends Entity>(
dispatch(entitiesFetchRequest(entityType, listKey)); dispatch(entitiesFetchRequest(entityType, listKey));
try { try {
const response = await api.request(toAxiosRequest(req)); const response = await request(req);
const schema = opts.schema || z.custom<TEntity>(); const schema = opts.schema || z.custom<TEntity>();
const entities = filteredArray(schema).parse(response.data); const entities = filteredArray(schema).parse(response.data);
const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']); const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']);
@ -82,7 +83,7 @@ function useEntities<TEntity extends Entity>(
}; };
const fetchEntities = async(): Promise<void> => { const fetchEntities = async(): Promise<void> => {
await fetchPage(request, true); await fetchPage(entityRequest, true);
}; };
const fetchNextPage = async(): Promise<void> => { const fetchNextPage = async(): Promise<void> => {
@ -112,7 +113,7 @@ function useEntities<TEntity extends Entity>(
if (isInvalid || isUnset || isStale) { if (isInvalid || isUnset || isStale) {
fetchEntities(); fetchEntities();
} }
}, [request, isEnabled]); }, [entityRequest, isEnabled]);
return { return {
entities, entities,

View file

@ -1,11 +1,11 @@
import { useEffect, useState } from 'react'; import { useEffect } from 'react';
import z from 'zod'; import z from 'zod';
import { useApi, useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { importEntities } from '../actions'; import { importEntities } from '../actions';
import { toAxiosRequest } from './utils'; import { useEntityRequest } from './useEntityRequest';
import type { Entity } from '../types'; import type { Entity } from '../types';
import type { EntitySchema, EntityPath, EntityRequest } from './types'; import type { EntitySchema, EntityPath, EntityRequest } from './types';
@ -20,10 +20,10 @@ interface UseEntityOpts<TEntity extends Entity> {
function useEntity<TEntity extends Entity>( function useEntity<TEntity extends Entity>(
path: EntityPath, path: EntityPath,
request: EntityRequest, entityRequest: EntityRequest,
opts: UseEntityOpts<TEntity> = {}, opts: UseEntityOpts<TEntity> = {},
) { ) {
const api = useApi(); const { request, isLoading: isFetching } = useEntityRequest();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [entityType, entityId] = path; const [entityType, entityId] = path;
@ -33,21 +33,16 @@ function useEntity<TEntity extends Entity>(
const entity = useAppSelector(state => state.entities[entityType]?.store[entityId] as TEntity | undefined); const entity = useAppSelector(state => state.entities[entityType]?.store[entityId] as TEntity | undefined);
const [isFetching, setIsFetching] = useState(false);
const isLoading = isFetching && !entity; const isLoading = isFetching && !entity;
const fetchEntity = async () => { const fetchEntity = async () => {
setIsFetching(true);
try { try {
const response = await api.request(toAxiosRequest(request)); const response = await request(entityRequest);
const entity = schema.parse(response.data); const entity = schema.parse(response.data);
dispatch(importEntities([entity], entityType)); dispatch(importEntities([entity], entityType));
} catch (e) { } catch (e) {
// do nothing // do nothing
} }
setIsFetching(false);
}; };
useEffect(() => { useEffect(() => {