Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions docs/pages/en/6.API/3.callWithContext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: callWithContext
description: 'Ensures Context will always be available inside called Composable'
---

This function equals one of Nuxt Bridge/Nuxt 3 internals: `callWithNuxt`.
It accepts `useContext()` response as first argument, function-to-call as second and function's arguments as third.
When you call this function, you can always be sure that function-to-call will have access to `useContext`, `useRoute`, e.t.c. methods.

Example of usage with useAsync:

```ts
import {
defineComponent,
useContext,
callWithContext,
} from '@nuxtjs/composition-api'
import { useMyComposable, useSecondComposable } from '../composables'

export default defineComponent({
setup() {
const context = useContext()

useAsync(async () => {
try {
//Context is lost after you call first await on SSR
const firstAwait = await useMyComposable({ option: true })
//This one depends on firstAwait and calls useContext inside of it
const secondAwait = await callWithContext(
context,
useSecondComposable,
[{ option: true }]
)

return {
firstAwait,
secondAwait,
}
} catch (e) {
//Wait for logging system response etc
await callWithContext(context, useProcessError, [{ error: e }])
throw e
}
})
},
})
```

:::alert{type="info"}
Note: after first call of useContext on Client Side context will be stored as global. You will not have to call function to access returned in setup composables on client side.
:::
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
"fs-extra": "^9.1.0",
"magic-string": "^0.27.0",
"pathe": "^1.0.0",
"ufo": "^1.0.1"
"ufo": "^1.0.1",
"unctx": "^2.1.2"
},
"devDependencies": {
"@babel/traverse": "^7.20.5",
Expand Down Expand Up @@ -102,12 +103,14 @@
"tsd": "^0.25.0",
"typescript": "4.9.4",
"vitest": "^0.25.7",
"vue-router": "^3.6.5",
"yorkie": "^2.0.0"
},
"peerDependencies": {
"@nuxt/vue-app": "^2.15",
"nuxt": "^2.15",
"vue": "^2.7.14"
"vue": "^2.7.14",
"vue-router": "^3.6"
},
"engines": {
"node": ">=v14.13.0"
Expand Down
78 changes: 58 additions & 20 deletions src/runtime/composables/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { computed } from 'vue'
import type { Ref } from 'vue'
import type { Context } from '@nuxt/types'
import type { Route } from 'vue-router'
import { useRoute } from 'vue-router/composables'

import { getContext } from 'unctx'
import { globalNuxt } from '@nuxtjs/composition-api/dist/runtime/globals'
import { getCurrentInstance } from './utils'
import { Vue } from 'vue/types/vue'

interface ContextCallback {
(context: Context): void
Expand All @@ -30,6 +33,25 @@ interface UseContextReturn
params: Ref<Route['params']>
}

const nuxtCtx = getContext<UseContextReturn>('nuxt-context')

/**
* Ensures that the setup function passed in has access to the Nuxt instance via `useContext`.
*
* @param context useContext response
* @param setup The function to call
* @param args Function's arguments
*/
export function callWithContext<T extends (...args: any[]) => any>(
context: UseContextReturn,
setup: T,
args?: Parameters<T>
) {
const fn: () => ReturnType<T> = () =>
args ? setup(...(args as Parameters<T>)) : setup()
return nuxtCtx.callAsync(context, fn)
}

/**
* `useContext` will return the Nuxt context.
* @example
Expand All @@ -45,26 +67,42 @@ interface UseContextReturn
```
*/
export const useContext = (): UseContextReturn => {
const vm = getCurrentInstance()
if (!vm) throw new Error('This must be called within a setup function.')
const nuxtContext = nuxtCtx.tryUse()

if (!nuxtContext) {
const vm = getCurrentInstance()
if (!vm) {
throw new Error('This must be called within a setup function.')
}

const root = vm.$root as unknown as { _$route: typeof vm.$root['$route'] }

return {
...(vm[globalNuxt] || vm.$options).context,
/**
* @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `route` from `useContext` but rather to use the `useRoute` helper function.
*/
route: computed(() => vm.$route),
/**
* @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `query` from `useContext` but rather to use the `useRoute` helper function.
*/
query: computed(() => vm.$route.query),
/**
* @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `from` from `useContext` but rather to use the `useRoute` helper function.
*/
from: computed(() => (vm[globalNuxt] || vm.$options).context.from),
/**
* @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `params` from `useContext` but rather to use the `useRoute` helper function.
*/
params: computed(() => vm.$route.params),
// Call of vue-router initialization of _$route
if (!root._$route) useRoute()

const context = {
...(vm[globalNuxt] || vm.$options).context,
/**
* @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `route` from `useContext` but rather to use the `useRoute` helper function.
*/
route: computed(() => root._$route),
/**
* @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `query` from `useContext` but rather to use the `useRoute` helper function.
*/
query: computed(() => root._$route.query),
/**
* @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `from` from `useContext` but rather to use the `useRoute` helper function.
*/
from: computed(() => (vm[globalNuxt] || vm.$options).context.from),
/**
* @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `params` from `useContext` but rather to use the `useRoute` helper function.
*/
params: computed(() => root._$route.params),
}

if (process.client) nuxtCtx.set(context)
return context
}

return nuxtContext
}
11 changes: 9 additions & 2 deletions src/runtime/composables/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
export { useAsync } from './async'
export { defineComponent } from './component'
export { useContext, withContext } from './context'
export { callWithContext, useContext, withContext } from './context'
export * from './defineHelpers'
export { useFetch } from './fetch'
export { globalPlugin, onGlobalSetup, setMetaPlugin } from './hooks'
export { useMeta } from './meta'
export { reqRef, reqSsrRef } from './req-ref'
export { ssrRef, shallowSsrRef, setSSRContext, ssrPromise } from './ssr-ref'
export { useStatic } from './static'
export { useRoute, useRouter, useStore, wrapProperty } from './wrappers'
export {
useRoute,
useRouter,
useStore,
wrapProperty,
wrapContextProperty,
useRedirect,
} from './wrappers'

export * from './vue'
58 changes: 51 additions & 7 deletions src/runtime/composables/wrappers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { computed, ComputedRef, InjectionKey } from 'vue'
import { computed, ComputedRef, InjectionKey, isRef } from 'vue'
import type { Store } from 'vuex'
import { useContext } from './context'

import { getCurrentInstance } from './utils'
import { Context } from '@nuxt/types'
import { useRouter as useVueRouter } from 'vue-router/composables'

/**
* You might want to create a custom helper to 'convert' a non-Composition API property to a Composition-ready one. `wrapProperty` enables you to do that easily, returning either a computed or a bare property as required.
Expand All @@ -27,6 +30,27 @@ export const wrapProperty = <
}
}

/**
* You might want to create a custom helper to 'convert' a non-Composition Context property to a Composition-ready one. `wrapProperty` enables you to do that easily, returning either a computed or a bare property as required.
* @param property the name of the property you would like to access. For example, `store` to access `context.store`.
* @param makeComputed a boolean indicating whether the helper function should return a computed property or not. Defaults to `true`.
*/
export const wrapContextProperty = <
K extends keyof Context,
T extends boolean = true
>(
property: K,
makeComputed?: T
) => {
return (): T extends true ? ComputedRef<Context[K]> : Context[K] => {
const context = useContext()

return makeComputed !== false && !isRef(context[property])
? (computed(() => context[property]) as any)
: context[property]
}
}

/**
* Gain access to the router just like using this.$router in a non-Composition API manner.
* @example
Expand All @@ -41,7 +65,30 @@ export const wrapProperty = <
})
```
*/
export const useRouter = wrapProperty('$router', false)
export const useRouter = (): VueRouter => {
const vm = getCurrentInstance()
if (vm) return useVueRouter()

const contextRouter = useContext().app.router
if (contextRouter) return contextRouter

throw new Error('This must be called within a setup function.')
}

/**
* Gain safe access to the redirect method from Context
* @example
```ts
import { defineComponent, useRedirect } from '@nuxtjs/composition-api'

export default defineComponent({
setup() {
useRedirect('/')
}
})
```
*/
export const useRedirect = wrapContextProperty('redirect')

/**
* Returns `this.$route`, wrapped in a computed - so accessible from `.value`.
Expand All @@ -57,7 +104,7 @@ export const useRouter = wrapProperty('$router', false)
})
```
*/
export const useRoute = wrapProperty('$route')
export const useRoute = wrapContextProperty('route')

/**
* Gain access to the store just like using this.$store in a non-Composition API manner. You can also provide an injection key or custom type to get back a semi-typed store:
Expand All @@ -82,8 +129,5 @@ export const useRoute = wrapProperty('$route')
```
*/
export const useStore = <S>(key?: InjectionKey<S>): Store<S> => {
const vm = getCurrentInstance()
if (!vm) throw new Error('This must be called within a setup function.')

return vm.$store
return useContext().store
}
Loading