Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { createContext, useSyncExternalStore, useContext, useReducer, Suspense } from 'react';
- function assertNever(state) {
- throw new Error(`Unexpected state: ${state}`);
- }
- function createFetchContext() {
- return {
- cache: {},
- };
- }
- const FetchContext = createContext(createFetchContext());
- function createSlice(url) {
- var debugUrl = new URL(url).pathname
- console.log(debugUrl + ': Created slice')
- let state = {
- state: 'idle',
- }
- let listeners = [];
- const entry = {
- getState: () => state,
- dispatch: (action) => {
- console.log(debugUrl + ': dispatch: ' + action.type)
- switch (action.type) {
- case 'reset':
- state = {
- state: 'idle',
- }
- break;
- case 'success':
- state = {
- state: 'success',
- data: action.payload,
- }
- break;
- case 'error':
- state = {
- state: 'error',
- data: action.payload,
- }
- break;
- default:
- return assertNever(action);
- }
- listeners.forEach(listener => listener());
- },
- subscribe: (listener) => {
- listeners = listeners.toSpliced(-1, 0, listener);
- console.log(debugUrl + ': subscribe: ' + listeners.length + " listeners")
- return () => {
- const index = listeners.indexOf(listener);
- if (index >= 0) {
- // We make a copy of the listeners to deal with the case of the listeners changing as we loop over them
- listeners = listeners.toSpliced(index, 1);
- }
- console.log(debugUrl + ': unsubscribe: ' + listeners.length + " listeners")
- };
- },
- getStateAndDispatch: () => {
- if (state.state === 'idle') {
- // The idle state is a phantom state that is never returned to the user
- // It is used to indicate that the fetch is in progress
- console.log(debugUrl + ': getStateAndDispatch: Started fetching')
- const promise = fetch(url).then(response => response.json());
- state = {
- state: 'loading',
- data: promise,
- }
- promise.then(
- data => entry.dispatch({ type: 'success', payload: data }),
- error => entry.dispatch({ type: 'error', payload: error }),
- );
- }
- return state;
- },
- };
- return entry;
- }
- function useFetch(url) {
- var context = useContext(FetchContext);
- var entry = context.cache[url] ??= createSlice(url);
- var state = useSyncExternalStore(entry.subscribe, entry.getStateAndDispatch);
- switch (state.state) {
- case 'loading':
- throw state.data;
- case 'success':
- return state.data;
- case 'error':
- throw state.data;
- default:
- return assertNever(state);
- }
- }
- function Expander({ children, summary }) {
- const [opened, onClick] = useReducer((state) => !state, false);
- return <>
- <p>
- <button onClick={onClick}>{opened ? 'Close ' : 'Open '}{summary}</button>
- </p>
- {opened && <Suspense fallback={<p>Loading...</p>}>
- {children}
- </Suspense>}
- </>
- }
- function FetchSingle ({ url, Component }) {
- const json = useFetch(url);
- return <fieldset>
- <legend><code>{url}</code></legend>
- <Component data={json} />
- </fieldset>;
- }
- function FetchList ({ url, Component }) {
- const json = useFetch(url);
- return <fieldset>
- <legend><code>{url}</code></legend>
- {json.map(item => <fieldset key={item.id}><Component data={item} /></fieldset>)}
- </fieldset>;
- }
- function User ({ data }) {
- return <>
- <p>User: {data.name}</p>
- <p>Email: {data.email}</p>
- <p>Phone: {data.phone}</p>
- <Expander summary="Posts">
- <FetchList url={`https://jsonplaceholder.typicode.com/users/${data.id}/posts`} Component={Post} />
- </Expander>
- </>;
- }
- function Post ({ data }) {
- return <>
- <p>Post: {data.title}</p>
- <p>Body: {data.body}</p>
- <Expander summary={`User ${data.userId}`}>
- <FetchSingle url={`https://jsonplaceholder.typicode.com/users/${data.userId}`} Component={User} />
- </Expander>
- <Expander summary={`Comments`}>
- <FetchList url={`https://jsonplaceholder.typicode.com/posts/${data.id}/comments`} Component={Comment} />
- </Expander>
- <Expander summary={`Todos`}>
- <FetchList url={`https://jsonplaceholder.typicode.com/posts/${data.id}/todos`} Component={Todo} />
- </Expander>
- <Expander summary={`Albums`}>
- <FetchList url={`https://jsonplaceholder.typicode.com/posts/${data.id}/albums`} Component={Album} />
- </Expander>
- </>;
- }
- function Comment ({ data }) {
- return <>
- <p>Comment: {data.name}</p>
- <p>Email: {data.email}</p>
- <p>Body: {data.body}</p>
- <Expander summary={`Post ${data.postId}`}>
- <FetchSingle url={`https://jsonplaceholder.typicode.com/posts/${data.postId}`} Component={Post} />
- </Expander>
- </>;
- }
- function Todo ({ data }) {
- return <>
- <p>Todo: {data.title}</p>
- <p>Completed: {data.completed ? 'Yes' : 'No'}</p>
- <Expander summary={`User ${data.userId}`}>
- <FetchSingle url={`https://jsonplaceholder.typicode.com/users/${data.userId}`} Component={User} />
- </Expander>
- </>;
- }
- function Album ({ data }) {
- return <>
- <p>Album: {data.title}</p>
- <Expander summary={`User ${data.userId}`}>
- <FetchSingle url={`https://jsonplaceholder.typicode.com/users/${data.userId}`} Component={User} />
- </Expander>
- <Expander summary={`Photos`}>
- <FetchList url={`https://jsonplaceholder.typicode.com/albums/${data.id}/photos`} Component={Photo} />
- </Expander>
- </>;
- }
- function Photo ({ data }) {
- return <>
- <p>Photo: {data.title}</p>
- <p>URL: <a href={data.url}>{data.url}</a></p>
- </>;
- }
- export function App () {
- return (
- <Suspense fallback={<p>Loading...</p>}>
- <FetchSingle url="https://jsonplaceholder.typicode.com/users/1" Component={User} />
- </Suspense>
- );
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement