feat: add new logic for stream api (#76)

This commit is contained in:
Kilin Mikhail
2021-01-17 20:33:44 +03:00
committed by GitHub
parent 5078a2cf4b
commit 4f389c47f5
6 changed files with 110 additions and 54 deletions

View File

@ -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<string, (...args: any[]) => Promise<unknown>>;
type StreamApi<T extends PromiseApi> = {
[K in keyof T]: (...params: Parameters<T[K]>) => (
T[K] extends (...args: any[]) => Promise<infer R> ? Stream<R> : never
);
type StreamApi<T extends PromiseApi, E> = {
[K in keyof T]: (
...params: Parameters<T[K]>
) => T[K] extends (...args: any[]) => Promise<infer R> ? Stream<RemoteData<E, R>> : never;
};
const pipeApiStream = <T>(stream$: Stream<T>) =>
pipe(
stream$,
map(val => success(val)),
startWith(pending),
recoverWith(err => now(failure(err)))
);
export const makeApi = <T extends PromiseApi>(apiObj: T) => {
export const makeApi = <T extends PromiseApi, E = Error>(apiObj: T) => {
return objectEntries(apiObj).reduce((streamObj, [apiKey, apiMethod]) => {
return {
...streamObj,
[apiKey]: (...args: Parameters<typeof apiMethod>) => fromPromise(apiMethod(...args)),
[apiKey]: (...args: Parameters<typeof apiMethod>) => {
const res = fromPromise(apiMethod(...args));
return pipeApiStream(res);
}
};
}, {} as StreamApi<T>);
}, {} as StreamApi<T, E>);
};

View File

@ -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<T extends Array<unknown>, R>(
piping: () => Stream<R>,
@ -31,3 +32,32 @@ export function useStream<T extends Array<unknown>, R>(
return state;
}
export function useStreamRD<T extends Array<unknown>, R, E = Error>(
piping: () => Stream<RemoteData<E, R>>,
props: T,
) {
const [state, setState] = useState<RemoteData<E, R>>(pending);
useEffect(() => {
setState(pending);
// eslint-disable-next-line
}, props);
useEffect(() => {
const effect$ = piping();
const sink: Sink<RemoteData<E, R>> = {
event: (_, val) => {
setState(val);
},
end: noop,
error: noop
};
const unsub = effect$.run(sink, newDefaultScheduler());
return () => {
unsub.dispose();
};
// eslint-disable-next-line
}, props);
return state;
}

View File

@ -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 (
<Fragment>
{isNotEmpty(tree) && (
{renderAsyncData(treeRD, tree => (
<InfoList list={tree} space={1} />
)}
))}
</Fragment>
);
};

View File

@ -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 (
<Fragment>
<div>
tags
{users?.map(user => (
<UserComponent userId={user.id} key={user.id}/>
))}
{renderAsyncData(users, successData =>
successData?.map(user => <UserComponent userId={user.id} key={user.id} />)
)}
</div>
<div>
tags
{users?.map(user => (
<UserComponent userId={user.id} key={user.id}/>
))}
{renderAsyncData(users, successData =>
successData?.map(user => <UserComponent userId={user.id} key={user.id} />)
)}
</div>
</Fragment>
);

View File

@ -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<Props> = ({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 (
<Fragment>

View File

@ -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<Props> = () => {
const {id} = useParams<Props>();
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 (
<div>
{user ? (
{renderAsyncData(userRD, user => (
<div>
<div>{user.avatar}</div>
<div>{user.email}</div>
@ -41,9 +47,7 @@ const User: FC<Props> = () => {
<div>{user.last_name}</div>
<div>{user.chainableNumber}</div>
</div>
) : (
'Loading...'
)}
))}
</div>
);
};