Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 2.0 kB view raw
1/* eslint-disable react-hooks/exhaustive-deps */ 2 3import { useMemo, useEffect, useState } from 'preact/hooks'; 4 5import type { Source } from 'wonka'; 6import { fromValue, makeSubject, pipe, concat, subscribe } from 'wonka'; 7 8type Updater<T> = (input: T) => void; 9 10let currentInit = false; 11 12// Two operations are considered equal if they have the same key 13const areOperationsEqual = ( 14 a: { key: number } | undefined, 15 b: { key: number } | undefined 16) => { 17 return a === b || !!(a && b && a.key === b.key); 18}; 19 20const isShallowDifferent = (a: any, b: any) => { 21 if (typeof a != 'object' || typeof b != 'object') return a !== b; 22 for (const x in a) if (!(x in b)) return true; 23 for (const key in b) { 24 if ( 25 key === 'operation' 26 ? !areOperationsEqual(a[key], b[key]) 27 : a[key] !== b[key] 28 ) { 29 return true; 30 } 31 } 32 return false; 33}; 34 35export function useSource<T, R>( 36 input: T, 37 transform: (input$: Source<T>, initial?: R) => Source<R> 38): [R, Updater<T>] { 39 const [input$, updateInput] = useMemo((): [Source<T>, (value: T) => void] => { 40 const subject = makeSubject<T>(); 41 const source = concat([fromValue(input), subject.source]); 42 43 const updateInput = (nextInput: T) => { 44 if (nextInput !== input) subject.next((input = nextInput)); 45 }; 46 47 return [source, updateInput]; 48 }, []); 49 50 const [state, setState] = useState<R>(() => { 51 currentInit = true; 52 let state: R; 53 try { 54 pipe( 55 transform(fromValue(input)), 56 subscribe(value => { 57 state = value; 58 }) 59 ).unsubscribe(); 60 } finally { 61 currentInit = false; 62 } 63 64 return state!; 65 }); 66 67 useEffect(() => { 68 return pipe( 69 transform(input$, state), 70 subscribe(value => { 71 if (!currentInit) { 72 setState(prevValue => { 73 return isShallowDifferent(prevValue, value) ? value : prevValue; 74 }); 75 } 76 }) 77 ).unsubscribe; 78 }, [input$ /* `state` is only an initialiser */]); 79 80 return [state, updateInput]; 81}