sveltejs/svelte

Allow createContext get context to provide default value/work without parent context.

Opened this issue · 1 comments

Describe the problem

In most cases context existing is the expected behavior and throwing error is good by default. Coming from other frameworks where you do it manually this saves time, however there are cases where context is optional.

Imagine pattern like useScrollContainer child component might be wrapped in <ScrollContainer />, but it might not be. Context not returning anything or defaulting to window intended. Useful with virtualization libraries.

To achieve this in Svelte you would need to use setContext/getContext, but they are not type safe. Given that createContext is new type-safe alternative, this use case should be supported as well, so there is only one way to do things.

Describe the proposed solution

Allow setting default value.

const [getContext, setContext] = createContext<number>()

const count = getContext(-1) // this will return -1 when no context is found.

Importance

nice to have

Example that allows providing a default to createContext(default) or to getContext(default)

export function createContext<T>(createDefault?: () => T): [(getDefault?: () => T) => T, (context: T) => T] {
	const key = Symbol("context");
	return [
		(getDefault) => {
			if (!hasContext(key)) {
				if (getDefault) return setContext(key, getDefault());
				if (createDefault) return setContext(key, createDefault());
				throw new Error("Context not found");
			}
			return getContext(key);
		},
		(context) => setContext(key, context)
	];
}

Or if you don't like side effects:

export function createContext<T>(createDefault?: () => T): [(getDefault?: () => T) => T, (context: T) => T] {
	const key = Symbol("context");
	return [
		(getDefault) => {
			if (!hasContext(key)) {
				if (getDefault) return getDefault();
				if (createDefault) return createDefault();
				throw new Error("Context not found");
			}
			return getContext(key);
		},
		(context) => setContext(key, context)
	];
}