Refactor hooks with useEntityRequest
This commit is contained in:
parent
45c12e9b65
commit
aa7e2f6965
4 changed files with 26 additions and 41 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
Loading…
Reference in a new issue