useStream refactoring (#40)

This commit is contained in:
Kilin Mikhail
2020-12-28 20:03:06 +03:00
committed by GitHub
parent 45a7694e64
commit 5664469b55
8 changed files with 109 additions and 39 deletions

View File

@ -39,7 +39,7 @@
"react-hooks/exhaustive-deps": [ "react-hooks/exhaustive-deps": [
"warn", "warn",
{ {
"additionalHooks": "(useEqualMemo)" "additionalHooks": "(useEqualMemo|useStream)"
} }
], ],
"jsx-a11y/label-has-associated-control": 0, "jsx-a11y/label-has-associated-control": 0,

View File

@ -8,22 +8,31 @@ type User = {
first_name: string; first_name: string;
last_name: string; last_name: string;
}; };
type Support = {
text: string;
url: string;
};
type UserReponse = { type UserReponse = {
data: Array<User>; data: Array<User>;
page: number; page: number;
per_page: number; per_page: number;
support: { support: Support;
text: string;
url: string;
}
total: number; total: number;
total_pages: number; total_pages: number;
}; };
type UserFindResponse = {
data: User;
support: Support;
}
const ROOT_URL = 'https://reqres.in/api/users';
export const usersApi = makeApi({ export const usersApi = makeApi({
request: async () => { request: async () => {
const {data} = await http.get<void, UserReponse>('https://reqres.in/api/users'); const {data} = await http.get<void, UserReponse>(ROOT_URL);
return data; return data;
}, },
findById: async (id: string) => {
const {data} = await http.get<void, UserFindResponse>(`${ROOT_URL}/${id}`);
return data;
}
}); });

View File

@ -1,7 +1,7 @@
import React, {ReactNode} from 'react'; 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 {Stream} from '@most/types';
import * as M from '@most/core'; import {map} from '@most/core';
import {pipe} from 'fp-ts/lib/pipeable'; import {pipe} from 'fp-ts/lib/pipeable';
export const renderAsyncData = <E, A>( export const renderAsyncData = <E, A>(
@ -20,7 +20,7 @@ export const mapRD = <E, A, R>(mapper: (val: A) => R) => {
return (stream$: Stream<RemoteData<E, A>>): Stream<RemoteData<E, R>> => { return (stream$: Stream<RemoteData<E, A>>): Stream<RemoteData<E, R>> => {
return pipe( return pipe(
stream$, stream$,
M.map(val => map(mapper)(val)) map(val => remoteDateMap(mapper)(val))
); );
}; };
}; };

View File

@ -1,32 +1,33 @@
import {Sink, Stream} from '@most/types';
import {noop} from 'lodash';
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import {Stream, Sink} from '@most/types';
import {newDefaultScheduler} from '@most/scheduler'; import {newDefaultScheduler} from '@most/scheduler';
import {pending, RemoteData} from '@devexperts/remote-data-ts';
// eslint-disable-next-line
const emptyFunc = () => {};
export const useStream = <T>(stream$: Stream<T>, defaultValue: T): T => {
const [state, setState] = useState(defaultValue);
export function useStream<T extends Array<unknown>, R>(
piping: () => Stream<R>,
props: T,
) {
const [state, setState] = useState<R>();
useEffect(() => { useEffect(() => {
const sink: Sink<T> = { setState(undefined);
// eslint-disable-next-line
}, props);
useEffect(() => {
const effect$ = piping();
const sink: Sink<R> = {
event: (_, val) => { event: (_, val) => {
setState(val); setState(val);
}, },
end: emptyFunc, end: noop,
error: emptyFunc error: noop
}; };
const unsub = effect$.run(sink, newDefaultScheduler());
const effect$ = stream$.run(sink, newDefaultScheduler());
return () => { return () => {
effect$.dispose(); unsub.dispose();
}; };
}, [stream$]); // eslint-disable-next-line
}, props);
return state; return state;
}; }
export const useStreamRD = <T, E = Error>(stream$: Stream<RemoteData<E, T>>): RemoteData<E, T> => {
return useStream(stream$, pending);
};

View File

@ -1,15 +1,15 @@
import React, {memo} from 'react'; import React, {memo} from 'react';
import {useStream} from '_utils/useStream'; import {useStream} from '_utils/useStream';
import {tasksService} from '_services/TasksService'; import {tasksService} from '_services/TasksService';
import {List, ListItem, ListItemIcon, ListItemText} from '@material-ui/core'; import {List, ListItem, ListItemIcon, ListItemText} from '@material-ui/core';
import InboxIcon from '@material-ui/icons/Inbox'; import InboxIcon from '@material-ui/icons/Inbox';
const MainPage: React.FC = () => { const MainPage: React.FC = () => {
const taskList = useStream(tasksService.stream$, []); const taskList = useStream(() => tasksService.stream$, []);
return ( return (
<List component="nav" aria-label="main mailbox folders"> <List component="nav" aria-label="main mailbox folders">
{taskList.map(task => ( {taskList?.map(task => (
<ListItem button key={task.id}> <ListItem button key={task.id}>
<ListItemIcon> <ListItemIcon>
<InboxIcon /> <InboxIcon />

View File

@ -1,16 +1,21 @@
import React, {memo} from 'react'; import React, {memo} from 'react';
import {usersApi} from '../../../../core/api/usersTestApi'; import {Link} from 'react-router-dom';
import {useStream} from '../../../../core/utils/useStream'; import {usersApi} from '_api/usersTestApi';
import {useStream} from '_utils/useStream';
const userList$ = usersApi.request(); const userList$ = usersApi.request();
const Page: React.FC = () => { const Page: React.FC = () => {
const users = useStream(userList$, []); const users = useStream(() => userList$, []);
return ( return (
<div> <div>
tags tags
{users.map(user => ( {users?.map(user => (
<div key={user.id}>{user.first_name}, {user.last_name}</div> <div key={user.id}>
{user.first_name}, {user.last_name}
<span><Link to={`/tags/${user.id}`}> More info...</Link></span>
</div>
))} ))}
</div> </div>
); );

View File

@ -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<Props> = () => {
const {id} = useParams<Props>();
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 (
<div>
{user ? (
<div>
<div>{user.avatar}</div>
<div>{user.email}</div>
<div>{user.first_name}</div>
<div>{user.id}</div>
<div>{user.last_name}</div>
<div>{user.chainableNumber}</div>
</div>
) : (
'Loading...'
)}
</div>
);
};
export default memo(User);

View File

@ -1,8 +1,12 @@
import React from 'react'; import React, {Fragment} from 'react';
import {Route} from 'react-router-dom'; import {Route} from 'react-router-dom';
import {ROUTES} from '_consts/common'; import {ROUTES} from '_consts/common';
import Page from './components/page'; import Page from './components/page';
import User from './components/user';
export default ( export default (
<Fragment>
<Route component={User} path={`${ROUTES.TAGS}/:id`} exact />
<Route component={Page} path={ROUTES.TAGS} exact /> <Route component={Page} path={ROUTES.TAGS} exact />
</Fragment>
); );