React
by Abdul Ahmad
HTML
<div id="app"></div>
CSS
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
React
function App() {
const { valPromise } = useSomeHook();
const [state, setState] = React.useState();
React.useEffect(() => {
valPromise.then(r => {
setState(r);
});
}, [valPromise]);
if (!state) return 'not resolved yet';
return 'resolved: ' + state;
}
function useSomeHook() {
const [state, setState] = React.useState('');
const { promise, resolve } = usePromiseValue({ deps: [state] });
React.useEffect(() => {
getValTimeout({ setState });
}, []);
React.useEffect(() => {
if (!state) return;
resolve(state);
}, [state]);
return { valPromise: promise };
}
let count = 0;
function getValTimeout({ setState }) {
setInterval(() => {
setState(++count);
}, 1000);
}
function usePromiseValue({ deps } = {}) {
const [count, setCount] = React.useState(0);
const resolve = React.useRef();
const promise = React.useRef(new Promise(_resolve => {
// - useRef is actually called on every render, but the
// subsequent result is discarded
// - however, this can cause the `resolve.current` to be overwritten
// which will make the initial promise unresolvable
// - this condition takes care of ensuring we always resolve the
// first promise
if (!resolve.current) resolve.current = _resolve;
}));
React.useEffect(() => {
setTimeout(() => {
setCount(count + 1);
}, 300);
}, [...deps]);
React.useEffect(() => {
promise.current = new Promise(_resolve => {
resolve.current = _resolve;
});
}, [count]);
return {
promise: promise.current,
resolve: resolve.current,
};
}
ReactDOM.render(<App />, document.querySelector("#app"))