Skip to content

Commit

Permalink
feat: add reqRef and ssrReqRef for refs to be reset per-request
Browse files Browse the repository at this point in the history
* ...and update docs more extensively to identify potential footguns
  • Loading branch information
danielroe committed Oct 16, 2020
1 parent 2da0d07 commit b6f327c
Show file tree
Hide file tree
Showing 23 changed files with 221 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: useFetch
category: Examples
link: https://codesandbox.io/s/github/nuxt-community/composition-api/tree/main/example?from-embed
fullscreen: True
position: 30
---

<code-sandbox :src="link"></code-sandbox>
65 changes: 65 additions & 0 deletions docs/content/en/getting-started/gotchas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: Gotchas
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Getting started
fullscreen: True
position: 3
version: 0.133
---

There are a couple of points that you should be aware of when using `@nuxtjs/composition-api`.

## **'Keyed' functions**

A number of helper functions use a key to pass JSON-encoded information from server to client (currently `shallowSsrRef`, `ssrPromise`, `ssrRef` and `useAsync`).

In order to make that possible, under the hood this library adds a key based on line number within your code.

If you want to use these functions within global composables, make sure to set a unique key based on each calling of the function - for example, you might key it to the route path. (Each function takes an optional second parameter that is this key.)

If you don't provide a unique key, this is the consequence:
```ts
function useMyFeature() {
// Only one unique key is generated, no matter
// how many times this function is called.
const feature = ssrRef('')

return feature
}

const a = useMyFeature()
const b = useMyFeature()

b.value = 'changed'
// On client-side, a's value will also be initialised to 'changed'
```

Here is how you might implement it by setting a key.
```ts
function useMyFeature(path: string) {
const content = useAsync(
() => fetch(`https://api.com/slug/${path}`).then(r => r.json()),
path,
)

return {
content
}
}

export default useMyFeature
```

## **Shared server state**

If you are declaring refs in the global state of your application - such as within plugins or in state/store files (for example, as a replacement for Vuex) - you should be aware that these refs are persisted across requests when your site is in production mode.

You should take especial care with declaring refs in this way.

<alert type="info">

If you do need to reinitialise a ref on each request, you can use [the `reqRef` helper](/helpers/reqRef).

</alert>


File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Definition helpers
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
position: 3
position: 10
---

There are some helpers to optimize developer experience when creating Nuxt plugins, middleware, server middleware and modules.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: onGlobalSetup
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
fullscreen: True
position: 11
---

This helper will run a callback function in the global setup function.
Expand Down
28 changes: 28 additions & 0 deletions docs/content/en/helpers/reqRef.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: reqRef, reqSsrRef
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
fullscreen: True
version: 0.133
position: 12
---

`reqRef` declares a normal `ref` with one key difference. It resets the value of this ref on each request. You can find out [more information here](/gotchas#shared-server-state).

<alert>You should take especial care because of the danger of shared state when using refs in this way.</alert>


## Example

```ts[~/state/sampleModule.js]
import { reqRef } from '@nuxtjs/composition-api'
export const user = reqRef(null)
export const fetchUser = async () => {
const r = await fetch('https://api.com/users)
user.value = await r.json()
}
```


Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: ssrPromise
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
fullscreen: True
position: 13
---

`ssrPromise` runs a promise on the server and serialises the result as a resolved promise for the client. It needs to be run within the `setup()` function but note that it returns a promise which will require special handling. (For example, you cannot just return a promise from setup and use it in the template.)
Expand Down Expand Up @@ -41,6 +42,6 @@ Under the hood, `ssrPromise` requires a key to ensure that the ref values match

<alert>

At the moment, an `ssrPromise` is only suitable for one-offs, unless you provide your own unique key. See [the `ssrRef` documentation](/ssrRef) for more information.
At the moment, an `ssrPromise` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions).

</alert>
</alert>
36 changes: 22 additions & 14 deletions docs/content/en/ssrRef.md → docs/content/en/helpers/ssrRef.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
---
title: ssrRef
title: ssrRef, shallowSsrRef
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
fullscreen: True
position: 14
---

When creating composition utility functions, often there will be server-side state that needs to be conveyed to the client.

## ssrRef

`ssrRef` will automatically add ref values to `window.__NUXT__` on SSR if they have been changed from their initial value. It can be used outside of components, such as in shared utility functions, and it supports passing a factory function that will generate the initial value of the ref.

```ts
Expand All @@ -31,24 +34,29 @@ Under the hood, `ssrRef` requires a key to ensure that the ref values match betw

<alert>

At the moment, an `ssrRef` is only suitable for one-offs, unless you provide your own unique key.
At the moment, an `ssrRef` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions).

</alert>

## shallowSsrRef

