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"))