Created
June 29, 2021 11:04
-
-
Save bodograumann/522b88d6d62c6935efacb2eee83b81c8 to your computer and use it in GitHub Desktop.
Vue 3 composable: Internal model state
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ref, toRaw, nextTick } from "vue"; | |
import type { Ref } from "vue"; | |
import useModelState from "../useModelState"; | |
describe("useModelState", () => { | |
const initialValue = [0]; | |
const updatedValue = [1]; | |
const validator = (value?: Array<unknown>) => | |
value !== undefined && value.length === 1; | |
const invalidValue = [0, 1]; | |
expect(updatedValue).not.toBe(initialValue); | |
let emit: () => void; | |
let source: Ref<Array<unknown>>; | |
beforeEach(() => { | |
source = ref(initialValue); | |
emit = jest.fn(); | |
}); | |
it("accepts an initial value", () => { | |
const { model } = useModelState(emit, initialValue); | |
expect(toRaw(model.value)).toBe(initialValue); | |
}); | |
it("accepts an initial ref", () => { | |
const { model } = useModelState(emit, source); | |
expect(toRaw(model.value)).toBe(initialValue); | |
expect(model).not.toBe(source); | |
}); | |
it("tracks changes to the inital ref", async () => { | |
const { model } = useModelState(emit, source); | |
source.value = updatedValue; | |
await nextTick(); | |
expect(toRaw(model.value)).toBe(updatedValue); | |
}); | |
it("emits new values", () => { | |
const { model } = useModelState(emit); | |
model.value = updatedValue; | |
expect(emit).toHaveBeenCalledTimes(1); | |
expect(emit).toHaveBeenCalledWith("update:modelValue", updatedValue); | |
}); | |
it("emits valid new values", () => { | |
const { model } = useModelState(emit, undefined, validator); | |
model.value = updatedValue; | |
expect(emit).toHaveBeenCalledTimes(1); | |
expect(emit).toHaveBeenCalledWith("update:modelValue", updatedValue); | |
}); | |
it("does not emit invalid new values", () => { | |
const { model } = useModelState(emit, undefined, validator); | |
model.value = invalidValue; | |
expect(toRaw(model.value)).toBe(invalidValue); | |
expect(emit).toHaveBeenCalledTimes(0); | |
}); | |
it("can reset to the source value", async () => { | |
const { model, reset } = useModelState(emit, source, validator); | |
source.value = updatedValue; | |
await nextTick(); | |
model.value = invalidValue; | |
expect(toRaw(model.value)).toBe(invalidValue); | |
reset(); | |
expect(toRaw(model.value)).toBe(updatedValue); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ref, computed, watch, isRef, unref } from "vue"; | |
import type { Ref } from "vue"; | |
/** | |
* Keep an internal copy of the model state | |
* | |
* Note: You still need to register the “update:modelValue” event for your | |
* component, like all other events in Vue 3. | |
* | |
* This allows a component to work, without an external model being connected. | |
* When a validator is defined, only valid values are emitted. To restore the | |
* internal model value to the outside state, even when the outside state | |
* didn’t change, call `reset`. | |
*/ | |
export default function useModelState<T>( | |
emit: (eventName: "update:modelValue", value?: T) => unknown, | |
source?: T | Ref<T>, | |
validator?: (value: T | undefined) => boolean | |
) { | |
const internalValue = ref() as Ref<T | undefined>; | |
function reset() { | |
internalValue.value = unref(source) as T | undefined; | |
} | |
if (isRef(source)) { | |
watch(source, reset, { immediate: true }); | |
} else { | |
internalValue.value = source; | |
} | |
return { | |
model: computed({ | |
get: () => internalValue.value, | |
set: (value?: T) => { | |
internalValue.value = value; | |
if (!validator || validator(value)) { | |
emit("update:modelValue", value); | |
} | |
}, | |
}), | |
reset, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment