I'm using a vaadin-dialog in my Web Components app. I would like the components inside to render on properties change but they don't.
I have a couple properties in my web component:
static get properties() {
return {
title: { type: String },
username: { type: String },
signUpOpened: { type: Boolean },
};
}
The dialog is defined as such. I use a function to bind it to the DialogRenderer. The dialog shouldn't be always open, so the user clicks it to open it.
render() {
return html`
<main>
<h1>${this.title}</h1>
<vaadin-dialog
header-title="SignUp"
.opened="${this.signUpOpened}"
#opened-changed="${e => (this.signUpOpened = e.detail.value)}"
${dialogRenderer(this.signUpRenderer)}
id="signUp">
</vaadin-dialog>
</main>
`;
}
signUpRenderer() {
return html`
<vaadin-text-field value="${this.username}" #change="${(e) => {
this.username = e.target.value
}}" label="email"></vaadin-text-field>
<vaadin-text-field value="${this.username}" }}" label="email-copy"></vaadin-text-field>
<p>${this.username}</p>
`;
}
You can try the demo over here, the source code is here.
When changing the value of the text field, you can see that the other text field and the paragraph don't update.
Can you folks direct me towards what is going on?
The dialogRenderer directive that you are using to render the contents of the dialog needs to know whether the data used in the rendering has changed in order to determine whether it should rerender the contents or not.
For that purpose the directive has a second parameter that allows you to pass in an array of dependencies that will be checked for changes.
In your example, you should apply the directive like so, in order for it to re-render when the username changes:
<vaadin-dialog
...
${dialogRenderer(this.signUpRenderer, [this.username])}>
</vaadin-dialog>
This is explained in the directive's JSdoc, unfortunately the directives do not show up in the component's API docs at the moment. You could open an issue here and request to improve the component documentation to add a section that explains how the directives work.
Related
I have a Prop in my component that is a User object, I then have this function:
onChange: function(event){
this.$v.$touch();
if (!this.$v.$invalid) {
this.$axios.put('update',{
code:this.user.code,
col:event.target.name,
val:event.target.value
}).then(response => {
this.user[event.target.name]=event.target.value
});
}
}
I can see in the Vue console debugger that the correct attribute has been updated, this attribute exists when the component is created but the template where I reference it does not refresh:
<p>Hi {{user.nickname}}, you can edit your details here:</p>
This is all within the same component so I'm not navigating to child or parent. I'm sure props have been reactive elsewhere in my code?
Ok, it seems this is intended behaviour. According to the documentation
https://v2.vuejs.org/v2/guide/components-props.html in the scenario that I have it should be handled as:
The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards. In this case,
it’s best to define a local data property that uses the prop as its
initial value:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
Usually components should be reactive to Props, though i have had experiences where it was non-reactive so i added the prop to a watcher and put the functional call there.
props: ["myProp"],
watch:{
myProp(){
// ... your functions here
}
}
I'm trying to use the <component> element to dynamically display a chosen component. Each of these displayed components can take one of any number of data objects. Something like:
<div id="containers">
<component v-bind:is="currentView"></component>
</div>
var myVue = new Vue({
el:"#containers",
data:{
currentView: "my-component-one",
currentData: {...}
},
method: {
changeView: function(){
//change this.currentView
//change this.currentData
}
}
});
However, the Vue documentation says the v-bind:is attribute can be used to pass either a component name or the options object.
It is unclear how I would conditionally get an object of values for that component to use and also conditionally change which component is shown.
I am very green with using Vue (coming fresh off a knockout kick) so perhaps I am simply misunderstanding the intention of the component tag.
you can simply use v-bind
html
<component v-bind:is="currentView" v-bind="data"></component>
script
data()
{
return {
data: {
currentData: "example"
}
}
}
and it will pass currentData down to child. You can also add other properties along with it, including is.
If you need to change the component along with props, then you just change the data property, or whatever you want to call it.
https://codesandbox.io/s/7w81o10mlx
This example might help you understand it. https://jsfiddle.net/jacobgoh101/mLbrj5gd/
For passing Component Name
If the component is global, you can pass the component name to v-bind:is
for e.g.,
Vue.component('test1', {
template: `<div>test1</div>`
})
HTML
<component is="test1"></component>
For passing option
A Vue component is literally just a Javascript object with specific properties
for e.g.,
<component v-bind:is="{
template: `<div>test2</div>`
}"></component>
I am using preactjs to create my application. On top of this, I am using kendo grid. Inside the grid, I want to show a hyperlink. if the user clicks on the link, it should change the route. To render the link, i am using preact-router.
Here is the working fiddle.
let { h, render, Component } = preact;
// import { ... } from 'preact';
let { route, Router, Link } = preactRouter;
/** #jsx h */
class App extends Component {
componentDidMount() {
console.log('did mount !');
$("#grid").kendoGrid({
selectable: "multiple cell",
allowCopy: true,
columns: [
{ field: "productName",
template: function(e) {
return <link href="/">Home</link>
} },
{ field: "category" }
],
dataSource: [
{ productName: "Tea", category: "Beverages" },
{ productName: "Coffee", category: "Beverages" },
{ productName: "Ham", category: "Food" },
{ productName: "Bread", category: "Food" }
]
});
};
render({}, { }) {
return (
<div>
<h1>
Preact Kickstart
<sub>powered by preact</sub>
</h1>
<div id="grid"></div>
</div>
);
}
}
// Start 'er up:
render(<App />, document.body);
This is not even related to preact.
What you're doing is rendering a kendo grid using via jquery inside a preact component and using a preact component as a template.
One way to fix this is to return a html string:
template: function(e) {
const linkEl = $('<a>')
.attr('href', '#') // keep the a el but do not redirect to a different page on click
.text('Home')
.click((e) => {
e.preventDefault(); // prevent the original a tag behavior
route('/'); // this is using the `route` from preactRouter, which is already included at the top of the original file
});
return linkEl[0].outerHTML; // access the JS DOM element from jQuery element and get it's full html
}
I've also replaced the link (which is a typo, probably should've been Link) with a tag since there is no link element in basic html. Even though the name is wrong, this still works because the JSX transformer will interpret all the lowercase components as string name instead of using the component as a function (see how Babel compiles it). The transformer will generate h("link", { href: "/" }, "Home") and the h function returns an object which is then rendered as [Object object] because this is what happens when you try to convert to a string via .toString function. If preact would work in this case, it would render the actual <link href="/">Home</link> to the user, which would not have the desired behavior (except if a custom link component is defined somewhere else).
You can't return a preact component here because the kendo grid template is expecting a string template. One way would be to convert the preact component to a string, but I'm not sure that's even possible, I have never seen it done and you shouldn't have to.
Note: As said, you shouldn't have to be converting little parts of React or React-like to html. I would strongly advise against mixing preact code with jQuery which is rendering the kendo grid in your case. These two libraries are doing the rendering very differently. While jQuery is using the old approach of directly modifying the DOM and replacing the whole subtree, React (and all the implementation, such as preact) are rendering to virtual DOM and then have internal logic which figures out the difference with the actual DOM the user is seeing and display only the minimum difference so it makes the least updates required. With my quick google search, I have found react-kendo, but it doesn't seem very popular. There are also some blog posts from the kendo team themselves, but I haven't found any official support. If you want to use preact, try to find the (p)react way of doing it, for your example you could be using something like react-table (official demo). If on the other hand, you want to use powerful UI tools provided by kendo, you would be better off not adding the preact to the mix, it will make things more complex without much benefit or it could even make the whole thing worse.
In my parent vue component I have a user object.
If I pass that user object to a child component as a prop:
<child :user="user"></child>
and in my child component I update user.name, it will get updated in the parent as well.
I want to edit the user object in child component without the changes being reflected in the user object that is in parent component.
Is there a better way to achieve this than cloning the object with: JSON.parse(JSON.stringify(obj))?
You don't have to use the JSON object.
const child = {
props:["user"],
data(){
return {
localUser: Object.assign({}, this.user)
}
}
}
Use localUser (or whatever you want to call it) inside your child.
Edit
I had modified a fiddle created for another answer to this question to demonstrate the above concept and #user3743266 asked
I'm coming to grips with this myself, and I'm finding this very
useful. Your example works well. In the child, you've created an
element in data that takes a copy of the prop, and the child works
with the copy. Interesting and useful, but... it's not clear to me
when the local copy gets updated if something else modifies the
parent. I modified your fiddle, removing the v-ifs so everything is
visible, and duplicating the edit component. If you modify name in one
component, the other is orphaned and gets no changes?
The current component looks like this:
Vue.component('edit-user', {
template: `
<div>
<input type="text" v-model="localUser.name">
<button #click="$emit('save', localUser)">Save</button>
<button #click="$emit('cancel')">Cancel</button>
</div>
`,
props: ['user'],
data() {
return {
localUser: Object.assign({}, this.user)
}
}
})
Because I made the design decision to use a local copy of the user, #user3743266 is correct, the component is not automatically updated. The property user is updated, but localUser is not. In this case, if you wanted to automatically update local data whenever the property changed, you would need a watcher.
Vue.component('edit-user', {
template: `
<div>
<input type="text" v-model="localUser.name">
<button #click="$emit('save', localUser)">Save</button>
<button #click="$emit('cancel')">Cancel</button>
</div>
`,
props: ['user'],
data() {
return {
localUser: Object.assign({}, this.user)
}
},
watch:{
user(newUser){
this.localUser = Object.assign({}, newUser)
}
}
})
Here is the updated fiddle.
This allows you full control over when/if the local data is updated or emitted. For example, you might want to check a condition before updating the local state.
watch:{
user(newUser){
if (condition)
this.localUser = Object.assign({}, newUser)
}
}
As I said elsewhere, there are times when you might want to take advantage of objects properties being mutable, but there are also times like this where you might want more control.
in the above solutions, the watcher won't trigger at first binding, only at prop change. To solve this, use immediate=true
watch: {
test: {
immediate: true,
handler(newVal, oldVal) {
console.log(newVal, oldVal)
},
},
}
you can have a data variable just with the information you want to be locally editable and load the value in the created method
data() {
return { localUserData: {name: '', (...)}
}
(...)
created() {
this.localUserData.name = this.user.name;
}
This way you keep it clear of which data you are editing. Depending on the need, you may want to have a watcher to update the localData in case the user prop changes.
According to this, children "can't" and "shouldn't" modify the data of their parents. But here you can see that if a parent passes some reactive data as a property to a child, it's pass-by-reference and the parent sees the child's changes. This is probably what you want most of the time, no? You're only modifying the data the parent has explicitly shared. If you want the child to have an independent copy of user, you could maybe do this with JSON.parse(JSON.stringify()) but beware you'll be serializing Vue-injected properties. When would you do it? Remember props are reactive, so the parent could send down a new user at any time, wiping out local changes?
Perhaps you need to tell us more about why you want the child to have it's own copy? What is the child going to do with its copy? If the child's user is derived from the parent user in some systematic way (uppercasing all text or something), have a look at computed properties.
I'm stuck in wondering if I can filter a list of names which I receive from Relay and graphql-java server without the need of making calls, without making any changes in my GrpahQL schema and only using ReactJS for this purpose.
---MobX as a state management library can be a decision but I should first store all the Relay result.
Caveat emptor: I'm newish at Relay as well & struggling with these same concepts. But, given the relative dearth of accessible information on Relay, I thought it'd be helpful to try and layout the key concepts here. My understanding could be wrong, so I'd love it if anyone who found a mistake in my code/reasoning would comment/edit.
Filtering took awhile for me to 'click' as well. It depends on where you keep the data you'll use to filter, but let's assume the name field lives on your Users Type, and the query is something like this:
viewer {
allUsers {
edges {
node {
name
}
}
}
}
And let's say your top-level NameList component looked like this:
class NameList extends Component {
render() {
return (
<div>
{this.props.users.edges
.map(u => {
<NameItem name={u.node} />
})
}
</div>
)
}
}
Relay.createContainer(NameList, {
initialVariables: { first: 10 },
fragments: {
users: () => Relay.QL`
fragment on Viewer {
allUsers(first: $first) {
edges {
node {
${NameItem.getFragment('user')}
}
}
}
}
`
}
})
And your NameItem setup was simply:
class NameItem extends Component {
render() {
return (
<div>
Name: {this.props.user.name}
</div>
)
}
}
Relay.createContainer(NameItem, {
initialVariables: {},
fragments: {
user: () => Relay.QL`
fragment on User {
name
}
`
}
})
Consider the generalizable pattern here:
The List Component
A List component takes a fragment on the top-level Type in the query--in this case, Viewer, from a Relay container.
List also inserts a fragment on behalf of its Item child at the level of the User Type.
In other words, it captures an array of User objects it's supposed to pass down to the Item component.
If this wasn't Relay, and instead was, say, Redux, this component might simply pass state.users to the Item component. You can do that because, at some point, you've manually extracted all your Users from your own back-end and loaded them into Redux. But since Relay does the hard thinking for you, it needs a teensy bit more information than Redux.
The Item Component
This is even more simple. It expects an entity of type User and renders the name. Besides syntax, the functionality here isn't much different from a similar component in a Redux setup.
So really, without the complexity of Relay on top, all you have is an array of items that you're rendering. In vanilla React, you'd simply filter the array prior to (or during) your call to .map() in render().
However, with Relay, the fragment handed to the child is opaque to the parent--i.e., the List is handing a blind package to the Item, so it can't make a decision on whether or not to pass it down based on the fragment's content.
The solution in this contrived example is pretty simple: just peel-off the name field at the parent and child level. Remember: Relay is about components telling GraphQL what data they need. Your List component needs whatever fields it intends on filtering on--no more, no less.
If we modify the above List container:
...
users: () => Relay.QL`
fragment on Viewer {
allUsers(first: $first) {
edges {
node {
name
${NameItem.getFragment('user')}
}
}
}
}
`
And then we update our render function:
<div>
{this.props.users.edges
.map(u => {
if (u.node.name == "Harvey") {
<NameItem name={u.node} />
}
})
}
</div>
Then we've achieved basic filtering without needing mobx, more server trips, etc.