A collection of Upstatement's most-used React hooks
A collection of Upstatement's most-used React hooks across projects, updated to have first-class TypeScript support!
This package has the following peer dependencies:
- React v16.8.0+ (for hooks ⚓️)
With npm:
$ npm install @upstatement/react-hooks
With yarn:
$ yarn add @upstatement/react-hooks
Then with a module bundler like webpack, use as you would anything else:
// using ES6 modules
import { useMultiRef, useSet } from '@upstatement/react-hooks';
import useMultiRef from '@upstatement/react-hooks/dist/esm/useMultiRef';
// using CommonJS modules
const { useMultiRef, useSet } = require('@upstatement/react-hooks');
const useMultiRef = require('@upstatement/react-hooks/dist/cjs/useMultiRef');
This basic hook provides an extra layer of security over React's existing useState
hook. While it functions the exact same as the original, our hook will no longer accept updates to the state post-unmount.
Learn more about the useState
API and Usage
The API remains unchanged from React's useState
hook: https://reactjs.org/docs/hooks-reference.html#usestate
import { useState } from '@upstatement/react-hooks';
const App = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count => count + 1);
};
return (
<div>
<h4>Count: {count}</h4>
<button onClick={increment}>Increment</button>
</div>
);
};
This hook is a step between the useState
and useReducer
hooks, providing a way to create a mini state store inside your component.
Learn more about the useStateReducer
API and Usage
const [state, set] = useStateReducer(initialState);
The initial state should be a record of key-value pairs. Similar to useState
, these values can either be the exact value, or a function to be lazily evaluated when the hook is run. For example:
const [state, set] = useStateReducer({
name: 'John',
expensiveValue: () => {
const initialState = someExpensiveComputation();
return initialState;
},
});
The set
function contains a number of properties that update the respective values within the state. For example:
const [state, set] = useStateReducer({
age: 6,
maxAge: 8,
});
set.age(5); // Sets age to 5
set.age(age => age + 1); // Increases age by 1
set.age((age, state) => Math.min(age + 1, state.maxAge)); // Increases age by 1, capped by current state's maxAge value
import { useStateReducer } from '@upstatement/react-hooks';
const UserForm = ({ onSubmit }) => {
const [state, set] = useStateReducer({
name: '',
age: 0,
createdAt: () => new Date(),
});
return (
<form onSubmit={onSubmit}>
<input id="name" value={state.name} onChange={evt => set.name(evt.target.value)} />
<input id="age" type="number" value={state.age} onChange={evt => set.age(evt.target.value)} />
</form>
);
};
This hook allows for tracking of the value of a given variable on a previous render.
Learn more about the usePrevious
API and Usage
const previousValue = usePrevious(currentValue);
The initial previous value returned will be the same as the current value.
It's important to note that the previous value does not update when the given value changes, but rather on every render.
import { usePrevious } from '@upstatement/react-hooks';
const Direction = ({ scrollY }) => {
const previousScrollY = usePrevious(scrollY);
if (scrollY === previousScrollY) {
return null;
} else if (scrollY > previousScrollY) {
return <ArrowUp />;
}
return <ArrowDown />;
};
This hook allows for use of the ES6 Map as a state variable inside your React component.
Learn more about the useMap
API and Usage
const map = useMap(arrayOfTuples);
// Accepts the same initial value that Map's constructor does
All map methods can then be used as normal, including (but not limited to) map.set
, map.has
, and map.delete
.
import { useMap, useState } from '@upstatement/react-hooks';
const DictionarySearch = () => {
const dictionaryMap = useMap();
const [search, setSearch] = useState('');
const [term, setTerm] = useState('');
const [definition, setDefinition] = useState('');
const addDefinition = evt => {
evt.preventDefault();
dictionaryMap.add(term, definition);
setTerm('');
setDefinition('');
};
const onChange = setFunction => evt => {
setFunction(evt.target.value);
};
return (
<div>
<input id="search" value={search} onChange={onChange(setSearch)} />
{dictionaryMap.has(search) ? (
<p style={{ color: 'green' }}>{dictionaryMap.get(search)}</p>
) : (
<p style={{ color: 'red' }}>Term not found in dictionary.</p>
)}
<form onSubmit={addDefinition}>
<input id="term" value={term} onChange={onChange(setTerm)} />
<textarea id="definition" value={definition} onChange={onChange(setDefinition)}></textarea>
</form>
</div>
);
};
Similar to useMap
, this hook allows you to use an ES6 Set as a state variable inside your React component.
Learn more about the useSet
API and Usage
const set = useSet(arrayOfValues);
// Accepts the same initial value that Set's constructor does
All set methods can then be used as normal, including (but not limited to) set.add
, set.has
, and set.delete
.
import { useSet } from '@upstatement/react-hooks';
const Shop = ({ items }) => {
const cartSet = useSet();
const addToCart = index => {
const item = items[index];
if (item) {
cartSet.add(item.name);
}
};
return (
<div>
<h2>Items</h2>
<ul>
{items.map(({ name, price }, index) => (
<li key={name}>
<p>{name}</p>
<p>${price}</p>
<button disabled={cartSet.has(name)} onClick={() => addToCart(index)}>
Add to cart
</button>
</li>
))}
</ul>
</div>
);
};
Allows for tracking multiple refs in the React DOM. This is particularly useful when looping over items.
Learn more about the useMultiRef
API and Usage
const [refs, setRef] = useMultiRef();
import { useEffect } from 'react';
import { useMultiRef } from '@upstatement/react-hooks';
import { last } from 'lodash';
const Modal = ({ links }) => {
const [linkRefs, setLinkRef] = useMultiRef();
const lockModalFocus = evt => {
if (evt.keyCode === 9) {
// Pressed tab
const linkEls = linkRefs.current;
if (evt.shiftKey && document.activeElement === linkEls[0]) {
evt.preventDefault();
last(linkEls).focus();
} else if (!evt.shiftKey && document.activeElement === last(linkEls)) {
evt.preventDefault();
linkEls[0].focus();
}
}
};
useEffect(() => {
linkRefs.current[0].focus();
window.addEventListener('keydown', lockModalFocus);
return () => {
window.removeEventListener('keydown', lockModalFocus);
};
}, []);
return (
<ul>
{links.map(({ href, text }, index) => (
<li key={index} ref={setLinkRef(index)}>
<a href={href}>{text}</a>
</li>
))}
</ul>
);
};
This utility hook provides a way to force the a component to update. It's recommended to only be used when the DOM is dependent on a ref value.
Learn more about the useForceUpdate
API and Usage
const update = useForceUpdate();
Every call to the update
function will increase an internal tick. This in turn will force a re-render of the component.
This hook is actually used in our useSet
and useMap
hooks! A snippet of that code is found below:
import { useRef } from 'react';
import { useForceUpdate } from '@upstatement/react-hooks';
const useSet = iterable => {
const update = useForceUpdate();
const setRef = useRef(new Set(iterable));
const set = new Set(setRef.current);
set.add = value => {
const newSet = setRef.add(value); // Add to our set reference
update(); // force update to hook, recreating the `set` value
return newSet;
};
return set;
};
We welcome all contributions to our projects! Filing bugs, feature requests, code changes, docs changes, or anything else you'd like to contribute are all more than welcome! More information about contributing can be found in the contributing guidelines.
Upstatement strives to provide a welcoming, inclusive environment for all users. To hold ourselves accountable to that mission, we have a strictly-enforced code of conduct.
Upstatement is a digital transformation studio headquartered in Boston, MA that imagines and builds exceptional digital experiences. Make sure to check out our services, work, and open positions!