This is because server and client `ssrRef` matches up based on line number within your code.
This helper creates a [`shallowRef`](https://vue-composition-api-rfc.netlify.app/api.html#shallowref) (a ref that tracks its own .value mutation but doesn't make its value reactive) that is synced between client & server.

```ts
function useMyFeature() {
// Only one unique key is generated
const feature = ssrRef('')
return feature
}
import { shallowSsrRef, onMounted } from '@nuxtjs/composition-api'

const a = useMyFeature()
const b = useMyFeature()
const shallow = shallowSsrRef({ v: 'init' })
if (process.server) shallow.value = { v: 'changed' }

b.value = 'changed'
// On client-side, a's value will also be initialised to 'changed'
// On client-side, shallow.value will be { v: changed }
onMounted(() => {
// This and other changes outside of setup won't trigger component updates.
shallow.value.v = 'Hello World'
})
```

If you want to use this pattern, make sure to set a unique key based on each calling of the function.
<alert>

</alert>
At the moment, a `shallowSsrRef` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions).

</alert>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: useAsync
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
fullscreen: True
position: 15
---

You can create reactive values that depend on asynchronous calls with `useAsync`.
Expand All @@ -11,8 +12,6 @@ On the server, this helper will inline the result of the async call in your HTML

However, if the call hasn't been carried out on SSR (such as if you have navigated to the page after initial load), it returns a `null` ref that is filled with the result of the async call when it resolves.

<alert>Note that there are some gotchas when using `ssrRef` (which this helper does) - see [the ssrRef docs](/ssrRef) for more information.</alert>

```ts
import { defineComponent, useAsync, useContext } from '@nuxtjs/composition-api'

Expand All @@ -25,3 +24,9 @@ export default defineComponent({
},
})
```

<alert>

At the moment, `useAsync` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions).

</alert>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: useContext
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
fullscreen: True
position: 16
---

You can access the Nuxt context more easily using `useContext`, which will return the Nuxt context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: useFetch
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
fullscreen: True
position: 17
---

Versions of Nuxt newer than v2.12 support a [custom hook called `fetch`](https://nuxtjs.org/api/pages-fetch/) that allows server-side and client-side asynchronous data-fetching.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: useMeta
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
fullscreen: True
position: 18
---

You can interact directly with [head properties](https://nuxtjs.org/api/pages-head/) in `setup` by means of the `useMeta()` helper.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: useStatic
description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.'
category: Helpers
position: 19
---

You can pre-run expensive functions using `useStatic`.
Expand Down Expand Up @@ -34,7 +35,7 @@ If you are generating the whole app (or just prerendering some routes with `nuxt

<alert>

If you are pregenerating some pages in your app note that you may need to increase `generate.interval`. (See [setup instructions](/setup).)
If you are pregenerating some pages in your app note that you may need to increase `generate.interval`. (See [setup instructions](/getting-started/setup).)

</alert>

Expand Down
45 changes: 0 additions & 45 deletions docs/content/en/shallowSsrRef.md

This file was deleted.

1 change: 1 addition & 0 deletions src/babel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default function ssrRefPlugin({ loadOptions, getEnv, types: t }: Babel) {
case 'shallowSsrRef':
case 'ssrPromise':
case 'ssrRef':
case 'reqSsrRef':
case 'useAsync':
if (path.node.arguments.length > 1) return
break
Expand Down
1 change: 1 addition & 0 deletions src/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { useContext, withContext } from './context'
export { useFetch } from './fetch'
export { globalPlugin, onGlobalSetup } 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 * from './defineHelpers'
Expand Down
4 changes: 4 additions & 0 deletions src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Plugin } from '@nuxt/types'
import type { SetupContext } from '@vue/composition-api'

import { reqRefs } from './req-ref'
import { setSSRContext } from './ssr-ref'

type SetupFunction = (
Expand Down Expand Up @@ -35,6 +36,9 @@ export const onGlobalSetup = (fn: SetupFunction) => {
export const globalPlugin: Plugin = context => {
const { setup } = context.app
globalSetup = new Set<SetupFunction>()
if (process.server) {
reqRefs.forEach(reset => reset())
}
context.app.setup = function (...args) {
let result = {}
if (setup instanceof Function) {
Expand Down
27 changes: 27 additions & 0 deletions src/req-ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ref, Ref } from '@vue/composition-api'

import { sanitise, ssrRef } from './ssr-ref'

export const reqRefs = new Set<() => void>()

export const reqRef = <T>(initialValue: T): Ref<T> => {
const _ref = ref(initialValue)

if (process.server) reqRefs.add(() => (_ref.value = initialValue as any))

return _ref as Ref<T>
}

export const reqSsrRef = <T>(initialValue: T, key?: string) => {
const _ref = ssrRef(initialValue, key)

if (process.server)
reqRefs.add(() => {
_ref.value =
initialValue instanceof Function
? sanitise(initialValue())
: initialValue
})

return _ref
}
4 changes: 2 additions & 2 deletions src/ssr-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export function setSSRContext(ssrContext: any) {
}

const isProxyable = (val: unknown): val is Record<string, unknown> =>
val && typeof val === 'object'
!!val && typeof val === 'object'

const sanitise = (val: unknown) =>
export const sanitise = (val: unknown) =>
(val && JSON.parse(JSON.stringify(val))) || val

const ssrValue = <T>(value: T | (() => T), key: string): T => {
Expand Down
Loading

1 comment on commit b6f327c

@vercel
Copy link

@vercel vercel bot commented on b6f327c Oct 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.