We intend to ship a standardised version of this Hook in a later Melody release. For now, you'll need to copy it over to your own code.
Accessing state, mutating state
In most real world scenarios you'll not want to constantly re-define all of your functions and event handlers. The performance impact would be too high. However, useState lacks an important feature that would help you to avoid all of complexity: an accessor function.
Thus, it is strongly advisable to rely on useAtom instead. It still offers a similar level of convenience and hides the complexity very nicely but it allows you to avoid the most common mistakes when it comes to state handling in Melody.
The useAtom hook returns an accessorFunction, which returns the current value at any given time, a mutator function which allows to modify the value and the current value stored in it.
Example
Let's build the prime example of a counter that has to be in a selected state in order for the user to be able to increase or decrease it again.
// this is a utility function to work with plain event handlers
// and Melody refs
const createEventHandler = (eventName, callback) => el => {
el.addEventListener(eventName, callback, false);
return {
unsubscribe() {
el.removeEventListener(eventName, callback, false);
}
};
};
// A hook for handling a selectable state
function useSelectable(props) {
// note that we rely on useAtom instead of useState here
const [isSelected, setSelected] = useAtom(props.isSelected);
return {
isSelected,
// with a state atom we don't have to use the updater function
// but it is still a good habit to get into
toggle: () => setSelected(selected => !selected)
// alternative (works just as well):
// toggle: () => setSelected(!getSelected())
};
}
function useSelectableCounter(props) {
const { count, increaseCounter, decreaseCounter } = useCounterState(props);
// and here we're using it!
const { isSelected, toggle } = useSelectable(props);
// These callbacks could not have been cached for long if we didn't rely
// on useAtom here since they'd have to be coupled
// to a boolean isSelected value instead.
// However, because we useAtom, we can reuse the very same callback for
// the entire lifetime of the component and will never have to
// rebind anything - which also simplifies the event binding a lot
const increaseCounterHandler = useCallback(() => {
if (isSelected()) {
increaseCounter();
}
}, []);
const decreaseCounterHandler = useCallback(() => {
if (isSelected()) {
decreaseCounter();
}
}, []);
// we use the helper function to create a Melody ref handler
// (a function which accepts an element and returns a subscription object)
const increaseCounterRef = createEventHandler(
'click',
increaseCounterHandler
);
const decreaseCounterRef = createEventHandler(
'click',
decreaseCounterHandler
);
return {
count,
isSelected: isSelected(),
toggle,
increaseCounterRef,
decreaseCounterRef
};
}
Without the useAtom hook, we'd have to constantly re-define our event handlers, adding a lot of noise around adding and removing event handlers. Thus, it is strongly recommended to prefer useAtom over useState.
Implementation
function useAtom(initialState) {
// create an state hook for storing the current value
const [value, setValue] = useState(initialState);
// useRef is a generic tool for storing mutable state that does not
// cause an update to happen upon change
const s = useRef(null);
// the accessor function can be memoized and will always return the
// the value from the ref
const getValue = useCallback(() => s.current);
// link the value stored within the ref to the current value in
// the state hook to ensure its always the latest
s.current = value;
// return the tuple
return [getValue, setValue, value];
}
As a rule of thumb, you should use the accessor function in anything related to event handling while the current value is most useful when you intend to pass data to the template or you wish to ensure a is being run whenever the value changes.
The above example is still not very pretty and is not how we recommend to deal with event handling. If you're interested in what that can look like, please take a look at .