Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: useId composable generate different id on client and server for SSR rendering #919

Closed
mykhailo-alekseiev opened this issue May 13, 2024 · 2 comments
Labels
bug Something isn't working

Comments

@mykhailo-alekseiev
Copy link

mykhailo-alekseiev commented May 13, 2024

Environment

Developement/Production OS: MacOs Sonoma 14.4.1 
Node version: 20.10.0
Package manager: bun@1.1.7
Radix Vue version: 1.7.4
Vue version: 3.4.27
Nuxt version: 3.11.2
Nuxt mode: universal
Nuxt target: server
CSS framework: tailwindcss (nuxt module)
Client OS: MacOs Sonoma 14.4.1
Browser: Arc Browser 124.0.6367.155

Steps to reproduce

  • initialize Nuxtjs project with Shadcn ui library
  • install form and input components from there
  • Use the example of the Form component

Describe the bug

Hi!
I caught the issue with hydration and radix. I implemented the fix with the ConfigProvider component and used composable but for input components, I bumped into a warning that

[Vue warn]: Hydration attribute mismatch on <input class=​"flex h-12 w-full rounded-[.875rem]​ border border-transparent bg-neutral-50 px-3.5 py-3 text-base text-primary-black ring-offset-background file:​border-0 file:​bg-transparent file:​text-sm file:​font-normal placeholder:​text-neutral-400 autofill:​!bg-neutral-50 focus-visible:​border-primary-black focus-visible:​outline-none disabled:​cursor-not-allowed" data-v-inspector=​"components/​ui/​input/​Input.vue:​24:​3" type=​"text" placeholder=​"username" name=​"username" id=​"radix-nE3KxD8SGyR-1-form-item" aria-describedby=​"radix-nE3KxD8SGyR-1-form-item-description" aria-invalid=​"false" value>​flex

  - rendered on server: id="radix-nE3KxD8SGyR-1-form-item"
  - expected on client: id="radix-nE3KxD8SGyR_1-form-item"
  Note: this mismatch is check-only. The DOM will not be rectified in production due to performance overhead.
  You should fix the source of the mismatch. 
  at <Input type="text" placeholder="username" name="username"  ... > 
  at <PrimitiveSlot id="radix-nE3KxD8SGyR_1-form-item" aria-describedby="radix-nE3KxD8SGyR_1-form-item-description" aria-invalid=false > 
  at <FormControl> 
  at <FormItem> 
  at <Field name="username" > 

Packages:

"dependencies": {
    "@nuxt/eslint": "0.3.12",
    "@nuxt/fonts": "0.7.0",
    "@nuxt/image": "1.7.0",
    "@nuxtjs/html-validator": "1.8.1",
    "@nuxtjs/i18n": "8.3.1",
    "@nuxtjs/tailwindcss": "6.12.0",
    "@vee-validate/zod": "4.12.8",
    "@vueuse/core": "^10.9.0",
    "class-variance-authority": "0.7.0",
    "clsx": "2.1.1",
    "lucide-vue-next": "0.378.0",
    "nuxt": "3.11.2",
    "nuxt-graphql-client": "0.2.34",
    "nuxt-svgo": "4.0.1",
    "nuxt-time": "0.1.3",
    "radix-vue": "1.7.4",
    "shadcn-nuxt": "0.10.4",
    "tailwind-merge": "2.3.0",
    "tailwindcss-animate": "1.0.7",
    "vee-validate": "4.12.8",
    "vue": "3.4.27",
    "vue-router": "4.3.2",
    "zod": "3.23.8"
  },
  "devDependencies": {
    "@vueuse/nuxt": "^10.9.0",
    "locize-cli": "^8.0.1",
    "typescript": "5.4.5",
    "vue-tsc": "2.0.17"
  }

Expected behavior

Client and server IDs have to be the same.

Context & Screenshots (if applicable)

Screenshot 2024-05-13 at 23 59 29
@mykhailo-alekseiev mykhailo-alekseiev added the bug Something isn't working label May 13, 2024
@mykhailo-alekseiev
Copy link
Author

Code samples:

app.vue:

<template>
  <ConfigProvider :use-id="useIdFunction">
    <NuxtLayout>
      <NuxtPage />
    </NuxtLayout>
  </ConfigProvider>
</template>

<script lang="ts" setup>
import { ConfigProvider } from 'radix-vue'

const route = useRoute()
const useIdFunction = () => useId()
const appConfig = useAppConfig()
const { t } = useI18n()

useHead({
  title: () => t(route.meta.title as string) || '',
  titleTemplate: title => (title ? `${title} - ${appConfig.title}` : appConfig.title),
  bodyAttrs: { class: 'font-inter' },
})
</script>

input.vue

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { useVModel } from '@vueuse/core'
import { cn } from '~/lib/utils'

const props = defineProps<{
  defaultValue?: string | number
  modelValue?: string | number
  class?: HTMLAttributes['class']
  isError?: boolean
}>()

const emits = defineEmits<{
  (e: 'update:modelValue', payload: string | number): void
}>()

const modelValue = useVModel(props, 'modelValue', emits, {
  passive: true,
  defaultValue: props.defaultValue,
})
</script>

<template>
  <input
    v-model="modelValue"
    :class="cn('flex h-12 w-full rounded-[.875rem] border border-transparent bg-neutral-50 px-3.5 py-3 text-base text-primary-black ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-normal placeholder:text-neutral-400 autofill:!bg-neutral-50 focus-visible:border-primary-black focus-visible:outline-none disabled:cursor-not-allowed',
               props.class,
               isError && 'border-system-red focus-visible:border-system-red')"
  >
</template>

Form.vue

<template>
  <form @submit="onSubmit">
    <div
      class="grid w-full gap-1"
    >
      <FormField
        v-slot="{ componentField, errorMessage }"
        name="username"
      >
        <FormItem>
          <FormLabel>Username</FormLabel>
          <FormControl>
            <Input
              type="text"
              placeholder="username"
              v-bind="componentField"
              :disabled="form.isSubmitting.value"
              :is-error="!!errorMessage"
            />
          </FormControl>
          <FormMessage />
        </FormItem>
      </FormField>
      <FormField
        v-slot="{ componentField, errorMessage }"
        name="password"
      >
        <FormItem>
          <FormLabel>Password</FormLabel>
          <FormControl>
            <Input
              type="password"
              placeholder="password"
              v-bind="componentField"
              :disabled="form.isSubmitting.value"
              :is-error="!!errorMessage"
            />
          </FormControl>
          <FormMessage />
        </FormItem>
      </FormField>
    </div>
    <button />
    <Button
      type="submit"
      full-width
      :loading="form.isSubmitting.value"
    >
      Continue
    </Button>
  </form>
</template>

<script lang="ts" setup>
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import * as z from 'zod'

const formSchema = toTypedSchema(z.object({
  username: z.string().min(1, {
    message: 'required',
  }),
  password: z.string().min(1, {
    message: 'required',
  }),
}))

const form = useForm({
  validationSchema: formSchema,
  initialValues: {
    username: '',
    password: '',
  },
})

const onSubmit = form.handleSubmit(async ({ password, username }) => {
  try {
    const { password_login } = await GqlPasswordLogin({
      password,
      username,
    })
    if (password_login) {
      alert('Logged in!')
    }
    // await router.push('/dashboard')
  }
  catch (error) {
    console.log(error)
  }
})
</script>

<style>

</style>

@insuber
Copy link

insuber commented Jun 26, 2024

@mykhailo-alekseiev Could I ask how you resolved this issue? I've encountered the same problem where radix-vue generates IDs with a difference between an underscore '_' and a hyphen '-' on the client and server sides.
It occurs with the MenubarTrigger component in radix-vue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants