From 4f389c47f5f3da42332c82195a272524cc662a68 Mon Sep 17 00:00:00 2001 From: Kilin Mikhail Date: Sun, 17 Jan 2021 20:33:44 +0300 Subject: [PATCH] feat: add new logic for stream api (#76) --- src/core/utils/makeApi.ts | 29 +++++++++---- src/core/utils/useStream.ts | 30 +++++++++++++ .../information/components/page/Page.tsx | 31 ++++++++++---- src/pages/tags/components/page/Page.tsx | 18 ++++---- src/pages/tags/components/page/User.tsx | 14 ++----- src/pages/tags/components/user/index.tsx | 42 ++++++++++--------- 6 files changed, 110 insertions(+), 54 deletions(-) diff --git a/src/core/utils/makeApi.ts b/src/core/utils/makeApi.ts index bb24f14..3985b5c 100644 --- a/src/core/utils/makeApi.ts +++ b/src/core/utils/makeApi.ts @@ -1,20 +1,33 @@ -import {fromPromise} from '@most/core'; +import {failure, pending, RemoteData, success} from '@devexperts/remote-data-ts'; +import {fromPromise, startWith, recoverWith, map, now} from '@most/core'; import {Stream} from '@most/types'; +import {pipe} from 'fp-ts/pipeable'; import {objectEntries} from './objectEntries'; type PromiseApi = Record Promise>; -type StreamApi = { - [K in keyof T]: (...params: Parameters) => ( - T[K] extends (...args: any[]) => Promise ? Stream : never - ); +type StreamApi = { + [K in keyof T]: ( + ...params: Parameters + ) => T[K] extends (...args: any[]) => Promise ? Stream> : never; }; +const pipeApiStream = (stream$: Stream) => + pipe( + stream$, + map(val => success(val)), + startWith(pending), + recoverWith(err => now(failure(err))) + ); -export const makeApi = (apiObj: T) => { +export const makeApi = (apiObj: T) => { return objectEntries(apiObj).reduce((streamObj, [apiKey, apiMethod]) => { return { ...streamObj, - [apiKey]: (...args: Parameters) => fromPromise(apiMethod(...args)), + [apiKey]: (...args: Parameters) => { + const res = fromPromise(apiMethod(...args)); + + return pipeApiStream(res); + } }; - }, {} as StreamApi); + }, {} as StreamApi); }; diff --git a/src/core/utils/useStream.ts b/src/core/utils/useStream.ts index 763ec08..7275b4a 100644 --- a/src/core/utils/useStream.ts +++ b/src/core/utils/useStream.ts @@ -2,6 +2,7 @@ import {Sink, Stream} from '@most/types'; import {noop} from 'lodash'; import {useEffect, useState} from 'react'; import {newDefaultScheduler} from '@most/scheduler'; +import {pending, RemoteData} from '@devexperts/remote-data-ts'; export function useStream, R>( piping: () => Stream, @@ -31,3 +32,32 @@ export function useStream, R>( return state; } + +export function useStreamRD, R, E = Error>( + piping: () => Stream>, + props: T, +) { + const [state, setState] = useState>(pending); + useEffect(() => { + setState(pending); + // eslint-disable-next-line + }, props); + useEffect(() => { + const effect$ = piping(); + const sink: Sink> = { + event: (_, val) => { + setState(val); + }, + end: noop, + error: noop + }; + const unsub = effect$.run(sink, newDefaultScheduler()); + + return () => { + unsub.dispose(); + }; + // eslint-disable-next-line + }, props); + + return state; +} diff --git a/src/pages/information/components/page/Page.tsx b/src/pages/information/components/page/Page.tsx index a2bf1be..d09d434 100644 --- a/src/pages/information/components/page/Page.tsx +++ b/src/pages/information/components/page/Page.tsx @@ -1,22 +1,35 @@ import {combine} from '@most/core'; +import {combine as combineRD, map} from '@devexperts/remote-data-ts'; +import {pipe} from 'fp-ts/lib/function'; import React, {Fragment, memo} from 'react'; -import {isNotEmpty} from '_referers/common'; -import {makeTreeList} from '_utils/makeTreeList'; + import {commonApi} from '_api/commonApi'; -import {useStream} from '_utils/useStream'; +import {renderAsyncData} from '_utils/asyncDataUtils'; +import {makeTreeList} from '_utils/makeTreeList'; +import {useStreamRD} from '_utils/useStream'; + import InfoList from '../info-list'; -const stream$ = combine((taskList, folderList) => { - return makeTreeList(folderList, taskList); -}, commonApi.taskList.getAll(), commonApi.folderList.getAll()); +const stream$ = combine( + (taskListRD, folderListRD) => { + return pipe( + combineRD(taskListRD, folderListRD), + map(([taskList, folderList]) => { + return makeTreeList(folderList, taskList); + }) + ); + }, + commonApi.taskList.getAll(), + commonApi.folderList.getAll() +); const Page: React.FC = () => { - const tree = useStream(() => stream$, []); + const treeRD = useStreamRD(() => stream$, []); return ( - {isNotEmpty(tree) && ( + {renderAsyncData(treeRD, tree => ( - )} + ))} ); }; diff --git a/src/pages/tags/components/page/Page.tsx b/src/pages/tags/components/page/Page.tsx index 088e524..9ae4d75 100644 --- a/src/pages/tags/components/page/Page.tsx +++ b/src/pages/tags/components/page/Page.tsx @@ -1,25 +1,27 @@ import React, {Fragment, memo} from 'react'; +import {renderAsyncData} from '_utils/asyncDataUtils'; import {usersApi} from '_api/usersTestApi'; -import {useStream} from '_utils/useStream'; +import {useStreamRD} from '_utils/useStream'; import UserComponent from './User'; const Page: React.FC = () => { - const users = useStream(() => usersApi.request(), []); + const users = useStreamRD(() => usersApi.request(), []); + return (
tags - {users?.map(user => ( - - ))} + {renderAsyncData(users, successData => + successData?.map(user => ) + )}
tags - {users?.map(user => ( - - ))} + {renderAsyncData(users, successData => + successData?.map(user => ) + )}
); diff --git a/src/pages/tags/components/page/User.tsx b/src/pages/tags/components/page/User.tsx index 4fe3f58..d24d085 100644 --- a/src/pages/tags/components/page/User.tsx +++ b/src/pages/tags/components/page/User.tsx @@ -1,12 +1,9 @@ -import {pending, success} from '@devexperts/remote-data-ts'; -import {map} from '@most/core'; -import {pipe} from 'fp-ts/lib/function'; import React, {FC, Fragment, memo} from 'react'; import {Link} from 'react-router-dom'; import {usersApi} from '_api/usersTestApi'; import {renderAsyncData} from '_utils/asyncDataUtils'; -import {useStream} from '_utils/useStream'; +import {useStreamRD} from '_utils/useStream'; import {userEntityStore} from './utils'; type Props = { @@ -15,16 +12,13 @@ type Props = { const User: FC = ({userId}) => { const data = - useStream(() => { + useStreamRD(() => { const userStringId = userId.toString(); return userEntityStore.get(userStringId, () => - pipe( - usersApi.findById(userStringId), - map(val => success(val)) - ) + usersApi.findById(userStringId) ); - }, [userId]) ?? pending; + }, [userId]); return ( diff --git a/src/pages/tags/components/user/index.tsx b/src/pages/tags/components/user/index.tsx index 71426f0..608b57c 100644 --- a/src/pages/tags/components/user/index.tsx +++ b/src/pages/tags/components/user/index.tsx @@ -1,38 +1,44 @@ -import React, {FC, memo} from 'react'; -import {pipe} from 'fp-ts/es6/pipeable'; +import {success} from '@devexperts/remote-data-ts'; import {at, chain, periodic, map} from '@most/core'; - -import {useStream} from '_utils/useStream'; -import {usersApi} from '_api/usersTestApi'; +import {pipe} from 'fp-ts/es6/pipeable'; +import React, {FC, memo} from 'react'; import {useParams} from 'react-router-dom'; +import {usersApi} from '_api/usersTestApi'; +import {chainRD, renderAsyncData} from '_utils/asyncDataUtils'; +import {useStreamRD} from '_utils/useStream'; + type Props = { id: string; -} +}; const User: FC = () => { const {id} = useParams(); - const user = useStream(() => { + const userRD = useStreamRD(() => { let i = 0; return pipe( at(3000, undefined), chain(() => usersApi.findById(id)), - chain(data => { - return pipe(periodic(1000), map(() => { - i = i + 1; - return { - ...data, - chainableNumber: i - }; - })); + chainRD(data => { + const res = pipe( + periodic(1000), + map(() => { + i = i + 1; + return success({ + ...data, + chainableNumber: i + }); + }) + ); + return res; }) ); }, [id]); return (
- {user ? ( + {renderAsyncData(userRD, user => (
{user.avatar}
{user.email}
@@ -41,9 +47,7 @@ const User: FC = () => {
{user.last_name}
{user.chainableNumber}
- ) : ( - 'Loading...' - )} + ))}
); };