I'm facing the below problem.
I have a pure web component:
<my-web-comp options='["op1", "op2"]' active-option="op2"></my-web-comp>
This renders as two tabs with the second one selected by default. When you click on the other, the active-option HTML attribute changes to op1 and you can actually see that the property is changing in the DOM if you open the DevTools.
However, I cannot detect the change in the Vue component where I am using the web component. I have:
<template>
<div>
<my-web-comp :options="options" :active-option="activeOption"></my-web-comp>
</div>
</template>
<script>
export default {
name: 'MyVueComponent',
data() {
return {
options: '["op1", "op2"]',
activeOption: "op2"
}
},
computed: {
testVar() {
console.log("activeOption", this.activeOption) <--------- THIS LINE
},
}
}
</script>
The marked line only gets fired on the first load of the Vue component (printing "op2"). After that, testVar never gets modified again, doesn't mind if I click on the other tab and I don't see nothing in the console.
What can I be missing? I think it can be something related with Vue reactivity system, but can't wonder what.
This happens because your web-component mutates copy not a reference of this variable (copy created by your web component is also not reactive). There are two ways to change this:
You can modify your web component to use getters and setters to change value of this variable
You can use MutationObserver. To detect changes in your web-component. This approach will not require changes in this web-component
If you choose approach with MutationObserver then create this observer in vue mounted life-cycle-hook
Related
I'm currently learning vue.js and i'm struggling with the communication between parent and child components.
I'm trying to build two components (in separate files), a "accordion-container" and "accordion". The idea ist to then use them something like that on pages:
<accordion-container>
<accordion :title="'Accordion n1'">Insert HTML code here</accordion>
<accordion :title="'2nd Accordion'">Insert HTML code here</accordion>
</accordion-container>
Code for the container:
<template #closeAccordions="closeOtherAccordions">
<div class="accordion-container"><slot></slot></div>
</template>
<script>
export default {
props:['title'],
methods:{
closeOtherAccordions: function(){
console.log('Emit from child component received')
},
},
data: function() {
return {
}
}
};
</script>
Code for the accordions:
<template>
<div class="accordion" v-bind:class="{ open: isOpen }" :data-title="title">
<div class="title" #click="toggleAccordion">
<p>{{title}}</p>
</div>
<div class="content"><slot></slot></div>
</div>
</template>
<script>
export default {
props:['title'],
methods:{
toggleAccordion: function(){
this.isOpen = !this.isOpen
this.$emit('closeAccordions')
}
},
data: function() {
return {
isOpen: false
}
},
};
</script>
On the accordion i'm trying to emit "closeAccordions" (with the method toggleAccordion())
Then on the parent (accordion-container) i'd like to "listen" for that emit (with :closeAccordions="closeOtherAccordions"), and then execute a method on the parent.
But that method does not get called when i click the accordions.
Is my idea even possible? (Open to other ideas :) )
It won't work that way. The parent component cannot directly communicate to any components rendered within its slots via events, props, or by any other means that can only be achieved at the site where the slot contents are directly rendered (the container component doesn't control this).
When you are designing a component and you put a <slot> in the template, all you are doing is designating an insertion point within the template that users of the component can inject their own content.
You have 4 options:
(Advanced) Write the render function by hand and override the rendered slot vnodes to inject your own event listeners, props, etc.
Expose an API using scoped slots where you pass some data or methods to the slot which the user of the component would have to hook up in order for the component to operate correctly. Users of the component would have to remember to hook everything up correctly between the container and each accordion, so it's not ideal in this situation, but in general it is useful when you want to leave some of the functionality up to the user as to how the parent and children should operate.
Don't use events to communicate between the container and accordions, instead the accordions can call methods on the container directly via this.$parent.
Use provide/inject to allow the container to provide an API that each accordion can inject and use.
(3) is the recommended approach in this situation. The container and accordion components should be tightly coupled here. The accordion component can (and should) only be used directly within the container component, so it's OK if they communicate directly like that.
// Change this
this.$emit('closeAccordions')
// To this
this.$parent.closeOtherAccordions()
For more complicated components, (4) might be better.
I'd like to use js-data 3.0.1 as store for my Vue.js 2.4.2 SPA. Everything works like a charm, but unfortunately I can't make the reactive data bindings work.
I've already tried vue-js-data, which seems to be broken.
The following example doesn't work. If I change the text fields, the text will not be updated. However if I replace the js-data record with a plain old JS object, it works like expected.
user.vue:
<template>
<div>
<input name="name" v-model="user.name" />
<br />
Name: {{user.name}}
</div>
</template>
<script>
export default {
data() {
return {
user: this.store.createRecord('user')
}
}
}
</script>
I'm thankful for any advice how to make the data binding (via v-model for example) work.
If you add this to the constructor of your User class, it should work for all root level properties. See here for a complete example: https://gist.github.com/calebroseland/2fa37abdb5560739b3b4b901382b0a90
// apply vue reactivity
for (let key in this) {
Vue.util.defineReactive(this, key, this[key])
}
// re-apply js-data schema
this._mapper().schema.apply(this)
The reason Vue and Js-Data reactivity don't work together out-of-the-box is that they both have separate implementations that use slightly different mechanisms. Here's an explanation: https://medium.com/p/525ffe12ad81#c925
I have a component that basically loads a video in an overlay using JW Player (simplified example below).
import React, { Component } from 'react'
import ReactJWPlayer from 'react-jw-player'
class VideoPopup extends Component {
render() {
return (
<div id="video">
<ReactJWPlayer
playerId='video-player'
playerScript='https://content.jwplatform.com/libraries/vr6ybmGf.js'
file='path to video file'
/>
</div>
)
}
}
export default VideoPopup;
I would like the component to sit directly in the root of my app, but I need to be able to display it when called from ANY other component - this might be a child component, a child of a child, a sibling etc. etc. I was hoping to be able to call it and pass the video file reference simply like below:
<button onClick={somehow show video popup}>show video popup</button>
I understand how to do this easily if there is a direct parent-child relationship, but not if I want to place the link in a variety of different components; I hope someone can point me in the right direction.
If you want to get rid of the parent-children relationships when it comes to actions, use an event manager like Redux (react-redux). It is pretty standard an becomes necessary as your app grows anyway.
The principle is that wherever you place your link, it will fire an "action" on click, which is sent to a global dispatcher that other components listen to for changes.
Multiple ways to do this
You can define a function that controls the show/hide functionality
of the video player in the app component itself and pass it down as a
prop to all the components where the event can be fired.
Use Redux. This is the ideal choice. You just have to dispatch an action from anywhere in your app and the corresponding reducer will take care of the functionality.
Using a global function (not recommended).
Please comment if you need more explanation.
You can try to make global function and call it wherever you want.
showVideoPopup () {
ReactDOM.render(
<VideoPopup />,
document.getElementById('popupHolder')
);
}
I've been experimenting with creating a component based UI using ReactJS, versus my usual slapdash approach of a million global functions, variables and non-reusable markup. So far I really like React but I've hit a stumbling block.
Consider the following component layout
EventView
EventViewSidebar
EventViewList
EventViewListRow
EventViewDetail
In this layout, multiple occurrences of EventViewListRow are present for each unique key. Clicking an instance of EventViewListRow should update EventViewDetail with the details of that item.
This is the render function for the top level EventView component:
render: function () {
return (
<div className="event-view row-fluid">
<div className="event-view__sidebar col-md-4">
<EventViewSidebar projectId={this.state.projectId} />
</div>
<div className="event-view__content col-md-8" id="eventDetail">
</div>
</div>
);
}
And this is the EventViewDetail component
var EventViewDetail = React.createClass({
getInitialState: function () {
return { eventId: 0 };
},
render: function () {
if (this.state.eventId === 0) {
return (<h3>Nothing selected</h3>);
}
else {
return (
<div>
{this.state.eventId}
</div>
);
}
}
});
For the updating of EventViewDetail when a EventViewListRow is clicked, I have the following event handler defined in EventViewListRow
handleClick: function (event) {
event.preventDefault();
React.render(
React.createElement(EventViewDetail, { eventId: this.props.id }),
document.getElementById("eventDetail")
).setState({ eventId: this.props.id });
},
This all seems to be working fine (with the exception of the setState call above which I had to add otherwise clicking a different EventViewListRow didn't seem to have any effect - no doubt that's my first problem). The actual critical problem is that if I add default html to the eventDetail div defined in EventView then when I click the link in EventViewListRow, the following message is displayed in the console and the browser hangs.
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) <h3 data-reactid=".0">Nothing selected
(server) <h3 data-reactid=".0.1.0">Select an even
Once the browser tab (Chrome 43) has hung, I have to terminate it using Task Manager.
Originally, I was calling an instance of the EventViewDetail directly, for example
<div className="event-view__content col-md-8" id="eventDetail">
<EventViewDetail />
</div>
but it also hangs if I just use vanilla HTML
<div className="event-view__content col-md-8" id="eventDetail">
<h3>Select an event to view</h3>
</div>
Clearly I'm doing something very wrong, but I'm somewhat unfamiliar with React so I don't know what that is. I read that I'm suppose to have state on the top level EventView component, but I don't have access to that and React doesn't seem to offer the ability to go back up the component chain. Unless you are supposed to pass the EventView instance as a property to each child component?
Oh, I should also add - I also tried removing the setState call from the EventViewListRow click handler in case that was the cause, but it had no effect.
Can anyone offer any advice on what it is I'm doing wrong. Should EventView have all the state for the child components, and if so, how do I reference the parent from a nested child component - do I have to pass the instance of EventView as a prop to every single child?
Sorry if these are idiot questions!
You should not call React.render in the handleClick function. Just call this.setState and React will automatically render again.
Before anyone press eagerly the close button, I already have looked the following question: ReactJS Two components communicating. My problem is exactly the third scenario developped in the current accepted answer.
I am using ReactJS to build something with two components. For HTML reasons (and presentation), i want my two components to be at two different places of the page.
For the moment, I have the following pattern, corresponding to scenario #2:
FooForm = React.createClass({
...
});
FooList = React.createClass({
...
});
FooManager = React.createClass({
...
render: function () {
return (
<div>
<FooForm ref="form" manager={this} />
<FooList ref="list" />
</div>
);
}
});
React.render(
<FooManager someProp={value} />,
document.getElementById('foo')
);
This gives something like:
<div id="foo">
<form>Form generated with the render of FooForm</form>
<ul>List generated with the render of FooList</ul>
</div>
However, i would like to have something like this:
<div id="fooform">
<form>Form generated with the render of FooForm</form>
</div>
<!-- Some HTML + other controls. Whatever I want in fact -->
<div>...</div>
<div id="foolist">
<ul>List generated with the render of FooList</ul>
</div>
The problem here is: how can I keep a reference in each component? Or at least the link Form -> List?
I tried to create the FooList before and pass the reference to the current manager, but I get the following warning/error:
Error: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref.
The documentation says you can attach events to link two components which do not have a parent-child relation. But I don't see how. Can someone give me some pointers?
The Less Simple Communication lesson from react-training has a good example of how you can move actions & state sideways to avoid having to create an explicit link between related components.
You don't need to jump into a full Flux implementation to get the benefit of this approach, but it's a good example to lead you up to Flux, should you eventually need it or something like it.
Note that this requires you to model the relationship between the components based on changing state rather than explicitly passing a reference to a component instance (as you're doing above) or a callback bound to the component managing the state.
This would be the perfect use-case for a Flux type architecture.
What you want is someone FooManager to be able to trigger state changes in both components. Or, in fact, having the different components trigger, through Actions, state changes in each other.
The Flux Todo-App Tutorial illustrates your use-case perfectly!
After this, then you'd have the choices of using Facebooks implementation of Flux or the other gazillion ones.
My personal favorite is Reflux