Dynamic Dropdown

@aaron-ford
@aaron-ford Adocasts Plus

I have a page with two dropdowns. I want the second to populate depending on the option selected in the first. Using the PlotMyCourse code as an example, lets say you have a dropdown to select your organization, and when you select it, the second dropdown populates with the courses available within that organization. How would that be done? I've tried something like this:

const props = defineProps<{ 
    organizations: OrganizationDto[]
    courseMap: Record<number, { id: number; name: string }[]>
    orgId: number
    courseId: number
}>()

const form = useForm({
    organizationId: props.orgId.toString(),
    courseId: props.courseId.toString(),
})

const courses = computed(() => {
  return props.courseMap.[Number(form.organizationId)] || []
})

// Reset course if org changes and doesn't contain current course
watchEffect(() => {
  const courseIds = courses.value.map(c => c.id)
  if (form.courseId && !courseIds.includes(Number(form.courseId))) {
    form.courseId= "0"
  }
})
....
Copied!
<FormInput type="select" label="Organization" v-model="form.organizationId" :error="form.errors.organizationId" required>
            <SelectItem :key=0 value="0">
                None
            </SelectItem>
            <SelectItem v-for="org in props.organizations" :key="org.id" :value="org.id.toString()">
                {{ org.name}}
            </SelectItem>
        </FormInput>
        <FormInput v-if="courses.length" type="select" label="Course" v-model="form.courseId" :error="form.errors.courseId" required>
            <SelectItem :key=0 value="0">None</SelectItem>
            <SelectItem v-for="c in courses" :key="wh.id" :value="c.id.toString()">
                {{ c.name}}
            </SelectItem>
        </FormInput>
Copied!

I feel like I'm binding wrong. I put logs in the computed and watchEffect and they log on page load, but I can never get them to log when changing the selected organization.

Create a free account to join in on the discussion
  1. @tomgobich

    Hey Aaron! Great question, the watchEffect hook will only watch reactive properties utilized directly within the callback. I think that's the reason it isn't running for you when your form.organizationId is updated, as that isn't used within the watchEffect callback so it isn't being watched by it. In this case, I'd reach for watch instead of watchEffect

    const form = useForm({
        organizationId: props.orgId.toString(),
        courseId: props.courseId.toString(),
    })
    
    const courses = computed(() => {
      return props.courseMap.[Number(form.organizationId)] || []
    })
    
    // Reset course if org changes and doesn't contain current course
    watch(() => form.organizationId, (_newOrganizationId) => {
      const courseIds = courses.value.map(c => c.id)
      if (form.courseId && !courseIds.includes(Number(form.courseId))) {
        form.courseId= "0"
      }
    })
    Copied!

    Alternatively, you can bind to the update event on the input if you prefer that.

    <template>
      <FormInput 
        type="select" 
        label="Organization" 
        v-model="form.organizationId" 
        :error="form.errors.organizationId" 
        required
        @update:model-value="form.courseId = '0'">
        <SelectItem :key=0 value="0">
          None
        </SelectItem>
        <SelectItem v-for="org in props.organizations" :key="org.id" :value="org.id.toString()">
          {{ org.name}}
        </SelectItem>
      </FormInput>
    </template>
    Copied!
    0
New Discussion
Topic