Created
January 21, 2022 20:44
-
-
Save honzabrecka/180a6a3ada7fdb2776515d0205ff7845 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useCallback, useMemo, Suspense } from "react"; | |
const [rejected, resolved, pending] = ["rejected", "resolved", "pending"]; | |
export const wrapAsyncResource = (factory, lazy = true) => { | |
let promise; | |
let state = [pending, undefined]; | |
const startPromise = () => { | |
promise = factory(); | |
promise | |
.then((data) => { | |
state = [resolved, data]; | |
}) | |
.catch((error) => { | |
state = [rejected, error]; | |
}); | |
}; | |
if (!lazy) { | |
startPromise(); | |
} | |
return { | |
getValue() { | |
if (!promise) { | |
startPromise(); | |
} | |
const [status, result] = state; | |
if (status === pending) { | |
throw promise; | |
} | |
if (status === rejected) { | |
throw result; | |
} | |
return result; | |
}, | |
getPromise() { | |
if (!promise) { | |
startPromise(); | |
} | |
return promise; | |
} | |
}; | |
}; | |
const wrapDynamicAsyncResource = (factory, lazy) => { | |
const resources = {}; | |
const createIfUndefined = (id) => { | |
if (resources[id]) return; | |
resources[id] = wrapAsyncResource(factory, lazy); | |
}; | |
return { | |
getValue(id) { | |
createIfUndefined(id); | |
return resources[id].getValue(); | |
}, | |
getPromise(id) { | |
createIfUndefined(id); | |
return resources[id].getPromise(); | |
} | |
}; | |
}; | |
export const useResource = ({ | |
factory, | |
lazy = true, | |
initialArgs = [], | |
wrapper = wrapAsyncResource | |
}) => { | |
const [resource, setResource] = useState(() => | |
wrapper(() => factory(...initialArgs), lazy) | |
); | |
const refresh = useCallback( | |
(...args) => { | |
setResource(wrapper(() => factory(...args), lazy)); | |
}, | |
[factory, lazy, wrapper] | |
); | |
return [resource, refresh]; | |
}; | |
// | |
// fake, just for demonstration how to pass another hook (it's result) inside useResource | |
const useApi = () => { | |
return {}; | |
}; | |
const fetchChart = async (id) => { | |
// return fetch(`api/chart/${id}`); | |
return { series: [{ source: "bar" }] }; | |
}; | |
const fetchChartCollection = async (id) => { | |
// return fetch(`api/chart-collection/${id}`); | |
return [1, 2, 3]; | |
}; | |
const ChartInner = ({ id, resource, chartFieldCollections }) => { | |
const { series } = resource.getValue(); | |
console.log(chartFieldCollections); | |
// somehow collect required "sources" from series | |
const sources = useMemo(() => { | |
// each inner resource is lazy, so is created on demand and fetched when needed | |
// many charts can ask for one "source" (resource) and it will be fetched just once | |
// All the hard work (is all ready?) will be done by react :) | |
return series.map(({ source }) => chartFieldCollections.getValue(source)); | |
}, [series, chartFieldCollections]); | |
// render chart as now we have all the data... | |
console.log({ id, series, sources }); | |
return <canvas />; | |
}; | |
const Chart = ({ id, chartFieldCollections }) => { | |
// in real app it would be useChart wrapping this | |
const [resource] = useResource({ factory: fetchChart, initialArgs: [id] }); | |
return ( | |
<Suspense fallback="loading"> | |
<ChartInner | |
id={id} | |
chartFieldCollections={chartFieldCollections} | |
resource={resource} | |
/> | |
</Suspense> | |
); | |
}; | |
export default function App() { | |
const api = useApi(); | |
// no caching, just state uplifting so one resource can be shared | |
// across children | |
const [chartFieldCollections] = useResource({ | |
factory: fetchChartCollection, | |
initialArgs: [api], | |
wrapper: wrapDynamicAsyncResource | |
}); | |
return ( | |
<div className="App"> | |
<Chart id={"foo"} chartFieldCollections={chartFieldCollections} /> | |
</div> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment