Skip to content

Instantly share code, notes, and snippets.

@busypeoples
Created March 6, 2020 13:43
Show Gist options
  • Save busypeoples/71a69e5514dd87cb770de6d39e325e3b to your computer and use it in GitHub Desktop.
Save busypeoples/71a69e5514dd87cb770de6d39e325e3b to your computer and use it in GitHub Desktop.
RemoteData implementation using React, hooks and TypeScript
import React, { useState } from "react";
type NotAsked = {
_type: "NOT_ASKED";
};
type Loading = {
_type: "LOADING";
};
type Success<T> = {
_type: "SUCCESS";
data: T;
};
type Error<E> = {
_type: "ERROR";
msg: E;
};
type Data<T, E> = NotAsked | Loading | Error<E> | Success<T>;
type RemoteData<T, E> = {
NotAsked: NotAsked;
Loading: Loading;
Success: (data: T) => Success<T>;
Error: (msg: E) => Error<E>;
};
const makeRemoteData = <T, E>(): RemoteData<T, E> => {
return {
NotAsked: { _type: "NOT_ASKED" },
Loading: { _type: "LOADING" },
Error: (msg: E) => ({ _type: "ERROR", msg }),
Success: (data: T) => ({ _type: "SUCCESS", data })
};
};
type ViewInput<T, E> = {
notAsked: () => React.ReactNode;
loading: () => React.ReactNode;
error: (msg: E) => React.ReactNode;
success: (data: T) => React.ReactNode;
};
const useRemoteData = <T, E>(
fetch: () => Promise<T>
): [(v: ViewInput<T, E>) => React.ReactNode, () => void] => {
const remoteData = makeRemoteData<T, E>();
const [state, setState] = useState<Data<T, E>>(remoteData.NotAsked);
const loadData = () => {
setState(remoteData.Loading);
fetch().then(
data => setState(remoteData.Success(data)),
error => setState(remoteData.Error(error))
);
};
const view = ({ notAsked, loading, error, success }: ViewInput<T, E>) => {
switch (state._type) {
case "NOT_ASKED":
return notAsked();
case "LOADING":
return loading();
case "ERROR":
return error(state.msg);
case "SUCCESS":
return success(state.data);
}
};
return [view, loadData];
};
// Example
const fakeFetch = (): Promise<User[]> =>
new Promise((res, rej) => {
setTimeout(
() =>
res([
{ id: 1, name: "User A" },
{ id: 2, name: "User B" },
{ id: 3, name: "User C" }
]),
1000
);
});
type User = {
id: number;
name: string;
};
const UserItems = ({ data }: { data: User[] }) => {
return (
<div>
{data.map(user => {
return (
<div key={user.id}>
{user.id}: {user.name}
</div>
);
})}
</div>
);
};
const App = () => {
const [renderView, loadData] = useRemoteData<User[], string>(fakeFetch);
return (
<div>
{renderView({
notAsked: () => <div className="start">Not Data Loaded</div>,
loading: () => <div className="loader">Loading...</div>,
error: error => (
<div className="error">Something went wrong: {error}</div>
),
success: data => <UserItems data={data} />
})}
<button onClick={loadData}>Load Data</button>
</div>
);
};
export default App;
@PavanGangireddy
Copy link

You could handle NoDataView as well like here: https://gist.github.com/busypeoples/6e88899c81825964ac614cc2e71fc2f5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment