useStream refactoring (#40)
This commit is contained in:
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
51
src/pages/tags/components/user/index.tsx
Normal file
51
src/pages/tags/components/user/index.tsx
Normal 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);
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user