Package Exports
- @alismael/remote-data
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@alismael/remote-data) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
remote-data
Handle modeling, fetching, and displaying remote data in React/Redux apps
Idea
A React library aimed at modeling, fetching, and displaying remote data and the states it can be in.
This library provides:
- api request wrapper based on Axios to make the HTTP requests
- fetchingReducer to update the store
- RemoteComponent to handle displaying remote data
Dependencies
Required Peer Dependencies
These libraries are not bundled with remote-data but required at runtime:
Install
npm i @alismael/remote-dataUsage
Performing a GET request to fetch the data
actions.ts
import { api } from 'remote-data';
import { Post, ErrorResponse } from '../models';
import { FETCH_POSTS } from './constants';
const fetchPosts = () =>
api<Post[], ErrorResponse>({
method: 'GET',
url: 'posts',
baseURL: 'https://jsonplaceholder.typicode.com/',
action: FETCH_POSTS,
});Adding a reducer to update the store
reducer.ts
import { Reducer } from 'react';
import { combineReducers } from 'redux';
import { fetchingReducer, RemoteData } from 'remote-data';
import { Post, ErrorResponse } from '../../models';
import { FETCH_POSTS } from './constants';
export type PostsStore = {
posts: RemoteData<Post[], ErrorResponse>;
};
const postsReducer: Reducer<PostsStore, any> = combineReducers({
posts: fetchingReducer<Post[], ErrorResponse>(FETCH_POSTS),
});
export default postsReducer;Displaying your remote data
PostsComponent.tsx
const PostsLoading = () => <>Loading posts...</>;
const PostsError = ({ err }: { err: ErrorResponse }) => <>{err}</>;
const ListPosts = ({ data }: { data: Post[] }) => <>Here you can use the fetched data</>
type PostsContainerProps = {
fetchPosts: () => Promise<Post[]>;
posts: RemoteData<Post[], ErrorResponse>;
};
const PostsContainer = ({ fetchPosts, posts }: PostsContainerProps) => {
React.useEffect(() => {
fetchPosts();
}, [fetchPosts]);
return (
<RemoteComponent
remote={{ posts }}
loading={PostsLoading}
reject={({ posts }) => <PostsError error={posts.error} />}
success={({ posts }) => <ListPosts posts={posts.data} />}
/>
);
};
const mapStateToProps = ({ posts }: StoreState) => ({
posts: posts.posts,
});
const mapDispatchToProps = (
dispatch,
) => ({
fetchPosts: () => dispatch(fetchPostsAction()),
});
connect(mapStateToProps, mapDispatchToProps)(PostsContainer);You can check the example folder for more details
api<T, E>
api<T, E>(config) where T, E are the types of data and the expected error respectively
import { api } from 'remote-data';
api<Post[], ErrorResponse>({
method: 'GET',
url: 'posts',
baseURL: 'https://jsonplaceholder.typicode.com/',
action: FETCH_POSTS,
});Request Config
In addition to axios request config there are three more options:
action: is the action type that will be dispatched when request state changed. If not provided no action will be dispatched.onSuccess,onError: are the callbacks to be triggered for the relevant request state.
fetchingReducer
fetchingReducer<T, E>(actionType) a reducer for managing the state of the remote data
import { fetchingReducer } from 'remote-data';
combineReducers({
posts: fetchingReducer<Post[], ErrorResponse>(FETCH_POSTS),
});actionType: it should be the same as the action passed to theapirequest wrapper
RemoteComponent
Handle displaying of your remote data.
import { RemoteComponent } from 'remote-data';
<RemoteComponent
remote={{ posts }}
loading={PostsLoading}
reject={({ posts }) => <PostsError error={posts.error} />}
success={({ posts }) => <ListPosts posts={posts.data} />}
/>Only remote and success are required
remotepassing your remote data here, it should be of type RemoteData<T, E>loading,success, andrejectwill be rendered for the relevant state
You can handle displaying multiple remote data at once with one component. here
RemoteData<T, E>
RemoteData<T, E> where T is the data type and E is the error type respectively
enum RemoteKind {
NotAsked = 'NOT_ASKED',
Loading = 'LOADING',
Success = 'SUCCESS',
Reject = 'REJECT',
}
type NotAsked = {
kind: RemoteKind.NotAsked;
};
type Loading = {
kind: RemoteKind.Loading;
};
type Success<T> = {
kind: RemoteKind.Success;
data: T;
};
type Reject<E> = {
kind: RemoteKind.Reject;
error: E;
};
type RemoteData<T, E> = NotAsked | Loading | Success<T> | Reject<E>;Action<T, E>
Action<T, E> where T is the data type and E is the error type respectively
type ActionType = string;
type NotAskedAction = {
type: ActionType;
kind: RemoteKind.NotAsked;
};
type LoadingAction = {
type: ActionType;
kind: RemoteKind.Loading;
};
type SuccessAction<T> = {
type: ActionType;
kind: RemoteKind.Success;
data: T;
headers: any;
};
type RejectAction<E> = {
type: ActionType;
kind: RemoteKind.Reject;
error: E;
headers: any;
};
type Action<T, E> =
| NotAskedAction
| LoadingAction
| SuccessAction<T>
| RejectAction<E>;Handle displaying multiple remote data with RemoteComponent component
<RemoteComponent
remote={{ posts, users }}
loading={() => (
<>
<PostsLoading />
<UsersLoading />
</>
)}
reject={({ posts, users }) => (
<>
{users.error && <UsersError error={users.error} />}
{posts.error && <PostsError error={posts.error} />}
</>
)}
success={({ posts, users }) => (
<>
<h1 className="page-title">Users</h1>
<ListUsers users={users.data} />
<h1 className="page-title">Posts</h1>
<ListPosts posts={posts.data} />
</>
)}
/>Create a custom reducer to manually update the store
You can create your custom reducer, here's an example:
import { RemoteData, RemoteKind, Action } from 'remote-data';
import { Post, ErrorResponse } from '../../models';
import { FETCH_POSTS } from './constants';
export type PostsStore = {
posts: RemoteData<Post[], ErrorResponse>;
};
const initialState: PostsStore = {
posts: {
kind: RemoteKind.NotAsked,
},
};
export default (
state: PostsStore = initialState,
action: Action<Post[], ErrorResponse>,
): PostsStore => {
if (action.type === FETCH_POSTS) {
switch (action.kind) {
case RemoteKind.Loading:
return {
...state,
posts: {
kind: RemoteKind.Loading,
},
};
case RemoteKind.Success:
return {
...state,
posts: {
kind: RemoteKind.Success,
data: action.data,
},
};
case RemoteKind.Reject:
return {
...state,
posts: {
kind: RemoteKind.Reject,
error: action.error,
},
};
default:
return state;
}
}
return state;
};- Initialize your state
- Verify the action type and kind
- Update your state
action is of type Action<T, E>
Development
To setup and run locally
- Clone this repo with
git clone https://github.com/alismael/remote-data - Run
npm installin the root folder - Run
npm installin the example folder - run
npm startin the root and example folders
License
MIT © alismael