Configuring Composables with the Options Object

We can make our composables more reusable by passing in an object that contains all of the configuration options for how we want the composable to behave:

const state = ref({ email: '' });
const { history, undo, redo } = useRefHistory(state, {
// Track history recursively
deep: true,
// Limit how many changes we save
capacity: 10,
});

We use an object here instead of a long list of parameters:

const { history, undo, redo } = useRefHistory(state, true, 10));

Using an options object instead of parameters gives us several benefits.

First, it’s self-documenting. We have the name of the parameter right beside the value, so we never forget what each value is doing.

We can also create a type for the entire options object:

export type RefHistoryOptions {
deep?: boolean;
capacity?: number;
};
export type RefHistoryReturn {
history: Ref;
undo: () => void;
redo: () => void;
};
export function useRefHistory(
ref: Ref,
options: RefHistoryOptions
): RefHistoryReturn {};

Second, we don’t need to worry about ordering or unused options. The more potential edge cases we cover with a composable, the more options we’ll have. But we usually only need to worry about a couple of them at one time — they’re all optional.

Third, it’s much easier to add new options. Because the order doesn’t matter and none of the options are required, adding a new capability to our composable won’t break anything. We simply add it to the list of possible options and carry on.

The pattern doesn’t require a lot of work to implement, either:

export function useRefHistory(ref, options) {
const {
deep = false,
capacity = Infinity,
} = options;
// ...
};

First, we pass in the options object as the last parameter. This makes it possible to have the options object itself as an optional parameter.

The required params come first. Typically, there will only be one or two. More parameters is a code smell, and likely means that your composable is trying to do too much.

The required parameter (or parameters) is very often a Ref, or a MaybeRef if we’re also implementing the Flexible Arguments Pattern.

We then access the options by destructuring.

Doing this gives us a really clean and readable way of providing defaults. Remember, these are options so they should all have defaults. If the values are required they should likely have

This helps to clarify what options are being used in this composable. It’s not uncommon for one composable to use another composable, and in that case some of the options are simply passed along to the inner composable:

export function useRefHistory(ref, options) {
const {
deep = false,
capacity = Infinity,
...otherOptions,
} = options;
// Pass along some options we're not using directly
useSomeOtherComposable(otherOptions);
};