Vue with Laravel, passing null props - javascript

Is it possible to pass a null prop? I have defined a master component which takes user prop.
<div id="app">
<master :user="{{ $user }}"></master>
</div>
Prop is defined like this:
props : {
user: {
type: Object
}
},
Now inside the controller I am passing user variable if user is authenticated, otherwise I am passing null:
public function index()
{
$user = Auth::check() ? Auth::user() : null;
return view('master', compact('user'));
}
I am getting an issue that The value for a v-bind expression cannot be empty. I couldn't make it even if I remove the colon so that it doesn't bind. I also tried explicitly setting that prop is not required, but none of that worked.
Can this be resolved somehow so that I pass a null/empty object to Vue?

In JS null is actually of type object. But the prop check of Vue does not consider null to be an object, see here.
Anyway, if $user is null, {{ $user }} is converted to nothing so you end up with
<master :user=""></master>
Which is an empty string.
You could either not specify the type, like this:
props : ['user'],
or if you want to, specify the type as string or Object:
props : {
user: {
type: [Object, String]
}
},
or you create an empty object if $user is null:
<master :user="{{ $user ?? '{}' }}"></master>

Although the accepted answer gives a solution, I think the exact requirement can be easily achieved by using PHP Null Coalescing Operator and VUE Multiple Prop Types
For example:
VUE component prop updated to accept both Object and null type
props: {
user: {
type: [Object, null]
}
},
Then in the blade file
<div id="app">
<master :user="{{ Auth::user() ?? 'null' }}"></master>
</div>

You can use null as default value. I usually do this.
props: {
user: {
type: Object,
default: null,
}
},
The downside is, when user is changed from null to object, you can't change it back to null.
What I do when I need the clear the object, I use empty object and make it as default.
props: {
user: {
type: Object,
default: () => {},
}
},

Related

Dynamically form Input, to a function in Service, from Input from Template- Angular

Let's say a service has a Subject, whose value you want to dynamically set from a component.
A.service.ts:
//ChartTools: zoom?: boolean, pan?: boolean
public ChartConfig$: BehaviorSubject<ChartTools> = new BehaviorSubject(this.defaultValues);
public changeChartConfig(config: ChartTools):void{
const currentValues = this.ptChartToolsConfig$.value;
this.ptChartToolsConfig$.next({
...currentValues,
pan: config.pan,
zoom: config.zoom
})
}
Now in component A,we want to set this subject dynamically through a function:
A.component.html:
<mat-checkbox
value="zoom"
[checked]= "ToolsConfig$.zoom"
(change) = changeChartConfig($event.value, $event.source._checked)
>
A.component.ts
ngOnInit():void{
private ToolsConfig$ = this.A_Service.ChartConfig$.subscribe();
}
//cTask['constant']= "zoom" | "pan"
changeChartConfig(component: cTask['constant'], checked: boolean):void{
this.A_Service.changeChartConfig({
component : checked
})
}
But, the input 'component' in changeChartConfig() is not even used and I get the error:
Argument of type '{ component: boolean; }' is not assignable to parameter of type 'ChartTools'.
Object literal may only specify known properties, and 'component' does not exist in type 'ChartTools'.
I understand It's saying 'component' doesn't exist in interface ChartTools, but the values of component exist in ChartTools, and I want to use them.
Can someone please help me with what can be done to resolve this?
You have a typescript issue here.
What your error is saying is that you are expecting params to be ChartTools, but you are sending in something that looks different. Check your interface to make sure everything fits in properly. Also if in interface you are lacking component property add it. If everything is fine reset your VScode
So lets say your ChartTools interface looks like this:
interface ChartTools {
property1: string;
property2: number;
component: boolean;
}
The error prevents you to send in a value that lacks property1 and property2, because it is not a ChartTool.
How to fix it:
either make property1 and property2 opitonal:
interface ChartTools {
property1?: string;
property2?: number;
component: boolean;
}
use TS Partials, or add the missing params to you component when you are sending data:
changeChartConfig(component: cTask['constant'], checked: boolean):void{
this.A_Service.changeChartConfig({
component : checked
missingProp1: 'Some value'
missingProp2: 'Another value'
})
}
EDIT
If you are trying to achieve dynamic property name do it this way:
this.A_Service.changeChartConfig({
[component] : checked
// [component] will be parsed instead of being sent in as a string
})

