Home > Software design >  Pass optional fields of array to props
Pass optional fields of array to props

Time:10-08

Suppose I have a component that loops items in an array, in this example without specifying fields. No problem, it works as it should. But if I now use the component with an array of different fields, is there a way to tell the component to use those specific fields if needed?

<Component :items="items" />

items: [one, two, three]
<div v-for="item in items" :key="item.id">
  <p>{{ item }}</p>
</div>

Is something like following possible? Of course items.name isn't working, I would have to add item.name in the component itself. I would like to have a reusable component as much as possible. I want to use differently structured arrays in the component.

// Is something like this possible?
<Component :items="items.name" />

items: [{name: one}, {name: two}, {name: three},]

Based on tao's answer, I ended up declaring an itemType. In the component itself I could work with if-conditions and loop the exact data I want. But it's still not as reusable as I want it to be. I have to create a separate if condition for each possibility.

<Component :items="items" item-type="name" />

<!--  -->

<div v-if="!itemType">
  <div v-for="item in items" :key="item.id">
    <p>{{ item }}</p>
  </div>
</div>

<div v-if="itemType === 'name'">
  <div v-for="item in items" :key="item.id">
    <p>{{ item.name }}</p>
  </div>
</div>

Therefore the question. Would something like this be an option? If so, how? Maybe this is what tao's second approach is about, but unfortunately my current knowledge is not enough to fully understand this and creating interfaces.

<div v-if="itemType">
  <div v-for="item in items" :key="item.id">
    <p>{{ item.itemType }}</p>
  </div>
</div>

CodePudding user response:

Technically, you can't create a component with variable props. The whole point of declaring components is to encapsulate bits of app logic which share inputs, outputs and functionality.

When the inputs, the outputs or the functionality change substantially, you should consider developing a new component.

In your specific case, a good solution might be to create a top level component taking in :items and :itemType/:itemSchema. Based on the type/schema, it would know how to handle each item type. Of course, this logic of handling them could be furthermore declared into sub-components.


Another approach would be to create a unified Item interface (always an object), holding enough information about how the current item should be handled and maybe even what component should be used to render it:

interface Item {
  type: string;
  value: string | { name: string };
  component: StringItem | NamedItem;
  // ...any other props required to differentiate between items or their usage
}

The advantage of this approach is that it allows grouping items of various types into the same :items array.

Ultimately, you can implement any app logic you want/need. Look at the business requirements and decide on an implementation getting the currently desired result with the least amount of effort, while leaving room for possible upgrades to the app's logic.


Opinionated advice: you should create separate components at first, even if the goal is to combine them later into one smart enough to handle all cases.
You'll find it easier to develop the merged version if you have each case developed separately and working as expected.


Update, based on the addition to question:

The business requirement dictates the logic, not the data structure. Instead of asking yourself:

What do I want to show the user, given all possible cases?

(which, by the way, is considered bad for development and should be avoided at all costs. It has the tendency to kill projects. I'm not joking), it should be

What do I want to show the user, given all currently known cases?

Potential answer: - the title.

Ok, what's the title?

  • if item.value is a string, that's the title
  • if item.value is an object with name, that's the title
  • at some point in the future, item.value might be an object with .title, or .nom, but we don't know exactly, we haven't spoken with those guys

Then your <template> looks like this:

<div>
  <div v-for="(item, key) in items" :key="item?.id || key">
    <p>{{ getItemTitle(item) }}</p>
  </div>
</div>

What's getItemTitle?

In Options API, it would be a methods member. In Composition API, a const:

const getItemTitle = (item) => item.value?.name || item.value

Now let's say you'll want to add a third type of item to the mix and this one's value is an object with title, instead of name. Template remains the same. Function changes to:

const getItemTitle = (item) => item.value?.name || item.value?.title || item.value

Ok, now you have an item which has both .name and .title and it shows the .name. I want to see it's .title, even if it has .name. Fair enough:

const getItemTitle = (item) => item.value?.title || item.value?.name || item.value

Depending on how generic you want your component to be, your getItemTitle can become something like:

const getItemTitle = (item) => {
  switch (true) {
    case Boolean(item.value?.title): 
      return item.value.title;
    case Boolean(item.value?.name):
      return item.value.name;
    // ... a lot more cases

    case typeof item.value === 'string':
      return item.value
    default: 
      return '-- untitled item --'
  }
}

Bottom line: when you want to make things generic you start from the product requirements, not from the data.


Final note: if you need getItemTitle in more than one component, don't write it in each component.
Export it from a helper into every component needing it.

This way your components can handle any items type and you have a function in another file, holding all the necessary logic for extracting a title from every known item type.
If we invent another type of item tomorrow, we'll have to update that function and that function only.

CodePudding user response:

try something like that

<Component :items="items.map((item) => ({ name: item }))" />

map() method returns new array with the results of calling a provided function
in your case, it will return

[{"name":"one"},{"name":"two"},{"name":"three"}]

CodePudding user response:

Yes, you can have a component that maps [1,2] and [{n:1},{n:2}].

You could:

<div v-for="item in items" :key="item.id">
  <p>{{ item?.name ? item.name : item}}</p>
</div>

In other words, check if 1st item has property "n". The ?. symbol prevens it from having a "property name undefined" or similar error.

It's bad practice though, as this wouldn't be a functional component.

  • Related