clean up hooks a bit; add usePrevious, useToggle, useFilter, useSticky, useCallbackRef, useMeasure hooks

pull/67/head
dperolio 3 years ago
parent 47660b84c4
commit 10315f7bc7
No known key found for this signature in database
GPG Key ID: 3E9BBAA710D3DDCE

@ -1,15 +1,16 @@
import { useLayoutEffect, useEffect, useState, useRef } from 'react';
import { useLayoutEffect, useEffect, useState, useRef, useCallback, useMemo } from 'react';
import { createFilter } from '@vizality/util/filter';
import { getObjectURL } from '@vizality/util/file';
import { get, post, put, del } from '../http';
/**
*
*
* @param {string} requestType Request type. One of get, post, put, or del.
* @param {url} url URL to call
* @param {string|object} [headers] Headers
*/
export function useFetch (requestType, url, headers) {
export const useFetch = (requestType, url, headers) => {
const [ response, setResponse ] = useState(null);
const [ loading, setLoading ] = useState(true);
const [ hasError, setHasError ] = useState(false);
@ -70,7 +71,7 @@ export function useFetch (requestType, url, headers) {
}, [ url ]);
return [ response, loading, hasError ];
}
};
/**
* Hook that uses `util.file.getObjectURL` to get an async collection of blob object URLs.
@ -98,7 +99,7 @@ export function useFetch (requestType, url, headers) {
* }, [ images ]);
* ```
*/
export function useFetchImageObjectURL (path, allowedExtensions) {
export const useFetchImageObjectURL = (path, allowedExtensions) => {
const [ response, setResponse ] = useState(null);
const [ loading, setLoading ] = useState(true);
const [ hasError, setHasError ] = useState(false);
@ -116,7 +117,7 @@ export function useFetchImageObjectURL (path, allowedExtensions) {
}, [ path, allowedExtensions ]);
return [ response, loading, hasError ];
}
};
/**
* Simple hook to force a component to rerender.
@ -130,10 +131,10 @@ export function useFetchImageObjectURL (path, allowedExtensions) {
* forceUpdate();
* ```
*/
export function useForceUpdate () {
export const useForceUpdate = () => {
const setValue = useState(0)[1];
return useRef(() => setValue(v => ~v)).current;
}
};
/**
* In rare cases you may need to do something right after component forceUpdate finishes.
@ -159,7 +160,7 @@ export function useForceUpdate () {
* );
* ```
*/
export function useForceUpdateWithCallback (callback) {
export const useForceUpdateWithCallback = callback => {
const [ value, setValue ] = useState(0);
const isUpdating = useRef(0);
useLayoutEffect(() => {
@ -172,6 +173,159 @@ export function useForceUpdateWithCallback (callback) {
isUpdating.current = 1;
setValue(v => ~v);
}).current;
}
};
export const usePrevious = value => {
/*
* The ref object is a generic container whose current property is mutable
* and can hold any value, similar to an instance property on a class.
*/
const ref = useRef();
/**
* Store the current value in ref.
*/
useEffect(() => {
ref.current = value;
}, [ value ]); // Only re-run if value changes
/**
* Return previous value (happens before update in useEffect above).
*/
return ref.current;
};
/**
* Toggles a value.
* @param {boolean} [initialState=false] Initial state
* @example
* ```
* function App() {
* const [isTextChanged, setIsTextChanged] = useToggle();
*
* return (
* <button onClick={setIsTextChanged}>
* {isTextChanged ? 'Toggled' : 'Click to Toggle'}
* </button>
* );
* }
* ```
*/
export const useToggle = (initialState = false) => {
/**
* Initialize the state.
*/
const [ state, setState ] = useState(initialState);
/**
* Define and memorize toggler function in case we pass down the comopnent,
* This function change the boolean value to it's opposite value
*/
const toggle = useCallback(() => setState(state => !state), []);
return [ state, toggle ];
};
/**
*
* @param {*} param
* @returns
* @example
* ```
* const App = () => {
* const { inputText, setInputText, filtered } = useFilter({
* keys: [ 'user.name', 'subject', 'dest.name' ],
* data: sampleData,
* });
*
* return (
* <div className='App'>
* <input
* type='text'
* value={inputText}
* onChange={evt => setInputText(evt.target.value)}
* />
* {JSON.stringify(filtered)}
* </div>
* );
* };
* ```
*/
export const useFilter = ({ keys, data }) => {
const [ query, setQuery ] = useState('');
const filtered = useMemo(
() => data.filter(createFilter(query, keys)),
[ data, query ]
);
return [ query, setQuery, filtered ];
};
export const useSticky = ({ defaultSticky = false }) => {
const [ sticky, setSticky ] = useState(defaultSticky);
const tableRef = useRef(null);
const toggleSticky = useCallback(
({ top, bottom }) => {
if (top <= 0 && bottom > 2 * 68) {
!sticky && setSticky(true);
} else {
sticky && setSticky(false);
}
},
[ sticky ]
);
useEffect(() => {
const handleScroll = () => {
toggleSticky(tableRef.current.getBoundingClientRect());
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [ toggleSticky ]);
return { sticky, setSticky };
};
/**
*
* Sourced from @see {@link https://codesandbox.io/s/animate-height-framer-motion-yn59l?file=/src/use-callback-ref.js:48-209}
* @returns
*/
export const useCallbackRef = () => {
const [ ref, setRef ] = useState(null);
const fn = useCallback(node => {
setRef(node);
}, []);
return [ ref, fn ];
};
/**
*
* Sourced from @see {@link https://codesandbox.io/s/animate-height-framer-motion-yn59l?file=/src/use-measure.js}
* @returns
*/
export const useMeasure = ref => {
const [ element, attachRef ] = useCallbackRef();
const [ bounds, setBounds ] = useState({});
useEffect(() => {
function onResize([ entry ]) {
setBounds({
height: entry.contentRect.height
});
}
const observer = new ResizeObserver(onResize);
if (element && element.current) {
observer.observe(element.current);
}
return () => observer.disconnect();
}, [ element ]);
useEffect(() => {
attachRef(ref);
}, [ attachRef, ref ]);
return bounds;
};
export default this;

Loading…
Cancel
Save