Skip to content

Instantly share code, notes, and snippets.

@honzabrecka
Last active July 23, 2020 13:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save honzabrecka/9debf402fd4bc5001a2b3ef23ff3e8a6 to your computer and use it in GitHub Desktop.
Save honzabrecka/9debf402fd4bc5001a2b3ef23ff3e8a6 to your computer and use it in GitHub Desktop.
import React, {
Suspense,
useCallback,
useState,
useRef,
useEffect,
unstable_useTransition,
} from "react";
const delay = (d) => new Promise((res) => setTimeout(res, d));
/// FAKES
let users = {
"/user/1": {
id: 1,
firstname: "Alfons",
lastname: "Baar",
},
"/user/2": {
id: 2,
firstname: "Charles",
lastname: "Leclerc",
},
"/user/3": {
id: 3,
firstname: "Eddy",
lastname: "Foobar",
},
};
const fakeFetch = (url, options) => {
console.log("fakeFetch", url, options);
if (options) {
users[url] = options.body;
}
return delay(1500).then(() => users[url]);
};
/// LIB
function wrapPromise(x, lazy) {
const [rejected, resolved, pending] = [0, 1, 2];
let state = [pending, undefined];
let promise;
function start() {
promise = x();
promise
.then((data) => {
state = [resolved, data];
})
.catch((error) => {
state = [rejected, error];
});
}
if (!lazy) start();
return {
read() {
if (!promise) start();
const [status, result] = state;
if (status == pending) throw promise;
if (status == rejected) throw result;
return result;
},
write(value) {
state = [resolved, value];
},
};
}
const useResource = (factory, options, params) => {
const [startTransition, isPending] = unstable_useTransition({
timeoutMs: options.optimisticUpdateTimeout || 1000,
});
const [resource, setResource] = useState(() => factory(params));
const refresh = useCallback(() => {
startTransition(() => {
setResource(factory(params));
});
}, [factory]);
const update = useCallback(
(value) => {
resource.write && resource.write(value);
startTransition(() => {
setResource(factory(params, value));
});
},
[factory]
);
return [resource, refresh, update, isPending];
};
const createResource = ({ read, write, lazy = false }) => (params, value) => {
return wrapPromise(() => (value ? write(params, value) : read(params)), lazy);
};
const createPaginatedResource = ({ read }) => () => {
return {
read() {},
next() {},
isNextLoading() {},
hasNext() {},
};
};
/// APP
const Users = ({ resource, refresh }) => {
const users = resource.read();
return (
<>
<button onClick={refresh}>refresh</button>
<ul>
{Object.entries(users).map(([key, { firstname, lastname }]) => (
<li key={key}>
{firstname} {lastname}
</li>
))}
</ul>
</>
);
};
const User = ({ resource, update, isPending }) => {
const { firstname, lastname } = resource.read();
const onClick = useCallback(() => {
update({ firstname: "Johan", lastname });
}, [update, lastname]);
return (
<ul>
<li
style={{ background: isPending ? "#CCC" : "#FFF" }}
onClick={isPending ? undefined : onClick}
>
{firstname}
</li>
<li>{lastname}</li>
</ul>
);
};
const WithResource = ({
factory,
fallback,
children,
optimisticUpdateTimeout,
...params
}) => {
const [resource, refresh, update, isPending] = useResource(
factory,
{ optimisticUpdateTimeout },
params
);
return (
<Suspense fallback={fallback}>
{typeof children === "function"
? children({ resource, refresh, update, isPending })
: React.cloneElement(React.Children.only(children), {
resource,
refresh,
update,
isPending,
})}
</Suspense>
);
};
const App = () => {
const usersResourceFactory = useCallback(
createResource({
read() {
return delay(1500).then(() => ({ ...users }));
},
}),
[]
);
const userResourceFactory = useCallback(
createResource({
read: ({ id }) => {
return fakeFetch(`/user/${id}`);
},
write: async ({ id }, value) => {
await fakeFetch(`/user/${id}`, { body: value });
return fakeFetch(`/user/${id}`);
},
}),
[]
);
return (
<>
<WithResource fallback={"loading..."} factory={usersResourceFactory}>
<Users />
</WithResource>
<h2>#1</h2>
<WithResource
fallback={"loading..."}
factory={userResourceFactory}
id={1}
>
<User />
</WithResource>
<WithResource
fallback={"loading..."}
factory={userResourceFactory}
id={2}
optimisticUpdateTimeout={4000}
>
{({ resource, update, isPending }) => {
return (
<>
<h2>#2 a</h2>
<Suspense fallback="loading...">
<User
resource={resource}
update={update}
isPending={isPending}
/>
</Suspense>
<h2>#2 a</h2>
<Suspense fallback="loading...">
<User
resource={resource}
update={update}
isPending={isPending}
/>
</Suspense>
</>
);
}}
</WithResource>
<h2>#3</h2>
<WithResource
fallback={"loading..."}
factory={userResourceFactory}
id={3}
>
<User />
</WithResource>
</>
);
};
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment