Vue - Bind data conditionally according to property - javascript

I have a component that contains a child component which have some property that we can bind to. like:
<template>
<div>
<Child :bar="foo"></Child>
</div>
</template>
<script>
export default {
name: 'Parent',
props: ['foo'],
}
</script>
Now I want to bind the property 'bar' of the Child component conditionally. For example, I want to bind the property 'bar' of the child component only when the 'foo' property from the parent component is not undefined, how can I do that?
Update for the guys that advise me to use v-if directive:
Using v-if directive ISN'T an ideal solution, I think. When the conditions get complicated and the number of conditional binding properties get larger (like 5), the cases will grow exponentially and lead to a VERY LONG if-elseif-else chain. It's ugly, so NO.
I'm wondering if there is a REACT-LIKE way to solve this problem - just working with vdom and do binding pragmatically. but I don't know if I can do this in the render function, because I don't discover any API function that can do the same thing as v-bind directive on a child component.

You can use v-if:
<div v-if="foo">
<Child :bar="foo"></Child>
</div>
<div v-else>
<Child></Child>
</div>
However, I would recommend you not to render conditionally. It's because you can just check the bar props is not undefined.
Update:
I have found v-bind with object is useful in your case:
<Child v-bind="{bar: foo || '' }"></Child>
Also, you may try like this - ES6 feature: (But I'm not sure if this works)
<Child v-bind="...(foo && {bar: foo})"></Child>

Related

Vue 2 Updating Properties on Object in Array from Nested Component

I have been working on a Vue 2 project for a while, and upon upgrading our linting requirements I discovered that we had prop mutation errors in many of our child components. In our project, we pass a singleton object as a prop to many components and were originally updating the object directly from the child components. Vue seems to suggest using the v-bind.sync feature for updating props from child components (or using the equivalent v-bind and v-on). This, however, doesn't solve the issue of prop modification from nested components in an array.
Take this (pseudo)code for example that uses prop mutation:
Note: Assume const sharedObject: { arrayElements: Array<{ isSelected: boolean }> } = ...
Page.vue
<template>
...
<Component1 :input1="sharedObject" />
...
</template>
Component1.vue
<template>
...
<template v-for="elem in sharedObject.arrayElements">
<Component2 :input2="elem" />
</template>
...
</template>
Component2.vue
<template>
...
<q-btn #click="input2.isSelected = !input2.isSelected"></q-btn>
...
</template>
What is the proper way of updating a property like input2.isSelected from nested components in Vue 2? All of the approaches I've thought of are flawed.
Flawed Approaches
I believe that we would like to bubble up that input2.isSelected has been modified in Component2 to Page.vue, however, this seems to either lead to messy code or an uneasy feeling that we are just suppressing linting errors in a roundabout way.
To demonstrate the "messy code" approach, first note that Page.vue does not know the index of of elem in sharedObject.arrayElements. Therefore, we would need to emit an object to Page.vue from Component1 which contains the state of input2.isSelected as well of the index of elem in sharedObject.arrayElements. This gets messy quickly. What about the example where we have:
Component1.vue
<template>
...
<template v-for="elem in sharedObject.arrayElements">
<template v-for="elem2 in elem.arrayElements">
<Component2 :input2="elem2" />
</template>
</template>
...
</template>
in this case, then we could need to pass up 2 indices! It doesn't seem like a sustainable solution to me.
The alternative that I thought of is a callback function (passed as a prop through the component hierarchy) that takes as input the element we want to update and an object that contains the properties we want to update (using Object.assign).
This makes me very uneasy since I don't know the real reason why we can't update a pass-by-reference prop from a child component. To me, it seems like it's just a roundabout way of updating the passed-in from Component2 without the linter noticing. If there is some magic modification that happens to props when they're passed to child components, then surely passing in the object that I received in Component2 to the callback function and modifying it in the parent component would basically just be updating the prop in the child component, but more complicated.
What is the proper way of approaching this problem in Vue 2?
Very good question and analysis of the current state of this long-standing issue in Vue ecosystem.
Yes, modifying "value type" props from the child is a problem as it creates runtime issues (parent overwriting the changes when re-rendered) and thus Vue generates a runtime error when this happens...
Modifying a property of object passed as prop is OK from the "code works fine" POV. Unfortunately there are some influential people in the community with opinion (and many who blindly follow them) that this is an anti-pattern. I do not agree with that and raised my arguments many times (for example here). You described the reasons very well - it just creates unnecessary complexity/boilerplate code...
So what you are dealing with is really just a linting rule (vue/no-mutating-props). There is an ongoing issue/discussion that proposes the configuration option that should allow to ease the strictness of the rule with many good arguments but it gets very little attention from the maintainers (feel free to raise your voice there too)
For now what you can do is:
Disable the rule (far from perfect but luckily thanks to a Vue runtime error you can catch the real incorrect cases during development just fine)
Accept the reality and use workarounds
Workaround - use global state (store like Vuex or Pinia)
Note: Pinia is preferred as next version of Vuex will have same API
General idea is to place the sharedObject in the store and use props only to navigate the child components to the right object - in your case the Component2 will receive an index by prop and retrieve the right element from the store using it.
Stores are great for sharing the global state but using is just to overcome the linting rule is bad. Also as a result, components are coupled to the store hence both reusability suffers and testing is harder
Workaround - events
Yes it is possible to create mess and lot of boilerplate code using only events (especially if you nest components more than 2 levels) but there are ways to make things cleaner.
For example in your case Component2 does not need to know the index as you can handle the event like this
// Component1
<template>
...
<template v-for="elem in sharedObject.arrayElements">
<template v-for="(elem2, index) in elem.arrayElements">
<Component2 :input2="elem2" #update="updateElement($event, index)" />
</template>
</template>
...
</template>
In your case, the Component2 handles only change of single boolean property so $event can be simple boolean. If there are more than one property to be changed inside Component2, $event can be an object and you can use object spread syntax to "simplify" the Component2 (using one event instead of multiple - one for each property)
// Component2
<template>
...
<input v-model="someText" type="text">
<q-btn #click="updateInput('isSelected', !input2.isSelected)"></q-btn>
...
</template>
<script>
export default {
props: ['input2'],
computed: {
someText: {
get() { return this.input2.someText },
set(newVal) { updateInput('someText', newVal) }
}
},
methods: {
updateInput(propName, newValue) {
const updated = { ...this.input2 } // make a copy of input2 object
updated[propName] = newValue // change selected property
this.$emit('update', updated) // send updated object to parent
}
}
}
</script>
Well...I prefer just to disable the rule and set some clear naming conventions to indicate that the component is responsible for changing it's input...
Note that there are other workarounds like using this.$parent, inject\provide or event bus but those are really bad

what can't be passed by vue slot props?

In official react docs, It says the difference between 'other libraries's slot' and 'react's props.children' is as follows :
This approach may remind you of “slots” in other libraries but there are no limitations on what you can pass as props in React.
https://reactjs.org/docs/composition-vs-inheritance.html
And it sounds like there are some things can't pass through a 'vue slot'. Is there any thing can't be passed by vue slot's props?
In vue you have props as well. The difference and what may have been confusing you is that:
(vue) props === (react) props
(vue) slots === (react) props.children
You can pass data through props in both frameworks/libraries, but what you place inside <YourComponent>[content]</YourComponent will in vue terms be a slot and in react terms be accessible through props.children.
Let's say we have a popup/modal component which sole purpose is to act as a frame for the actual popup/modal content:
// parent component
<Modal>
<p>Watch out! Do you want to continue</p>
<button>Yes</button>
<button>No</button>
</Modal>
Then you would have the modalcomponent itself
// react
<div>
// this will output whatever you put inside the
// <Modal> tags in you parent component
{props.childen}
</div>
// vue
<div>
// the <slot> tag works the same way
// as React's {props.children}
<slot></slot>
</div>
Read more about vue slots here and vue props here.
When you're more familiar with the concepts you can read about vue's named slots here

How can I ensure that React creates a new instance of a component, even if the component tree has not changed?

React updates its component tree based on the names of the elements in that tree.
For example:
<div>
<MyComponent/>
</div>
And:
<div>
<MyComponent enabled/>
</div>
...results in React using the same <MyComponent> instance (because the component name did not change). This is very useful because it ensures that internal state within the component instance persists.
See the documentation for more details.
My question: "Is there a way to force a new instance to be created in certain circumstances?"
So rather than using the same MyComponent instance, I would like a new one to be instantiated if (let's say) prop 'x' has changed.
Untested, but I think you could trick React and add the prop as a key on the component.
<div>
<MyComponent enabled key={enabled.toString()} />
</div>
Since you're familiar with the reconciliation docs, I imagine you know what keys do already ;)

In Vue, can I transfer props?

Imagine I have a <progress-bar> UI component and I want to create an <app-progress-bar> component that is the same as the <progress-bar> but with some style tweaks.
AppProgressBar.vue:
<template>
<progress-bar class="app-progress-bar"></progress-bar>
</template>
This will break because <progress-bar> requires a percent prop. So I can pass it in:
<progress-bar class="app-progress-bar" percent="percent"></progress-bar>
This seems fine, but is brittle when <progress-bar> has many props. Can I simply pass through all props? Theoretical syntax:
<progress-bar class="app-progress-bar" {...props}></progress-bar>
Which is similar to React/JSX.
Like already answered, extending the component would be a nice way to create your components in this scenario. Similar to class inheritance in other languages.
However, you can also pass an object as prop. If you want to keep things clean, and don't want to extend your components, you could pass a prop like this:
//object with many values that you need to pass. for ex.
myObjectWithSuff: { percent: 10%, someOtherStuff: value, ... }
<progress-bar class="app-progress-bar" myProp="myObjectWithStuff"></progress-bar>
Inside the progress-bar component, declare the prop myProp. Then you can access any properties on that object. For example: this.myProp.percent.
This is just quick and simple, but depending on your architecture, extending components may be the way to go.
After researching this, I don't think it's possible. Instead, you can extend a component and put an extraClass key on data or computed:
import ProgressBar from './ProgressBar'
export default {
extends: ProgressBar,
data() {
return { extraClass: 'app-progress-bar' }
}
}
ProgressBar.vue:
<template>
<div class="progress-bar" :class="extraClass">...</div>
</template>
This isn't as clean or flexible as React, but it works.
Abstract
This can be done by binding $attrs to the child. So in your case it would be:
<template>
<progress-bar v-bind="$attrs" class="app-progress-bar"></progress-bar>
</template>
Link to Docs
vm.$attrs: Contains parent-scope attribute bindings (except for class and style)...and can be passed down to an inner component
Source: Vue $attrs documentation
Example
Let's say that a component called <inner> has a boolean prop called dark and a string prop called color.
Now let's define an outer component called <outer> which wraps <inner> in its definition.
<template>
<inner></inner>
</template>
.
.
.
name: 'outer'
The issue here is if we use the <outer> component, we can't transfer the dark and color props through to the <inner> component which knows what to do with them. For example <inner dark color="blue"></inner> works, but <outer dark color="blue"></outer> doesn't.
If you change the definition of <outer> to include the $attrs binding, it will transfer all the props for you (this doesn't include style and class). Here is the new definition:
<template>
<inner v-bind="$attrs"></inner>
</template>
.
.
.
name: 'outer'
Now you can use <outer dark color="blue"></outer>.
Additional note
You can also use the inheritAttrs prop to prevent this behaviour by default. You can take a look at the official Vue docs here and I also found this JSFiddle which gives an example of inheritAttrs being used.

What are the difference between reactJS props and refs?

I am getting confused with props and refs in ReactJS. Can anybody explain me the difference between them with proper example.
Thanks in advance.
Props are used to pass parameters which should be static (on the contrary of state). For example you can pass a size or name from an upperView to a lowerView (nested views);
Interesting part on props: https://facebook.github.io/react/docs/transferring-props.html
refs are used to acces the real DOM and not the virtual DOM of react. It's needed when you need to access the real DOM.
This part is interesting :https://facebook.github.io/react/docs/more-about-refs.html
this.setState({userInput: ''}, function() {
// This code executes after the component is re-rendered
React.findDOMNode(this.refs.theInput).focus(); // Boom! Focused!
});
The example above show you how to access a DOM element properly when the state is updated.
Hope it helps.
For sure there are differences between, one mostly use for selecting the DOM, one for getting data as a property, I create the image below and explain few major differences:
Also this the sample of grandparent, parent and child components which using ref and props to pass data, it's a good example to understand when and how they get used, please pay attention how ref helping to get in deeper component by referencing to the element:
function Child(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
function Parent(props) {
return (
<div>
My input: <Child inputRef={props.inputRef} />
</div>
);
}
class Grandparent extends React.Component {
render() {
return (
<Parent
inputRef={el => this.inputElement = el}
/>
);
}
}
Those are two different things.
Props: Use them to pass any parameters to your component.
Refs: Shortcut for references. These are references to your DOM elements. Use them if you need to access raw DOM element for some reason. For example to add custom event handler via .addEventListener() function.
in addition to the answer above by François Richard, u might wanna read:
https://github.com/uberVU/react-guide/blob/master/props-vs-state.md
because the confusion is more often between state and props.
good luck

Categories