streamich / react-use Goto Github PK
View Code? Open in Web Editor NEWReact Hooks — 👍
Home Page: http://streamich.github.io/react-use
License: The Unlicense
React Hooks — 👍
Home Page: http://streamich.github.io/react-use
License: The Unlicense
I'd like to have some useDistinct
hook, which takes a value (or another hook) and only changes state if the returned value has changed.
It's kinda the opposite way of useMemo
. It should rerender based on output instead of input value.
I'm not sure if that's possible at all...
Usage:
const compareFn = (oldValue, newValue) => {
return oldValue[0] === newValue[0];
}
const MyComponent = (props) => {
const [state, setState] = useDistinct(useState("foo"), compareFn);
console.log("rerendered", state)
return (
<button onClick={() => setState("foo")}>Change State</button>
)
}
Explanation:
When not using useDistinct
, every call to setState
will rerender the component. Even if you call setState(state)
.
When using useDistinct
, it should check for ===
equality. Or you can provide a custom comparison function which does the equality check.
https://github.com/salvoravida/react-universal-hooks
Hi!
Could be usefull, if you need a custom Hook in your class component without refactor ...
your feedback would be appreciated!
I think you can add it to the readme.😊
@streamich I think it's good idea to put EditorConfig in this project to make sure all contributors have the same configuration and prevent unnecessary the code formatting change over each PR.
Cheers,
Max
Create animation hook that interpolates a map of values: https://github.com/streamich/libreact/blob/master/docs/en/Interpolation.md
Any idea on useForm with (async) validation as a hook ?
Explicitly defining the licence will ensure there are no future developer concerns about whether or not this module is for them. May I suggest a permissive license such as MIT or Apache?
I implemented a similar hook recently and learned some stuff along the way.
Notably, useRefMounted
needs to use callback refs to be sure that the ref is updated whenever the component is (deeply) mounted and unmounted.
Nice work!
Currently useDebounce
only creates a new timeout if args
change. Is there any reason for this?
I tried adding delay
to args
like this:
useDebounce(() => setDebouncedState(state), delay, [state, delay]);
But this causes an additional (= unnecessary) rerender.
EDIT 4
I'm trying to create a useDebouncedState
hook, with dynamic debounce, like this:
const useDebouncedState = <T>(initialState: T): [T, T, (state: React.SetStateAction<T>, delay?: number) => void] => {
const [delay, setDelay] = useState(0);
const [state, setState] = useState(initialState);
const [debouncedState, setDebouncedState] = useState(initialState);
const updateDebouncedState = useCallback(() => {
if(state !== debouncedState) {
setDebouncedState(state);
}
}, [state, debouncedState]);
useDebounce(updateDebouncedState, delay, [state]);
const updateState = useCallback((nextState: React.SetStateAction<T>, delay: number = 0) => {
if(nextState instanceof Function) {
nextState = nextState(state);
}
setDelay(delay);
setState(nextState);
if(delay <= 0) {
setDebouncedState(nextState);
}
}, [state]);
return [state, debouncedState, updateState];
}
It works fine, as long as the state
changes. But it won't trigger when I pass equal state
with a new delay
to updateState
.
What it's intended to do:
setState({...state, query: ''}, 300)
.setState(state, 0)
or setState({...state, checkbox: true}, 0)
.state
is the current state, which can be used as value
for input fieldsdebouncedState
can be used with useEffect
to detect, when to trigger an action (for example execute API request)Test code:
const [state, debouncedState, setState] = useDebouncedState("foo");
useEffect(() => {
const s = "bar";
setTimeout(() => {
setState(s, 5000);
}, 0);
setTimeout(() => {
setState(s, 0); // this won't do anything, because only delay changed
}, 1000);
}, [])
EDIT: Maybe you could also add an optimization:
if(ms < 1) {
fn.bind(null, args)();
} else {
const t = setTimeout(fn.bind(null, args), ms);
setTimeoutVar(t);
}
This prevents rerender, when using zero (or negative) delay.
Btw: Is there any need for using bind
instead of simply fn(args)
?
EDIT 2: Here's what all my changes look like:
const useDebounce = (fn: () => any, ms: number = 0, args: Array<any> = []) => {
const [timeout, setTimeoutVar] = useState<any>(null);
useEffect(() => {
// if args change then clear timeout
clearTimeout(timeout);
if(ms < 1) { // optimization for zero-delay
fn.bind(null, args)();
} else {
const t = setTimeout(fn.bind(null, args), ms);
setTimeoutVar(t);
}
}, [ms, ...args]); // here I added `ms`
};
EDIT 3: Another optimization would be to remove const [timeout, setTimeoutVar]
. Because setTimeoutVar(t);
will trigger a rerender, too.
My tests showed that this code for useDebounce
rerenders less:
const useDebounce = (fn: () => any, ms: number = 0, args: Array<any> = []) => {
useEffect(() => {
// if args change then clear timeout
let t: any;
if(ms < 1) {
fn.bind(null, args)();
} else {
t = setTimeout(fn.bind(null, args), ms);
}
return () => { // use this callback for clearTimeout, it has been
clearTimeout(t); // suggested by Dan Abramov himself:
} // https://twitter.com/dan_abramov/status/1060729512227467264
}, [ms, ...args]);
};
Though I'm not sure if it's breaking change? But it seems to works fine.
EDIT 4: Forget (nearly) everything I said 😆
It looks like setTimeoutVar(t);
is the only real problem that causes unnecessary rerenders!
So... This code seems to work fine in combination with useDebounce(() => ..., delay, [state, delay])
:
const useDebounce = (fn: () => any, ms: number = 0, args: Array<any> = []) => {
useEffect(() => {
let handle = setTimeout(fn.bind(null, args), ms);
return () => {
// if args change then clear timeout
clearTimeout(handle);
}
}, args);
};
since update to react 16.8.2
useSetState doesn't work
Peer dependencies should be updated to the master release of [email protected] [email protected]
It would be nice to have useThrottle, implemented similarly to useDebounce
Some of my pages and components use use-css
to style, but when I switched to them, them would always twinkle at first, without styles and become normal in the next flash.
version: react-use 5.7.1
This library minified is almost 52kb, which is huge for what it is. 60% of the bundle size is solely based on dependencies, all of which are only used in a few hook.
I would suggest making turning those into peerDependencies
and requiring users to include the libraries in their own package.json
. This would make the size of this library ~21kB.
Although tree shaking should take care of this, not all environments support it, and forcing dependencies on users isn't the best course of action, especially if it's for a small functionality.
If this is a go, I could make the PR with the changes (code & docs) required for this.
Port scratch sensor: https://github.com/streamich/libreact/blob/master/docs/en/ScratchSensor.md
reason: callbag-subscribe
not transpiled to es5
Add useStateWithGetter
.
First of all, thank you for this package, it has been very useful for my projects!
I've noticed the production build it's not fully compatible with IE11.
I managed to do a "manual tree-shake" using babel-plugin-transform-imports
, but i still get errors on IE11 using some hooks, e.g:
// ...
// production build of useWindowSize, IE11 won't be able to destructure this
const [state, setState] = react_1.useState({
width: isClient ? window.innerWidth : initialWidth,
height: isClient ? window.innerHeight : initialHeight,
});
// ...
My question is: can we add a .browserslistrc
on the package, so we can get full support for older browsers in the production build?
Add useLocalStorage
hook.
const [value, setValue] = useLocalStorage('key', initialValue);
I’ve enabled the strict
option in my tsconfig.json
, and installing this package results in an error because it can’t find types for the useCallbag
hook, even though I don’t use that hook. It seems to be trying to import the types for the use-callbag
package, which don’t exist. Can you fix this error?
Add React Hooks that can be used component lifecycles.
Hey there!
Saw that you already got use-styles
on npm, but couldn't find anything on GitHub.
Are you actively working on something? Would be keen to share experiences, since I have been working on something very similar: https://github.com/andywer/react-usestyles
Cheers!
since after the first call,state.loading
was set to false in useEffect
.After then state.loading
would still be false in the second call for refetching.
so maybe state should be reset in useEffect
:
react_1.useEffect(
() => {
let mounted = true;
set({ loading: true }); // reset state
const promise = memoized();
promise.then(
value => {
if (mounted) {
set({
loading: false,
value
});
}
},
error => {
if (mounted) {
set({
loading: false,
error
});
}
}
);
return () => {
mounted = false;
};
},
[memoized]
);
It would be great to support ESModules to provide better tree shaking, rollup might be helpful
P.S. I mean mjs format
Convert DropArea
to useDropArea
hook.
const [el] = useDropArea(<div/>, {
onFiles: () => {},
// ...
});
or
const ref = useRef();
useDropArea(ref, {
onFiles: () => {},
// ...
});
Would like to add useSessionStorage.
Options API-wise:
s/local/session
storageKey
that defaults to 'localStorage'
and have useSessionStorage just call that. Then in useLocalStorage, s/localStorage/window[storageKey]
What would you prefer?
useToggle called twice in a row should be a no-op, but acts as if toggle was only called once.
https://codesandbox.io/s/p0r6m3zk7
Line 17 in useToggle should become setValue(prevValue => !prevValue)
https://github.com/streamich/react-use/blob/master/src/useToggle.ts#L17
Whenever the next state value depends on a previous state value, the state setter should take a function and return the new state.
Port mouse sensor: https://github.com/streamich/libreact/blob/master/docs/en/MouseSensor.md
I would like to propose a better typed version of your useDebounce
, here my actual step:
import { useEffect, useRef } from 'react';
import cloneDeep from 'lodash/fp/cloneDeep';
import noop from 'lodash/fp/noop';
// ------------------------------------------------------------ # Type aliases #
type AnyCallback = (...args: any[]) => any;
type Debounce<Callback extends AnyCallback> = (...params: Parameters<Callback>) => void;
// -------------------------------------------------------------------- # Hook #
export default function useDebounce() {
return <Callback extends AnyCallback>(callback: Callback, delay = 250) => {
const timer = useRef<NodeJS.Timeout>(null);
const debounce = useRef<Debounce<Callback>>(noop);
useEffect(() => {
debounce.current = (...params: Parameters<Callback>) => {
const paramsCopy = cloneDeep(params);
if (timer.current) {
clearTimeout(timer.current);
}
// @ts-ignore
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065
timer.current = setTimeout(() => callback(...paramsCopy), delay);
};
}, []);
return debounce.current;
};
}
Usage:
import useDebounce from './path/to/useDebounce'
function Component() {
const debounce = useDebounce()
function handleClick(event: React.ChangeEvent<HTMLInputElement>) {
console.log('Clicked with debounce')
}
return <input type="text" onChange={debounce(handleClick)} />
}
Any remark will be highly appreciated!
This issue is just to note that useLocation will break on IE11 without a polyfill for window.Event
. even when <meta http-equiv="x-ua-compatible" content="IE=edge">
is already in head.
IE9, 10 and 11 will break right here when new Event('pushstate');
is run by calling history.push('/something')
in your react app.
Line 9 in bc3760d
Object doesn't support this action
You need to polyfill window.Event
which import '@babel/polyfill';
is not doing for you. So here goes what I've added to my polyfill.js
/**
* To detect you are in IE (for this case) by checking typeof(Event) which is
* 'function' for all except IE where it is 'object'.
* You can then safely polyfill the Event constructor using the approach above.
* In IE11 it seems to be safe to set window.Event = CustomEvent.
*/
(function() {
if (typeof window.Event === 'function') return false; //If not IE
function CustomEvent(event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined,
};
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(
event,
params.bubbles,
params.cancelable,
params.detail
);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.Event = CustomEvent;
})();
The solution was provided here (accepted answer). Don't skip the comments.
https://stackoverflow.com/questions/26596123/internet-explorer-9-10-11-event-constructor-doesnt-work
Use the same as useState but with shallow compare
master
branch failed. 🚨I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.
You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.
Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.
Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master
branch. You can also manually restart the failed CI job that runs semantic-release.
If you are not sure how to resolve this, here is some links that can help you:
If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.
The npm token configured in the NPM_TOKEN
environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/
.
If you are using Two-Factor Authentication, make configure the auth-only
level is supported. semantic-release cannot publish with the default auth-and-writes
level.
Please make sure to set the NPM_TOKEN
environment variable in your CI with the exact value of the npm token.
Good luck with your project ✨
Your semantic-release bot 📦🚀
Here's a plain Javascript version that I'm using in my application:
function useUserMedia(constraints, errorCallback=(()=>undefined)) {
const [stream, setStream] = useState();
useEffect(_ => {
navigator.mediaDevices.getUserMedia(constraints)
.then(setStream)
.catch(errorCallback)
}, [stream]);
return stream;
}
To get user's front camera and audio feed:
const constraints = {
video: true,
audio: true
};
const onError = (e) => alert(e);
const localstream = useUserMedia(constraints, onError);
Create useSlider
hook, based on:
What about that possible hook?
type HookProps = [number, (position: number): number, ?number];
type HookParams = {
pageSize: number,
collectionSize: number,
initialCurrentPage: ?number = 1,
}
function ListComponent() {
const params: HookParams = {
pageSize,
collectionSize,
initialCurrentPage,
};
const [currentPage, gotoPage, totalPages]: HookProps = usePaginator(params);
const gotoNext = (): number => gotoPage(currentPage + 1)
const gotoPrev = (): number => gotoPage(currentPage - 1)
return (
<Pagination {...} />
)
}
Create useFullScreen
hook, based on:
I tried to use the package on next.js and it throws the following error:
Error: Addon "cssom" is missing the following dependencies:
require("nano-css/addon/sh").addon(nano);
at warnOnMissingDependencies (/Users/rafael/Code/blumpa-ssr/node_modules/nano-css/addon/__dev__/warnOnMissingDependencies.js:23:15)
at exports.addon (/Users/rafael/Code/blumpa-ssr/node_modules/nano-css/addon/cssom.js:5:55)
at Object.<anonymous> (/Users/rafael/Code/blumpa-ssr/node_modules/react-use/lib/useCss.js:27:1)
at Module._compile (module.js:653:30)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
at Module.require (module.js:597:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (/Users/rafael/Code/blumpa-ssr/node_modules/react-use/lib/index.js:17:18)
at Module._compile (module.js:653:30)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
I think the logic here may not let current animation stop perfectly, which may lead to memory leak or related things.
https://github.com/streamich/react-use/blob/master/src/useRaf.ts#L28-L30
As cancelAnimationFrame
method is executed in the clean-up period, the animation ID "stored" in raf
can be perfectly cancelled. However, the onFrame
method may continues (especially when the main thread render DOM slowly, and it will take sometime between useEffect doing cleaning work and your requestAnimationFrame method's running, https://github.com/streamich/react-use/blob/master/src/useRaf.ts#L9 ), so that a new raf
will be assigned, even though you already canvel previous raf
.
In this way, you don't clean up previous animation, and next effect will bring in a new animation, which is the problem.
In my opinion, a safe way to guarantee current animation's stop is to add conditions in your main loop method, onFrame
method can be rewrited to:
const onFrame = () => {
const time = Math.min(1, (Date.now() - start) / ms);
set(time);
if (YOUR_CANCEL_CONDITION) {
stop();
} else {
loop();
}
};
const stop = () => {
cancelAnimationFrame(raf);
}
in which YOUR_CANCEL_CONDITION is the logic you should handle with.
Create useObservable
that tracks state of an Observable
as shown here:
function useSubscribe(observable$) {
const [value, update] = useState();
useLayoutEffect(() => {
const s = observable$.subscribe(update)
return () => s.unsubscribe();
}, [observable$]);
return value;
}
Hook that creates a dispatcher for errors, that can be caught with error boundaries.
const useError = () => {
const [err, raise] = useState();
if (err) {
throw err;
}
return raise;
};
const Demo = () => {
const raise = useError();
useEffect(() => {
setTimeout(() => {
raise(
new Error("Could not work with setTimeout for some reason!")
);
}, 1000);
});
return <div>Hello world</div>;
};
Alternative names: useError
, useRaise
, useThrow
, useErrorDispatch
.
Seen here: reactjs/rfcs#84 🧐
Port Google Sign-in for Websites, see: https://github.com/streamich/libreact/blob/master/docs/en/GoogleAuth.md
just like Query Mutation in react-apollo
:
<Query query={gql_document} variables={variables}>
{({ data, error, loading }) => <div></div>}
</Query>
<Mutation mutation={gql_document}>
{(mutation_api, mutation_result) => {
const { data, error, loading } = mutation_result;
return <div></div>
}}
</Mutation>
And now we have useAsync
, but it can only act as Query
.
We should have an useApi
to act as Mutation
.
// A very very naive implementation
const useApi = (promisified_api, variables) => {
const [state, set_state] = useState({loading: !!variables});
const api = async (...variables) => {
set_state({loading:true});
const data = await promisified_api(...variables);
set_state({data});
}
// we should expose `set_state`, it can act as Query's updteQuery, and use can set_state({error:null}) manually when close the error dialog.
return [api, state, set_state];
}
const [api, state, set_state] = useApi(fn_return_promise, maybe_variables);
Suppose we want to localize component named UserProfile
// UserProfile.jsx
const UserProfile = ({ firstName, lastName }) => {
return (
<article>
Hello, {firstName}!
<div>First Name: {firstName}</div>
<div>Last Name: {lastName}</div>
<footer>
Full Name: {firstName} {lastName}
</footer>
</article>
);
};
At first we should define dynamically imported translations file per language:
// UserProfile.rus.jsx
export default {
"First Name": "Имя",
"Last Name": "Фамилия",
"Hello, ${name}!": name => `Привет, ${name}!`,
footer: (firstName, lastName) => (
<footer>
Полное Имя: {firstName} {lastName}
</footer>
)
};
Translation file is plain object, which values are strings or functions returning strings or even JSX markup.
Then we create React Context for locale LocaleContext = React.createContext()
and new hook named useTranslate
. It loads translations file on every context change and returns translation function to a component.
// UserProfile.jsx
const UserProfile = ({ firstName, lastName }) => {
// translation function
const tr = useTranslate(lang =>
import(/* webpackChunkName: "i18n-" */ `./UserProfile.${lang}.jsx`)
);
return (
<article>
{tr`Hello, ${firstName}!`}
<div>
{tr`First Name`}: {firstName}
</div>
<div>
{tr`Last Name`}: {lastName}
</div>
{tr("footer", firstName, lastName) || (
<footer>
Full Name: {firstName} {lastName}
</footer>
)}
</article>
);
};
Translation function can work in two modes:
Implementation can be found here: gist (about 100 SLOC).
If this is interesting, I can submit a PR.
Pending tests to be added
Port element scroll and window scroll sensors:
It just changes the value the first time.
Incorrect behavior can be found in the documentation demo itself:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.