diff --git a/.eslintrc.json b/.eslintrc.json index e533e06..5c94273 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -39,7 +39,7 @@ "react-hooks/exhaustive-deps": [ "warn", { - "additionalHooks": "(useEqualMemo)" + "additionalHooks": "(useEqualMemo|useStream)" } ], "jsx-a11y/label-has-associated-control": 0, diff --git a/src/core/api/usersTestApi.ts b/src/core/api/usersTestApi.ts index 09cbaf0..74c7aec 100644 --- a/src/core/api/usersTestApi.ts +++ b/src/core/api/usersTestApi.ts @@ -8,22 +8,31 @@ type User = { first_name: string; last_name: string; }; - +type Support = { + text: string; + url: string; +}; type UserReponse = { data: Array; page: number; per_page: number; - support: { - text: string; - url: string; - } + support: Support; total: number; total_pages: number; }; +type UserFindResponse = { + data: User; + support: Support; +} +const ROOT_URL = 'https://reqres.in/api/users'; export const usersApi = makeApi({ request: async () => { - const {data} = await http.get('https://reqres.in/api/users'); + const {data} = await http.get(ROOT_URL); return data; }, + findById: async (id: string) => { + const {data} = await http.get(`${ROOT_URL}/${id}`); + return data; + } }); diff --git a/src/core/utils/asyncDataUtils.tsx b/src/core/utils/asyncDataUtils.tsx index 9e36fa8..ee595c7 100644 --- a/src/core/utils/asyncDataUtils.tsx +++ b/src/core/utils/asyncDataUtils.tsx @@ -1,7 +1,7 @@ import React, {ReactNode} from 'react'; -import {RemoteData, fold, map} from '@devexperts/remote-data-ts'; +import {RemoteData, fold, map as remoteDateMap} from '@devexperts/remote-data-ts'; import {Stream} from '@most/types'; -import * as M from '@most/core'; +import {map} from '@most/core'; import {pipe} from 'fp-ts/lib/pipeable'; export const renderAsyncData = ( @@ -20,7 +20,7 @@ export const mapRD = (mapper: (val: A) => R) => { return (stream$: Stream>): Stream> => { return pipe( stream$, - M.map(val => map(mapper)(val)) + map(val => remoteDateMap(mapper)(val)) ); }; }; diff --git a/src/core/utils/useStream.ts b/src/core/utils/useStream.ts index 6c070a5..763ec08 100644 --- a/src/core/utils/useStream.ts +++ b/src/core/utils/useStream.ts @@ -1,32 +1,33 @@ +import {Sink, Stream} from '@most/types'; +import {noop} from 'lodash'; import {useEffect, useState} from 'react'; -import {Stream, Sink} from '@most/types'; import {newDefaultScheduler} from '@most/scheduler'; -import {pending, RemoteData} from '@devexperts/remote-data-ts'; - -// eslint-disable-next-line -const emptyFunc = () => {}; - -export const useStream = (stream$: Stream, defaultValue: T): T => { - const [state, setState] = useState(defaultValue); +export function useStream, R>( + piping: () => Stream, + props: T, +) { + const [state, setState] = useState(); useEffect(() => { - const sink: Sink = { + setState(undefined); + // eslint-disable-next-line + }, props); + useEffect(() => { + const effect$ = piping(); + const sink: Sink = { event: (_, val) => { setState(val); }, - end: emptyFunc, - error: emptyFunc + end: noop, + error: noop }; + const unsub = effect$.run(sink, newDefaultScheduler()); - const effect$ = stream$.run(sink, newDefaultScheduler()); return () => { - effect$.dispose(); + unsub.dispose(); }; - }, [stream$]); + // eslint-disable-next-line + }, props); return state; -}; - -export const useStreamRD = (stream$: Stream>): RemoteData => { - return useStream(stream$, pending); -}; +} diff --git a/src/pages/main/components/page/Page.tsx b/src/pages/main/components/page/Page.tsx index 0837b40..c19ed63 100644 --- a/src/pages/main/components/page/Page.tsx +++ b/src/pages/main/components/page/Page.tsx @@ -1,15 +1,15 @@ import React, {memo} from 'react'; import {useStream} from '_utils/useStream'; import {tasksService} from '_services/TasksService'; + import {List, ListItem, ListItemIcon, ListItemText} from '@material-ui/core'; import InboxIcon from '@material-ui/icons/Inbox'; const MainPage: React.FC = () => { - const taskList = useStream(tasksService.stream$, []); - + const taskList = useStream(() => tasksService.stream$, []); return ( - {taskList.map(task => ( + {taskList?.map(task => ( diff --git a/src/pages/tags/components/page/Page.tsx b/src/pages/tags/components/page/Page.tsx index b33f68f..ab8c8c2 100644 --- a/src/pages/tags/components/page/Page.tsx +++ b/src/pages/tags/components/page/Page.tsx @@ -1,16 +1,21 @@ import React, {memo} from 'react'; -import {usersApi} from '../../../../core/api/usersTestApi'; -import {useStream} from '../../../../core/utils/useStream'; +import {Link} from 'react-router-dom'; +import {usersApi} from '_api/usersTestApi'; +import {useStream} from '_utils/useStream'; const userList$ = usersApi.request(); const Page: React.FC = () => { - const users = useStream(userList$, []); + const users = useStream(() => userList$, []); + return (
tags - {users.map(user => ( -
{user.first_name}, {user.last_name}
+ {users?.map(user => ( +
+ {user.first_name}, {user.last_name} + More info... +
))}
); diff --git a/src/pages/tags/components/user/index.tsx b/src/pages/tags/components/user/index.tsx new file mode 100644 index 0000000..71426f0 --- /dev/null +++ b/src/pages/tags/components/user/index.tsx @@ -0,0 +1,51 @@ +import React, {FC, memo} from 'react'; +import {pipe} from 'fp-ts/es6/pipeable'; +import {at, chain, periodic, map} from '@most/core'; + +import {useStream} from '_utils/useStream'; +import {usersApi} from '_api/usersTestApi'; +import {useParams} from 'react-router-dom'; + +type Props = { + id: string; +} + +const User: FC = () => { + const {id} = useParams(); + + const user = useStream(() => { + 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 + }; + })); + }) + ); + }, [id]); + + return ( +
+ {user ? ( +
+
{user.avatar}
+
{user.email}
+
{user.first_name}
+
{user.id}
+
{user.last_name}
+
{user.chainableNumber}
+
+ ) : ( + 'Loading...' + )} +
+ ); +}; + +export default memo(User); diff --git a/src/pages/tags/routing.tsx b/src/pages/tags/routing.tsx index 0828b73..d014576 100644 --- a/src/pages/tags/routing.tsx +++ b/src/pages/tags/routing.tsx @@ -1,8 +1,12 @@ -import React from 'react'; +import React, {Fragment} from 'react'; import {Route} from 'react-router-dom'; import {ROUTES} from '_consts/common'; import Page from './components/page'; +import User from './components/user'; export default ( - + + + + );