The useRef hook is a multi-purpose hook. Its primary purpose in Melody is to store and update values without causing a re-evaluation of the state hook. Thus, you might say it offers a non-reactive value storage. Coming from React, it has inherited the name useRef and the additional capability to reference DOM elements. However, as mentioned in Event Handling, Melody offers better ways to bind DOM elements and you should not rely on useRef for that purpose.
The term "non-reactive" refers to a value that, when changed, does not cause the component to be re-evaluated. Values produced by useState or useAtom will cause re-evaluation whenever they are modified through the mutator function.
Storing non-reactive values
// a small hook to contain interval logic which can be canceled
function usePulse({ interval, callback }) {
// create a ref
const timer = useRef(null);
// timer.current will contain our value over time and will always
// refer to the latest value
const cancel = useCallback(() => clearInterval(timer.current));
useEffect(() => {
// update the value with the new interval
timer.current = setInterval(callback, interval);
return cancel;
}, [interval, callback]);
// and return a function to cancel it
return cancel;
}
function usePulseButton() {
const [shouldPulseIn, setPulseIn, isPulseIn] = useAtom(true);
// on each interval tick, we'll flip the pulse value
// that'd then trigger a CSS class change to do an animation
const cancelPulse = usePulse(1000, useCallback(() => {
setPulseIn(!shouldPulseIn());
}));
const buttonRef = useCallback(element => {
return fromEvent(element, 'click').subscribe(event => {
// when the value is clicked we'll stop the animation
// using the callback
cancelPulse();
});
});
return {
buttonRef,
isPulseIn
};
}
Storing all referenced elements
function useReferencedElements() {
// create a non-reactive variable which will hold all of our elements
const elements = useRef([]);
// use a normal ref handler to collect all referenced elements
const refElement = useCallback(element => {
elements.current.push(element);
// and make sure we drop them from the list if anything changes
return () => removeElement(elements.current, element);
}, []);
// provide easy access to the refHandler and the array of elements
return [refElement, elements.current];
}
function useItemList({ data }) {
const [item, itemElements] = useReferencedElements();
// not a particularly good example but this would animate all referenced
// item elements and would replay the animation if any of them changed
useEffect(() => {
itemElements.forEach(element => element.animate(...));
}, itemElements);
return {
item,
data
};
}
// utility function to remove an element from an array
const removeElement = (array, element) => {
const index = array.indexOf(element);
if (index < 0) return;
array.splice(index, 1);
};
Easy access to just a single referenced element
function useForm({ submitForm }) {
// the return value of useRef can be assigned to an element
// using the ref attribute
// its just a special ref handler
const inputElement = useRef(null);
// Let's also discuss how to reference an element for reading while
// binding event handlers
const buttonElement = useRef(null);
// but for event handling we really want to leverage the
// ref handler directly
const submitButton = useCallback(element => {
// just store the current element
buttonElement.current = element;
return fromEvent(element, 'click').subscribe(() => {
submitForm({ text: inputElement.value });
}).add(
// Rx allows to add teardown logic to an existing subscription
() => buttonElement.current = null
);
}, [submitForm]);
return {
inputElement,
submitButton
};
}