vuejs boolean props watch doesn't trigger

I'm working on a vue cli project where items have two state equipped and unequipped.
This State is controlled by a Boolean located in the Props. Since you can switch the state I had to create a data isEquipped set to false by default.
I then added a watcher but it doesn't change my data value if my props is set to True.
Here's the code
name: "Item",
props: {
Index : Number,
name: String,
desc : String,
bonus: Array,
equipped : Boolean
},
data() {
return {
isEquipped : false
}
},
watch: {
equipped: function(stateEquipped) {
this.isEquipped = stateEquipped;
},
},
So for instance let's say I created a new item with equipped set to True, the watcher doesn't trigger and isEquipped stays at False, is there any reason to that ?
I came across multiple similar questions like this one Vue #Watch not triggering on a boolean change but none of them helped me
If you want to use watch then you can try define it as:
equipped: {
handler () {
this.isEquipped = !this.isEquipped;
},
immediate: true
}
This will change the value of this.isEquipped whenever the value of equipped will change.
I am not sure what is the use case of isEquipped but seeing your code you can use the props directly unless there is a situation where you want to mutate the isEquipped that is not related to the props.
Why not just use a computed value instead?
{
// ...
computed: {
isEquipped () {
// loaded from the component's props
return this.equipped
}
}
}
You can then use isEquipped in your components just as if it was defined in your data() method. You could also just use equipped in your components directly as you don't transform it in any way.
<p>Am I equipped? - <b>{{ equipped }}</b></p>
Watchers are "slow" and they operate on vue's next life-cycle tick which can result in hard to debug reactivity problems.
There are cases where you need them, but if you find any other solution, that uses vue's reactivity system, you should consider using that one instead.
The solution using a computed value from #chvolkmann probably also works for you.
There is a imho better way to do this:
export default {
name: "Item",
props: {
Index : Number,
name: String,
desc : String,
bonus: Array,
equipped : Boolean
},
data() {
return {
isEquipped : false
}
},
updated () {
if (this.equipped !== this.isEquipped) {
this.isEquipped = this.equipped
// trigger "onEquip" event or whatever
}
}
}
The updated life-cycle hook is called -as the name suggests- when a component is updated.
You compare the (unchanged) isEquipped with the new equipped prop value and if they differ, you know that there was a change.

Understanding the order of properties execution in Vuejs [duplicate]

This question already has answers here:
Access vue instance/data inside filter method
(3 answers)
Closed 2 years ago.
I'm creating a simple Vuejs div component (to show a specific value) which needs to receive: a lists, a placeholder and a value as props. What I'm trying to do is displaying the value with the data from my database, if the user picks a new value from the lists, it should take that new value and display it. However, if the user never picks a new value and the data from the database is empty, it should display the placeholder.
So I have used filters to achieve this. However, it outputs an error: "Cannot read property 'lists' of undefined", which comes from the filters (I know because it outputs no error if I comment out the filters). When I changed the filter to this:
filters: {
placeholderFilter () {
return this.placeholderText || this.placeholder
}
}
It says:""Cannot read property 'placeholderText' of undefined"". So I was wondering if the filters properties executed before the data and props properties. What is the execution order of them? I have attached some of the relevant code down below. Anyway, If you could come up with a better way to achieve this. I would appreciate it!
Here is my component:
<template>
<div>{{ placeholderText | placeholderFilter }}</div>
<li #click="pickItem(index)" v-for="(list,index) in lists" :key="index">{{ list }}</li>
</template>
<script>
export default {
props: {
lists: {
type: Array,
required: true
},
value: {
type: [String, Number],
default: ''
},
placeholder: {
type: String,
default: ''
}
},
data () {
return {
selected: -1,
placeholderText: this.value || this.placeholder
}
},
methods: {
pickItem (index) {
this.selected = index
}
},
filters: {
placeholderFilter () {
return this.lists[this.selected] || this.placeholderText || this.placeholder
}
}
}
</script>
And this is where I use it:
<my-component
placeholder="Please type something"
value="Data from database"
lists="['option1','option2','option3']"
>
</my-component>
Filters aren't bound to the component instance, so they simply don't have access to it through the this keyword. They are meant to always be passed a parameter and to return a transformed version of that parameter. So in other words, they're just methods. They were removed in Vue 3 entirely probably for that reason.
And yeah, what you're looking for here is a computed!

