mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 21:06:42 +07:00
99 use swr (#100)
* 99: made node use SWR * 99: fixed comments for SWR node * 99: added error toast to useNodeFormFormik.ts
This commit is contained in:
parent
832386d39a
commit
c2d1c2bfc9
35 changed files with 366 additions and 413 deletions
34
src/utils/hooks/data/useCreateNode.ts
Normal file
34
src/utils/hooks/data/useCreateNode.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { useCallback } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
import { apiPostNode } from '~/redux/node/api';
|
||||
import { selectFlowNodes } from '~/redux/flow/selectors';
|
||||
import { flowSetNodes } from '~/redux/flow/actions';
|
||||
import { selectLabListNodes } from '~/redux/lab/selectors';
|
||||
import { labSetList } from '~/redux/lab/actions';
|
||||
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
export const useCreateNode = () => {
|
||||
const dispatch = useDispatch();
|
||||
const flowNodes = useShallowSelect(selectFlowNodes);
|
||||
const labNodes = useShallowSelect(selectLabListNodes);
|
||||
|
||||
return useCallback(
|
||||
async (node: INode) => {
|
||||
const result = await apiPostNode({ node });
|
||||
|
||||
// TODO: use another store here someday
|
||||
if (node.is_promoted) {
|
||||
const updatedNodes = [result.node, ...flowNodes];
|
||||
dispatch(flowSetNodes(updatedNodes));
|
||||
} else {
|
||||
const updatedNodes = [
|
||||
{ node: result.node, comment_count: 0, last_seen: node.created_at },
|
||||
...labNodes,
|
||||
];
|
||||
dispatch(labSetList({ nodes: updatedNodes }));
|
||||
}
|
||||
},
|
||||
[flowNodes, labNodes, dispatch]
|
||||
);
|
||||
};
|
|
@ -1,19 +1,29 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import useSWR from 'swr';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { ApiGetNodeResponse } from '~/redux/node/types';
|
||||
import { API } from '~/constants/api';
|
||||
import { api } from '~/utils/api';
|
||||
import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen';
|
||||
import { apiGetNode } from '~/redux/node/api';
|
||||
import { useCallback } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
|
||||
export const useGetNode = (id?: INode['id']) => {
|
||||
const { data, isValidating: isLoading } = useSWR<AxiosResponse<ApiGetNodeResponse>>(
|
||||
API.NODE.GET_NODE(id || ''),
|
||||
api.get
|
||||
export const useGetNode = (id: number) => {
|
||||
const { data, isValidating, mutate } = useSWR<ApiGetNodeResponse>(API.NODE.GET_NODE(id), () =>
|
||||
apiGetNode({ id })
|
||||
);
|
||||
|
||||
if (!id) {
|
||||
return { node: undefined, isLoading: false };
|
||||
}
|
||||
const update = useCallback(
|
||||
async (node?: Partial<INode>) => {
|
||||
if (!data?.node) {
|
||||
await mutate();
|
||||
return;
|
||||
}
|
||||
|
||||
return { node: data?.data.node, isLoading };
|
||||
await mutate({ node: { ...data.node, ...node } }, true);
|
||||
},
|
||||
[data, mutate]
|
||||
);
|
||||
|
||||
useOnNodeSeen(data?.node);
|
||||
|
||||
return { node: data?.node, isLoading: isValidating && !data, update };
|
||||
};
|
||||
|
|
43
src/utils/hooks/data/useUpdateNode.ts
Normal file
43
src/utils/hooks/data/useUpdateNode.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { useGetNode } from '~/utils/hooks/data/useGetNode';
|
||||
import { useCallback } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
import { apiPostNode } from '~/redux/node/api';
|
||||
import { selectFlowNodes } from '~/redux/flow/selectors';
|
||||
import { flowSetNodes } from '~/redux/flow/actions';
|
||||
import { selectLabListNodes } from '~/redux/lab/selectors';
|
||||
import { labSetList } from '~/redux/lab/actions';
|
||||
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
export const useUpdateNode = (id: number) => {
|
||||
const dispatch = useDispatch();
|
||||
const { update } = useGetNode(id);
|
||||
const flowNodes = useShallowSelect(selectFlowNodes);
|
||||
const labNodes = useShallowSelect(selectLabListNodes);
|
||||
|
||||
return useCallback(
|
||||
async (node: INode) => {
|
||||
const result = await apiPostNode({ node });
|
||||
|
||||
if (!update) {
|
||||
return;
|
||||
}
|
||||
|
||||
await update(result.node);
|
||||
|
||||
// TODO: use another store here someday
|
||||
if (node.is_promoted) {
|
||||
const updatedNodes = flowNodes.map(item =>
|
||||
item.id === result.node.id ? result.node : item
|
||||
);
|
||||
dispatch(flowSetNodes(updatedNodes));
|
||||
} else {
|
||||
const updatedNodes = labNodes.map(item =>
|
||||
item.node.id === result.node.id ? { ...item, node: result.node } : item
|
||||
);
|
||||
dispatch(labSetList({ nodes: updatedNodes }));
|
||||
}
|
||||
},
|
||||
[update, flowNodes, dispatch, labNodes]
|
||||
);
|
||||
};
|
|
@ -14,7 +14,7 @@ export const useFullNode = (id: string) => {
|
|||
} = useShallowSelect(selectNode);
|
||||
|
||||
useLoadNode(id);
|
||||
useOnNodeSeen(node);
|
||||
// useOnNodeSeen(node);
|
||||
|
||||
return { node, comments, commentsCount, lastSeenCurrent, isLoading, isLoadingComments };
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useEffect } from 'react';
|
||||
import { nodeGotoNode, nodeSetCurrent } from '~/redux/node/actions';
|
||||
import { nodeGotoNode } from '~/redux/node/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { EMPTY_NODE } from '~/redux/node/constants';
|
||||
|
||||
// useLoadNode loads node on id change
|
||||
export const useLoadNode = (id: any) => {
|
||||
|
@ -9,9 +8,5 @@ export const useLoadNode = (id: any) => {
|
|||
|
||||
useEffect(() => {
|
||||
dispatch(nodeGotoNode(parseInt(id, 10), undefined));
|
||||
|
||||
return () => {
|
||||
dispatch(nodeSetCurrent(EMPTY_NODE));
|
||||
};
|
||||
}, [dispatch, id]);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { nodeEdit, nodeLike, nodeLock, nodeStar } from '~/redux/node/actions';
|
||||
import { nodeLike, nodeLock, nodeStar } from '~/redux/node/actions';
|
||||
import { modalShowDialog } from '~/redux/modal/actions';
|
||||
import { NODE_EDITOR_DIALOGS } from '~/constants/dialogs';
|
||||
|
||||
export const useNodeActions = (node: INode) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onEdit = useCallback(() => dispatch(nodeEdit(node.id)), [dispatch, node]);
|
||||
const onEdit = useCallback(() => {
|
||||
if (!node.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(modalShowDialog(NODE_EDITOR_DIALOGS[node.type]));
|
||||
}, [dispatch, node]);
|
||||
|
||||
const onLike = useCallback(() => dispatch(nodeLike(node.id)), [dispatch, node]);
|
||||
const onStar = useCallback(() => dispatch(nodeStar(node.id)), [dispatch, node]);
|
||||
const onLock = useCallback(() => dispatch(nodeLock(node.id, !node.deleted_at)), [dispatch, node]);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { useCallback } from 'react';
|
||||
import { nodeLoadMoreComments, nodeLockComment } from '~/redux/node/actions';
|
||||
import { IComment, INode } from '~/redux/types';
|
||||
import { IComment } from '~/redux/types';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
export const useNodeComments = (id: INode['id']) => {
|
||||
export const useNodeComments = (nodeId: number) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onLoadMoreComments = useCallback(() => dispatch(nodeLoadMoreComments()), [dispatch]);
|
||||
|
||||
const onDelete = useCallback(
|
||||
(id: IComment['id'], locked: boolean) => dispatch(nodeLockComment(id, locked)),
|
||||
[dispatch]
|
||||
(id: IComment['id'], locked: boolean) => dispatch(nodeLockComment(id, locked, nodeId)),
|
||||
[dispatch, nodeId]
|
||||
);
|
||||
|
||||
return { onLoadMoreComments, onDelete };
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { FileUploader } from '~/utils/hooks/useFileUploader';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { FormikConfig, FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||
import { object } from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { nodeSubmitLocal } from '~/redux/node/actions';
|
||||
import { keys } from 'ramda';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
|
||||
const validationSchema = object().shape({});
|
||||
|
||||
const onSuccess = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHelpers<INode>) => (
|
||||
const afterSubmit = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHelpers<INode>) => (
|
||||
e?: string,
|
||||
errors?: Record<string, string>
|
||||
) => {
|
||||
|
@ -17,6 +16,7 @@ const onSuccess = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHel
|
|||
|
||||
if (e) {
|
||||
setStatus(e);
|
||||
showErrorToast(e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -33,17 +33,9 @@ const onSuccess = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHel
|
|||
export const useNodeFormFormik = (
|
||||
values: INode,
|
||||
uploader: FileUploader,
|
||||
stopEditing: () => void
|
||||
stopEditing: () => void,
|
||||
sendSaveRequest: (node: INode) => Promise<unknown>
|
||||
) => {
|
||||
const dispatch = useDispatch();
|
||||
const onSubmit = useCallback(
|
||||
(values: INode, helpers: FormikHelpers<INode>) => {
|
||||
helpers.setSubmitting(true);
|
||||
dispatch(nodeSubmitLocal(values, onSuccess(helpers)));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { current: initialValues } = useRef(values);
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
|
@ -52,7 +44,19 @@ export const useNodeFormFormik = (
|
|||
if (stopEditing) stopEditing();
|
||||
}, [uploader, stopEditing]);
|
||||
|
||||
const formik = useFormik<INode>({
|
||||
const onSubmit = useCallback<FormikConfig<INode>['onSubmit']>(
|
||||
async (values, helpers) => {
|
||||
try {
|
||||
await sendSaveRequest({ ...values, files: uploader.files });
|
||||
afterSubmit(helpers)();
|
||||
} catch (error) {
|
||||
afterSubmit(helpers)(error?.response?.data?.error, error?.response?.data?.errors);
|
||||
}
|
||||
},
|
||||
[sendSaveRequest, uploader.files]
|
||||
);
|
||||
|
||||
return useFormik<INode>({
|
||||
initialValues,
|
||||
validationSchema,
|
||||
onSubmit,
|
||||
|
@ -60,17 +64,6 @@ export const useNodeFormFormik = (
|
|||
initialStatus: '',
|
||||
validateOnChange: true,
|
||||
});
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
formik.setFieldValue('files', uploader.files);
|
||||
},
|
||||
// because it breaks files logic
|
||||
// eslint-disable-next-line
|
||||
[uploader.files, formik.setFieldValue]
|
||||
);
|
||||
|
||||
return formik;
|
||||
};
|
||||
|
||||
export const useNodeFormContext = () => useFormikContext<INode>();
|
|
@ -3,10 +3,6 @@ import { useMemo } from 'react';
|
|||
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||
|
||||
export const useNodeImages = (node: INode) => {
|
||||
if (!node?.files) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return useMemo(() => node.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE), [
|
||||
node.files,
|
||||
]);
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
|||
import { selectUser } from '~/redux/auth/selectors';
|
||||
import { INode } from '~/redux/types';
|
||||
|
||||
export const useNodePermissions = (node: INode) => {
|
||||
export const useNodePermissions = (node?: INode) => {
|
||||
const user = useShallowSelect(selectUser);
|
||||
const edit = useMemo(() => canEditNode(node, user), [node, user]);
|
||||
const like = useMemo(() => canLikeNode(node, user), [node, user]);
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useCallback } from 'react';
|
||||
import { nodeDeleteTag, nodeUpdateTags } from '~/redux/node/actions';
|
||||
import { INode, ITag } from '~/redux/types';
|
||||
import { ITag } from '~/redux/types';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { useGetNode } from '~/utils/hooks/data/useGetNode';
|
||||
import { apiDeleteNodeTag, apiPostNodeTags } from '~/redux/node/api';
|
||||
|
||||
export const useNodeTags = (id: INode['id']) => {
|
||||
const dispatch = useDispatch();
|
||||
export const useNodeTags = (id: number) => {
|
||||
const { update } = useGetNode(id);
|
||||
const history = useHistory();
|
||||
|
||||
const onChange = useCallback(
|
||||
(tags: string[]) => {
|
||||
dispatch(nodeUpdateTags(id, tags));
|
||||
async (tags: string[]) => {
|
||||
try {
|
||||
const result = await apiPostNodeTags({ id, tags });
|
||||
await update({ tags: result.node.tags });
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
},
|
||||
[dispatch, id]
|
||||
[id, update]
|
||||
);
|
||||
|
||||
const onClick = useCallback(
|
||||
|
@ -28,10 +33,15 @@ export const useNodeTags = (id: INode['id']) => {
|
|||
);
|
||||
|
||||
const onDelete = useCallback(
|
||||
(tagId: ITag['ID']) => {
|
||||
dispatch(nodeDeleteTag(id, tagId));
|
||||
async (tagId: ITag['ID']) => {
|
||||
try {
|
||||
const result = await apiDeleteNodeTag({ id, tagId });
|
||||
await update({ tags: result.tags });
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
},
|
||||
[dispatch, id]
|
||||
[id, update]
|
||||
);
|
||||
|
||||
return { onDelete, onChange, onClick };
|
||||
|
|
|
@ -2,15 +2,22 @@ import { INode } from '~/redux/types';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { labSeenNode } from '~/redux/lab/actions';
|
||||
import { flowSeenNode } from '~/redux/flow/actions';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
// useOnNodeSeen updates node seen status across all needed places
|
||||
export const useOnNodeSeen = (node: INode) => {
|
||||
export const useOnNodeSeen = (node?: INode) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Remove node from updated
|
||||
if (node.is_promoted) {
|
||||
dispatch(flowSeenNode(node.id));
|
||||
} else {
|
||||
dispatch(labSeenNode(node.id));
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!node?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove node from updated
|
||||
if (node.is_promoted) {
|
||||
dispatch(flowSeenNode(node.id));
|
||||
} else {
|
||||
dispatch(labSeenNode(node.id));
|
||||
}
|
||||
}, [dispatch, node]);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue