From 5e1e575ee3904901e73e18a1b24954c367f9212d Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Mon, 20 Sep 2021 11:26:20 +0700 Subject: [PATCH] loading node with hook --- package.json | 1 + src/components/node/NodeBottomBlock/index.tsx | 6 ++-- .../node/NodeCommentsBlock/index.tsx | 2 +- src/components/node/NodePanel/index.tsx | 2 +- src/components/node/NodePanelInner/index.tsx | 30 +++++++++------- .../node/NodeRelatedBlock/index.tsx | 4 +-- src/components/node/NodeTags/index.tsx | 4 +-- src/components/node/NodeTagsBlock/index.tsx | 8 ++--- src/layouts/NodeLayout/index.tsx | 31 ++++++++--------- src/redux/node/api.ts | 4 +-- src/redux/node/sagas.ts | 4 +-- src/utils/hooks/node/useNodeActions.ts | 34 ++++++++++++++----- src/utils/hooks/node/useNodeBlocks.ts | 5 +-- src/utils/hooks/node/useNodeCoverImage.ts | 9 +++-- src/utils/hooks/node/useNodeFetcher.ts | 11 ++++++ src/utils/hooks/node/useNodePermissions.ts | 2 +- src/utils/hooks/node/useOnNodeSeen.ts | 6 +++- src/utils/node.ts | 8 ++--- yarn.lock | 12 +++++++ 19 files changed, 117 insertions(+), 66 deletions(-) create mode 100644 src/utils/hooks/node/useNodeFetcher.ts diff --git a/package.json b/package.json index a7698031..55bf8ae8 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "redux-saga": "^1.1.1", "sticky-sidebar": "^3.3.1", "swiper": "^6.7.0", + "swr": "^1.0.1", "throttle-debounce": "^2.1.0", "typescript": "^4.0.5", "typograf": "^6.11.3", diff --git a/src/components/node/NodeBottomBlock/index.tsx b/src/components/node/NodeBottomBlock/index.tsx index d06da73c..087b5829 100644 --- a/src/components/node/NodeBottomBlock/index.tsx +++ b/src/components/node/NodeBottomBlock/index.tsx @@ -14,7 +14,7 @@ import StickyBox from 'react-sticky-box/dist/esnext'; import styles from './styles.module.scss'; interface IProps { - node: INode; + node?: INode; isLoading: boolean; commentsOrder: 'ASC' | 'DESC'; comments: IComment[]; @@ -35,7 +35,7 @@ const NodeBottomBlock: FC = ({ const { inline } = useNodeBlocks(node, isLoading); const { is_user } = useUser(); - if (node.deleted_at) { + if (node?.deleted_at) { return ; } @@ -55,7 +55,7 @@ const NodeBottomBlock: FC = ({ node={node} /> - {is_user && !isLoading && } + {is_user && !isLoading && }
diff --git a/src/components/node/NodeCommentsBlock/index.tsx b/src/components/node/NodeCommentsBlock/index.tsx index f111dbff..aeab8eb2 100644 --- a/src/components/node/NodeCommentsBlock/index.tsx +++ b/src/components/node/NodeCommentsBlock/index.tsx @@ -7,7 +7,7 @@ import { useUser } from '~/utils/hooks/user/userUser'; interface IProps { order: 'ASC' | 'DESC'; - node: INode; + node?: INode; comments: IComment[]; count: number; isLoading: boolean; diff --git a/src/components/node/NodePanel/index.tsx b/src/components/node/NodePanel/index.tsx index ab83d01c..72c5f59c 100644 --- a/src/components/node/NodePanel/index.tsx +++ b/src/components/node/NodePanel/index.tsx @@ -7,7 +7,7 @@ import { useNodeActions } from '~/utils/hooks/node/useNodeActions'; import { shallowEqual } from 'react-redux'; interface IProps { - node: INode; + node?: INode; isLoading: boolean; } diff --git a/src/components/node/NodePanelInner/index.tsx b/src/components/node/NodePanelInner/index.tsx index ea717ccc..41074f57 100644 --- a/src/components/node/NodePanelInner/index.tsx +++ b/src/components/node/NodePanelInner/index.tsx @@ -9,7 +9,7 @@ import { URLS } from '~/constants/urls'; import { Link } from 'react-router-dom'; interface IProps { - node: Partial; + node?: Partial; stack?: boolean; canEdit: boolean; @@ -26,8 +26,8 @@ interface IProps { const NodePanelInner: FC = memo( ({ - node: { id, title, user, is_liked, is_heroic, deleted_at, created_at, like_count }, stack, + node, canStar, canEdit, @@ -45,15 +45,15 @@ const NodePanelInner: FC = memo(
- {isLoading ? : title || '...'} + {isLoading ? : node?.title || '...'}
- {user && user.username && ( + {node?.user && node?.user.username && (
{isLoading ? ( ) : ( - `~${user.username.toLocaleLowerCase()}, ${getPrettyDate(created_at)}` + `~${node?.user.username.toLocaleLowerCase()}, ${getPrettyDate(node?.created_at)}` )}
)} @@ -67,8 +67,8 @@ const NodePanelInner: FC = memo(
{canStar && ( -
- {is_heroic ? ( +
+ {node?.is_heroic ? ( ) : ( @@ -77,10 +77,14 @@ const NodePanelInner: FC = memo( )}
- +
- +
@@ -89,15 +93,15 @@ const NodePanelInner: FC = memo(
{canLike && ( -
- {is_liked ? ( +
+ {node?.is_liked ? ( ) : ( )} - {!!like_count && like_count > 0 && ( -
{like_count}
+ {!!node?.like_count && node.like_count > 0 && ( +
{node.like_count}
)}
)} diff --git a/src/components/node/NodeRelatedBlock/index.tsx b/src/components/node/NodeRelatedBlock/index.tsx index c4948cb7..eae15277 100644 --- a/src/components/node/NodeRelatedBlock/index.tsx +++ b/src/components/node/NodeRelatedBlock/index.tsx @@ -8,12 +8,12 @@ import { Link } from 'react-router-dom'; interface IProps { isLoading: boolean; - node: INode; + node?: INode; related: INodeRelated; } const NodeRelatedBlock: FC = ({ isLoading, node, related }) => { - if (isLoading) { + if (isLoading || !node?.id) { return ; } diff --git a/src/components/node/NodeTags/index.tsx b/src/components/node/NodeTags/index.tsx index d5c48923..9596aa29 100644 --- a/src/components/node/NodeTags/index.tsx +++ b/src/components/node/NodeTags/index.tsx @@ -4,12 +4,12 @@ import { Tags } from '~/components/tags/Tags'; interface IProps { is_editable?: boolean; - tags: ITag[]; + tags?: ITag[]; onChange?: (tags: string[]) => void; onTagClick?: (tag: Partial) => void; } -const NodeTags: FC = memo(({ is_editable, tags, onChange, onTagClick }) => { +const NodeTags: FC = memo(({ is_editable, tags = [], onChange, onTagClick }) => { return ( ); diff --git a/src/components/node/NodeTagsBlock/index.tsx b/src/components/node/NodeTagsBlock/index.tsx index e4ee877f..85eb1a38 100644 --- a/src/components/node/NodeTagsBlock/index.tsx +++ b/src/components/node/NodeTagsBlock/index.tsx @@ -8,8 +8,8 @@ import { NodeTags } from '~/components/node/NodeTags'; import { useUser } from '~/utils/hooks/user/userUser'; interface IProps { - node: INode; - isLoading: boolean; + node?: INode; + isLoading?: boolean; } const NodeTagsBlock: FC = ({ node, isLoading }) => { @@ -19,7 +19,7 @@ const NodeTagsBlock: FC = ({ node, isLoading }) => { const onTagsChange = useCallback( (tags: string[]) => { - dispatch(nodeUpdateTags(node.id, tags)); + dispatch(nodeUpdateTags(node?.id, tags)); }, [dispatch, node] ); @@ -42,7 +42,7 @@ const NodeTagsBlock: FC = ({ node, isLoading }) => { return ( diff --git a/src/layouts/NodeLayout/index.tsx b/src/layouts/NodeLayout/index.tsx index c75abed8..f14cb3f9 100644 --- a/src/layouts/NodeLayout/index.tsx +++ b/src/layouts/NodeLayout/index.tsx @@ -6,7 +6,6 @@ import { Card } from '~/components/containers/Card'; import { NodePanel } from '~/components/node/NodePanel'; import { Footer } from '~/components/main/Footer'; -import styles from './styles.module.scss'; import { SidebarRouter } from '~/containers/main/SidebarRouter'; import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; import { Container } from '~/containers/main/Container'; @@ -19,6 +18,9 @@ import { URLS } from '~/constants/urls'; import { EditorEditDialog } from '~/containers/dialogs/EditorEditDialog'; import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen'; +import styles from './styles.module.scss'; +import { useNodeFetcher } from '~/utils/hooks/node/useNodeFetcher'; + type IProps = RouteComponentProps<{ id: string }> & {}; const NodeLayout: FC = memo( @@ -27,21 +29,16 @@ const NodeLayout: FC = memo( params: { id }, }, }) => { - const { - is_loading, - current, - comments, - comment_count, - is_loading_comments, - related, - } = useShallowSelect(selectNode); + const { node, isLoading } = useNodeFetcher(parseInt(id, 10)); - useNodeCoverImage(current); + const { comments, comment_count, is_loading_comments, related } = useShallowSelect(selectNode); + + useNodeCoverImage(node); useScrollToTop([id]); - useLoadNode(id, is_loading); - useOnNodeSeen(current); + useOnNodeSeen(node); + useLoadNode(id, isLoading); - const { head, block } = useNodeBlocks(current, is_loading); + const { head, block } = useNodeBlocks(node, isLoading); return (
@@ -51,13 +48,13 @@ const NodeLayout: FC = memo( {block} - + = memo( - + } />
); } diff --git a/src/redux/node/api.ts b/src/redux/node/api.ts index 02929c71..3add6bf7 100644 --- a/src/redux/node/api.ts +++ b/src/redux/node/api.ts @@ -66,8 +66,8 @@ export const getNodeDiff = ({ }) .then(cleanResult); -export const apiGetNode = ({ id }: ApiGetNodeRequest, config?: AxiosRequestConfig) => - api.get(API.NODE.GET_NODE(id), config).then(cleanResult); +export const apiGetNode = (id: INode['id']) => + api.get(API.NODE.GET_NODE(id!)).then(cleanResult); export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => { const cancelToken = axios.CancelToken.source(); diff --git a/src/redux/node/sagas.ts b/src/redux/node/sagas.ts index 31ff9776..755b382e 100644 --- a/src/redux/node/sagas.ts +++ b/src/redux/node/sagas.ts @@ -149,7 +149,7 @@ function* onNodeLoad({ id }: ReturnType) { yield put(nodeSetLoading(true)); yield put(nodeSetLoadingComments(true)); - const { node }: Unwrap = yield call(apiGetNode, { id }); + const { node }: Unwrap = yield call(apiGetNode, id); yield put(nodeSetCurrent(node)); yield put(nodeSetLoading(false)); @@ -240,7 +240,7 @@ function* onEditSaga({ id }: ReturnType) { yield put(modalShowDialog(DIALOGS.LOADING)); - const { node }: Unwrap = yield call(apiGetNode, { id }); + const { node }: Unwrap = yield call(apiGetNode, id); if (!node.type || !has(node.type, NODE_EDITOR_DIALOGS)) return; diff --git a/src/utils/hooks/node/useNodeActions.ts b/src/utils/hooks/node/useNodeActions.ts index cd3bb124..f6f490d6 100644 --- a/src/utils/hooks/node/useNodeActions.ts +++ b/src/utils/hooks/node/useNodeActions.ts @@ -3,17 +3,33 @@ import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { nodeEdit, nodeLike, nodeLock, nodeStar } from '~/redux/node/actions'; -export const useNodeActions = (node: INode) => { +export const useNodeActions = (node?: INode) => { const dispatch = useDispatch(); - const onEdit = useCallback(() => dispatch(nodeEdit(node.id)), [dispatch, nodeEdit, node]); - const onLike = useCallback(() => dispatch(nodeLike(node.id)), [dispatch, nodeLike, node]); - const onStar = useCallback(() => dispatch(nodeStar(node.id)), [dispatch, nodeStar, node]); - const onLock = useCallback(() => dispatch(nodeLock(node.id, !node.deleted_at)), [ - dispatch, - nodeLock, - node, - ]); + const onEdit = useCallback(() => { + if (!node?.id) { + return; + } + dispatch(nodeEdit(node.id)); + }, [node]); + const onLike = useCallback(() => { + if (!node?.id) { + return; + } + dispatch(nodeLike(node.id)); + }, [node]); + const onStar = useCallback(() => { + if (!node?.id) { + return; + } + dispatch(nodeStar(node.id)); + }, [node]); + const onLock = useCallback(() => { + if (!node?.id) { + return; + } + dispatch(nodeLock(node.id, !node.deleted_at)); + }, [node]); return { onEdit, onLike, onStar, onLock }; }; diff --git a/src/utils/hooks/node/useNodeBlocks.ts b/src/utils/hooks/node/useNodeBlocks.ts index 2490df35..5175540f 100644 --- a/src/utils/hooks/node/useNodeBlocks.ts +++ b/src/utils/hooks/node/useNodeBlocks.ts @@ -10,13 +10,14 @@ import { } from '~/redux/node/constants'; // useNodeBlocks returns head, block and inline blocks of node -export const useNodeBlocks = (node: INode, isLoading: boolean) => { +export const useNodeBlocks = (node?: INode, isLoading?: boolean) => { const createNodeBlock = useCallback( (block?: FC, key = 0) => + !isNil(node) && !isNil(block) && createElement(block, { node, - isLoading, + isLoading: !!isLoading, key: `${node.id}-${key}`, }), [node, isLoading] diff --git a/src/utils/hooks/node/useNodeCoverImage.ts b/src/utils/hooks/node/useNodeCoverImage.ts index 62e104a9..9d1483c5 100644 --- a/src/utils/hooks/node/useNodeCoverImage.ts +++ b/src/utils/hooks/node/useNodeCoverImage.ts @@ -3,14 +3,19 @@ import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { nodeSetCoverImage } from '~/redux/node/actions'; -export const useNodeCoverImage = (node: INode) => { +export const useNodeCoverImage = (node?: INode) => { const dispatch = useDispatch(); useEffect(() => { + if (!node?.cover) { + dispatch(nodeSetCoverImage(undefined)); + return; + } + dispatch(nodeSetCoverImage(node.cover)); return () => { dispatch(nodeSetCoverImage(undefined)); }; - }, [dispatch, node.cover, node.id]); + }, [node?.cover]); }; diff --git a/src/utils/hooks/node/useNodeFetcher.ts b/src/utils/hooks/node/useNodeFetcher.ts new file mode 100644 index 00000000..7376d8cf --- /dev/null +++ b/src/utils/hooks/node/useNodeFetcher.ts @@ -0,0 +1,11 @@ +import useSWR from 'swr'; +import { INode } from '~/redux/types'; +import { apiGetNode } from '~/redux/node/api'; + +export const useNodeFetcher = (id: INode['id']) => { + const { data, error, isValidating } = useSWR(`${id}`, apiGetNode); + const node = data?.node; + const isLoading = !node && !isValidating; + + return { node, error, isLoading }; +}; diff --git a/src/utils/hooks/node/useNodePermissions.ts b/src/utils/hooks/node/useNodePermissions.ts index 4f93ba78..08eea085 100644 --- a/src/utils/hooks/node/useNodePermissions.ts +++ b/src/utils/hooks/node/useNodePermissions.ts @@ -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]); diff --git a/src/utils/hooks/node/useOnNodeSeen.ts b/src/utils/hooks/node/useOnNodeSeen.ts index f9e87d9e..6f3fa0a2 100644 --- a/src/utils/hooks/node/useOnNodeSeen.ts +++ b/src/utils/hooks/node/useOnNodeSeen.ts @@ -4,9 +4,13 @@ import { labSeenNode } from '~/redux/lab/actions'; import { flowSeenNode } from '~/redux/flow/actions'; // useOnNodeSeen updates node seen status across all needed places -export const useOnNodeSeen = (node: INode) => { +export const useOnNodeSeen = (node?: INode) => { const dispatch = useDispatch(); + if (!node) { + return; + } + // Remove node from updated if (node.is_promoted) { dispatch(flowSeenNode(node.id)); diff --git a/src/utils/node.ts b/src/utils/node.ts index 0e2f426e..34b08798 100644 --- a/src/utils/node.ts +++ b/src/utils/node.ts @@ -4,7 +4,7 @@ import { IUser } from '~/redux/auth/types'; import { path } from 'ramda'; import { NODE_TYPES } from '~/redux/node/constants'; -export const canEditNode = (node: Partial, user: Partial): boolean => +export const canEditNode = (node?: Partial, user?: Partial): boolean => path(['role'], user) === USER_ROLES.ADMIN || (path(['user', 'id'], node) && path(['user', 'id'], node) === path(['id'], user)); @@ -12,10 +12,10 @@ export const canEditComment = (comment: Partial, user: Partial, user: Partial): boolean => +export const canLikeNode = (node?: Partial, user?: Partial): boolean => path(['role'], user) && path(['role'], user) !== USER_ROLES.GUEST; -export const canStarNode = (node: Partial, user: Partial): boolean => - (node.type === NODE_TYPES.IMAGE || node.is_promoted === false) && +export const canStarNode = (node?: Partial, user?: Partial): boolean => + (node?.type === NODE_TYPES.IMAGE || node?.is_promoted === false) && path(['role'], user) && path(['role'], user) === USER_ROLES.ADMIN; diff --git a/yarn.lock b/yarn.lock index e5a6393e..83bf3eff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4001,6 +4001,11 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +dequal@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d" + integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug== + des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -10881,6 +10886,13 @@ swiper@^6.7.0: dom7 "^3.0.0" ssr-window "^3.0.0" +swr@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/swr/-/swr-1.0.1.tgz#15f62846b87ee000e52fa07812bb65eb62d79483" + integrity sha512-EPQAxSjoD4IaM49rpRHK0q+/NzcwoT8c0/Ylu/u3/6mFj/CWnQVjNJ0MV2Iuw/U+EJSd2TX5czdAwKPYZIG0YA== + dependencies: + dequal "2.0.2" + symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"