MST: How to create the main store correctly?

I want to create rootStore which contains others store. The problem is that the children contain properties like:
id: types.identifier(types.string),
And when I create the rootStore, I get an error from the child:
[mobx-state-tree] Error while converting {} to SomeModelStore: at path "/id" value undefined is not assignable to type: identifier(string) (Value is not a string), expected an instance of identifier(string) or a snapshot like identifier(string) instead.
I tried to use types.late but it did not help.
The solution I found is to wrap all properties into types.maybe
Examples:
error:
https://codesandbox.io/s/yvnznxyvyj?module=%2Fmodels%2FSomeModelStore.js
workaround:
https://codesandbox.io/s/0mv558yq50?module=%2Fmodels%2FSomeModelStore.js
Here https://codesandbox.io/s/yvnznxyvyj?module=%2Fmodels%2FSomeModelStore.js you create an empty object
.model("FirstStore", {
item: types.optional(SomeModelStore, {})
})
but type
SomeModelStore
didn't support empty fields. If you write like this
export const FirstStore = types
.model("FirstStore", {
item: types.optional(SomeModelStore, {
id: 'defaultId',
activate: false,
name: 'defaultName'
})
})
it will work. Or you can use "types.maybe" instead of "types.optional".
export const FirstStore = types
.model("FirstStore", {item: types.maybe(SomeModelStore)})
Also read about types.reference
I think it's a better way to use it in your case.

dynamic params for ember component

I'm working on an Ember project in which I have to specify the parameters of the component dynamically.
I have the following array in the .js controller:
componentParams: ["id", "name"]
What I want to do is to take the values in the array and use them in handlebars as the component parameter like this
{{component-name id=somevalue name="somevalue"}}
Could this be done?
An approach I use.
controller.js
navbarParams: {
titleToShow: 'General.ResearchProjects',
glyphicon: 'glyphicon glyphicon-globe',
infoText: 'information/project'
},
template.hbs
{{my-navbar params=navbarParams}}
my-navbar.hbs
<h1> {{params.titleToShow}} <span class={{params.glyphicon}}> </span> </h1>
If your parameters are queryParams
They should be defined like that
queryParams: ['foo', 'bar',],
foo: null,
bar: null
{{my-navbar first=foo second=bar}}
Honestly it depends, if you are stuck with that array - you can use computed properties to extract the proper array values. ( This is probably not recommended - a better approach would be to format your componentParams into an object ( like #kristjan's example).
If you are stuck with the array - and the positions will never change ( id will always be componentParams[0] & name will always be componentParams[1], you could try something like this ::
// controller
import Ember from 'ember';
const {
Controller,
computed,
get
} = Ember;
export default Controller.extend({
componentParams: ['id', 'name'],
componentName: computed('componentParams', {
get() {
return get(this, 'componentParams')[1];
}
}),
componentId: computed('componentParams', {
get() {
return get(this, 'componentParams')[0];
}
})
});
// template
{{my-component name=componentName id=componentId}}
// component/template
name:: {{name}}
<br>
id :: {{id}}
check out this twiddle for a working example
Does this help ??

Categories