I have 3 components: a mainComponent, a SideMenu, and ContentArea (both children of mainComponent). I want to make ContentArea see the changes of popupOpen value.
The function togglePopup() simply toggle the popupOpen boolean value.
In more details: I pass this function as property from mainComponent to SideMenu. The function changes popupOpen value, but this change isn't reflected in ContentArea.
mainComponent
class mainComponent extends LitElement {
constructor(){
super()
this.popupOpen = false
}
togglePopup() {
console.log("togglePopup from main comp");
this.popupOpen = !this.popupOpen
this.requestUpdate();
}
render(){
return html`
<div>
<side-menu .togglePopup='${this.togglePopup}' popupOpen="${this.popupOpen}"></side-menu>
<content-area popupOpen="${this.popupOpen}"></content-area>
</div>
`
}
}
SideMenu
class SideMenu extends LitElement {
constructor(){
super()
}
static get properties(){
return {
popupOpen: Boolean
}
}
render(){
return html`
<section id="side-menu">
<a #click="${this.togglePopup}" >Add contact</a>
</section>
`
}
}
ContentArea
class ContentArea extends LitElement {
constructor(){
super()
}
static get properties(){
return {
popupOpen: Boolean
}
}
render(){
return html`
<section id="content-area">
<p>POPUP VALUE: ${this.popupOpen}</p> <!-- this value doesn't change! -->
</section>
`
}
}
In order to fire togglePopup function properly, try:
<side-menu .togglePopup='${e=> this.togglePopup()}' .popupOpen="${this.popupOpen}"></side-menu>
instead :
<side-menu .togglePopup='${this.togglePopup}' .popupOpen="${this.popupOpen}"></side-menu>
Demo
Related
How I could efficiently pass value from MyElement to GrandChildrenElement?
index.html
<my-element></my-element>
myElement.ts
#customElement('my-element')
export class MyElement extends LitElement {
value = 'foo';
onChangeValue() {
this.value = 'bar';
}
render() {
return html`
<child-element></child-element>
`;
}
}
childElement.ts
#customElement('child-element')
export class ChildElement extends LitElement {
...
render() {
return html`
<grandchild-element></grandchild-element>
`;
}
}
grandChildElement.ts
#customElement('grandchild-element')
export class GrandChildElement extends LitElement {
#property()
value = '';
render() {
return html`
<p>${value}</p>
`;
}
}
The standard way would be to pass the value down through the child element using properties:
#customElement('my-element')
export class Element extends LitElement {
#state()
value = 'foo';
toggle() {
if (this.value === 'foo') {
this.value = 'bar';
} else {
this.value = 'foo';
}
}
render() {
return html`
<my-child value="${this.value}"></my-child>
<button #click="${this.toggle}">Toggle</button>
`;
}
}
#customElement('my-child')
export class Child extends LitElement {
#property({ type: 'string' })
value;
render() {
return html`<my-grandchild value="${this.value}"></my-grandchild>`;
}
}
#customElement('my-grandchild')
export class GrandChild extends LitElement {
#property({ type: 'string' })
value;
render() {
return html`<div>${this.value}</div>`;
}
}
Playground
But if you're looking for a way to bypass the elements in between, you'll have to get a bit more creative.
One solution could be to have the element collect subscribers and update them when its value changes.
In the example below, the grandchild dispatches a "subscribe" event when it connects, which bubbles up to the element. The element then gets the grandchild (via the event's composedPath), updates its value, and adds it to the set of subscribers. When the element changes its value within toggle(), it updates its subscribers.
import {html, css, LitElement} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
#customElement('my-element')
export class Element extends LitElement {
value = 'foo';
subscribers = new Set();
constructor() {
super();
this.addEventListener('subscribe', (e: CustomEvent) => {
const composedTarget = e.composedPath()[0] as any;
composedTarget.value = this.value;
this.subscribers.add(composedTarget);
});
this.addEventListener('unsubscribe', (e: CustomEvent) => {
const composedTarget = e.composedPath()[0];
this.subscribers.delete(composedTarget);
});
}
toggle() {
if (this.value === 'foo') {
this.value = 'bar';
} else {
this.value = 'foo';
}
for (const subscriber of this.subscribers) {
grandchild.value = this.value;
}
}
render() {
return html`
<my-child></my-child>
<button #click="${this.toggle}">Toggle</button>
`;
}
}
#customElement('my-child')
export class Child extends LitElement {
render() {
return html`<my-grandchild></my-grandchild>`;
}
}
#customElement('my-grandchild')
export class GrandChild extends LitElement {
#property({ type: 'string' })
value;
connectedCallback() {
super.connectedCallback();
this.dispatchEvent(new CustomEvent('subscribe', { bubbles: true, composed: true }));
}
disconnectedCallback() {
super.disconnectedCallback();
this.dispatchEvent(new CustomEvent('unsubscribe', { bubbles: true, composed: true }));
}
render() {
return html`<div>${this.value}</div>`;
}
}
Playground
I have an Polymer 3 module (simplified/wrong below to explain only);
import {html,PolymerElement} from '#polymer/polymer/polymer-element.js';
class myInput extends PolymerElement {
static get template() {
return html `
<input id="inputBox" value='{{bar::input}}'/><br/>
<a>You have typed [[bar]]!</a>
`;
}
static get properties() {
return {
bar: {
observer: '_dataChanged',
},
}
_dataChanged () {
this.bar = "BAR HAS CHANGED!!"
}
}
[[bar]] is successfully updated & displayed on page.
{{bar::input}} successfully fires _dataChanged.
But [[bar]] does not update & display "BAR HAS CHANGED!!" on page when _dataChanged() is triggered.
Any idea what I have done wrong?
Thanks for your help.
Use one of the polymer button element. Then you can bind the value to the bar property easy. Here the example:
DEMO
import { PolymerElement, html } from '#polymer/polymer';
import '#polymer/paper-button/paper-button.js'
class MyElement extends PolymerElement {
static get properties() {
return {
bar: {
observer: '_dataChanged',
}
}}
static get template() {
return html`
<input id="inputBox" value='{{bar::input}}'/><br/>
<paper-button on-tap="_clickMe">You have typed [[bar]]!</paper-button>
`;
}
_dataChanged(d){console.log(d)}
_clickMe () {
this.bar = "CLICKED!!"
}
}
customElements.define('my-element', MyElement);
I found the child component Element was not connected when I want to use action in child component.
how to connect it ?
code:
import { connect } from 'react-redux';
import * as actions from '../actions';
class Element extends React.Component {
constructor(props) {
super(props);
}
render() {
let { children } = props.el.children;
return (
<div>
{ children ?
children.map(el =>
<Element key={ 'el-' + el.id } el={ el } />
)
:
null
}
</div>
)
}
}
Element = connect(
null
,
actions
)(Element);
export default Element;
You are rendering <Elemnt /> inside it's own render function.
If you just want to pass the children down the render function of your Element component then just change it to this:
import { connect } from 'react-redux';
import * as actions from '../actions';
class Element extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{ this.props.children }
</div>
)
}
}
Element = connect(
null
,
actions
)(Element);
export default Element;
Note that the wrapper <div> is there because current version of react only allow one top level element in the render body, and this.props.children can hold more than one element.
I have the following react component that passed data to another component:
export default class App extends TrackerReact(Component){
getUserFrameData(){
return (FrameCollection.find().fetch());
}
render(){
return(
<div className="main-container">
<Frames
data={this.getUserFrameData()}
/>
</div>
);
}
}
Now I want my frames component to do an action when the component initialises.
export default class Frames extends Component{
componentDidMount(){
console.log(this.props.data);
}
render() {...}
}
But on I only get empty data at on loadup. I think it's because I'm using subscriptions and a login system. So how can I tell my Frames component to wait until everything is "loaded up"?
Use the ready method of the subscription object.
constructor() {
super();
this.state = {
sub: Meteor.subscribe('myPublication')
}
}
render() {
if (!this.state.sub.ready()) return <p>Loading...</p>;
return ...
}
http://docs.meteor.com/api/pubsub.html#Subscription-ready
At which point in a React components life cycle can I get the components css properties which are set in a css file?
I've tried it in the render method and the componentDidMount method and neither assigned the css properties to the component.
export default class HomeArtist extends React.Component {
constructor(props){
super(props);
}
componentDidMount(){
let ImageStore = document.getElementsByClassName('home-artist-display');
console.log("ComponentDidMount: ", ImageStore);
}
render(){
var ImageStyle = {
backgroundImage: "url("+this.props.info.image+")"
};
return (
<div className="home-artist-display" style={ImageStyle}>
<Link to={"artist/" + this.props.info.id}>
<h3 className="home-artist-name">{this.props.info.name}</h3>
</Link>
</div>
)
}
}
I wrote a React library that exposes a size object (with width and height props) to components.
For your use case you could use it like so:
import SizeMe from 'react-sizeme'; // import me!
class HomeArtist extends React.Component {
...
render(){
// Size gets passed in as props!
const { width, height } = this.props.size;
var ImageStyle = {
backgroundImage: "url("+this.props.info.image+")"
};
return (
<div className="home-artist-display" style={ImageStyle}>
<Link to={"artist/" + this.props.info.id}>
<h3 className="home-artist-name">{this.props.info.name}</h3>
</Link>
</div>
)
}
}
// wrap your component export!
export default SizeMe()(HomeArtist);
--
You can find out full details at https://github.com/ctrlplusb/react-sizeme