How do I sent data to element's grandchildren in LitElement? - web-component

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

Related

Calling a function from another component with redux

Trying to toggle open a modal from another component with redux. Almost there but not really sure how to finish it up - been looking around for a clear answer!
On the HomeScreen component (the main component), to activate the openModal method on the AddCircleModal component, causing the Modal to open.
The Modal - AddCircleModal: Using redux, I can successfully close the modal if I open it manually in the code
class AddCircleModal extends React.Component {
state = {
top: new Animated.Value(screenHeight),
modalVisible: false
}
// componentDidMount() {
// this.openModal()
// }
openModal = () => {
Animated.spring(this.state.top, {
toValue: 174
}).start()
this.setState({modalVisible: true})
}
closeModal = () => {
Animated.spring(this.state.top, {
toValue: screenHeight
}).start()
this.setState({modalVisible: false})
}
render() {
return (
<Modal
transparent={true}
visible={this.state.modalVisible}
>
<AnimatedContainer style={{ top: this.state.top, }}>
<Header />
<TouchableOpacity
onPress={this.closeModal}
style={{ position: "absolute", top: 120, left: "50%", marginLeft: -22, zIndex: 1 }}
>
<CloseView style={{ elevation: 10 }}>
<FeatherIcon name="plus" size={24} />
</CloseView>
</TouchableOpacity>
<Body />
</AnimatedContainer>
</Modal>
)
}
}
function mapStateToProps(state) {
return { action: state.action }
}
function mapDispatchToProps(dispatch) {
return {
closeModal: () =>
dispatch({
type: "CLOSE_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddCircleModal)
HomeScreen: The other component that I want to toggle from
//redux
import { connect } from 'react-redux'
import styles from './Styles'
class HomeScreen extends React.Component {
constructor() {
super();
this.state = {
};
}
toggleOpenCircleModal = () => {
// this.openModal() - what do I do with this to call the openModal function in the modal component?
console.log('owwwww weeeee')
}
render() {
return (
<SafeAreaView>
<HomeHeader openModal={this.toggleOpenCircleModal}/> - this method is because I'm calling toggleOpenCircleModal from a button in the header of the home screen. It works as it outputs the 'owwwww weeeee' string to the console.
<SafeAreaView style={{ width: '100%', flex: 1}} />
<AddCircleModal />
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return { action: state.action }
}
function mapDispatchToProps(dispatch) {
return {
openModal: () =>
dispatch({
type: "OPEN_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
modalToggle: The reducer
const initialState = {
action: ""
}
const modalToggle = (state = initialState, action) => {
switch (action.type) {
case "OPEN_MODAL":
return { ...state, action: "openModal" }
case "CLOSE_MODAL":
return { ...state, action: "closeModal" }
default:
return state
}
}
export default modalToggle
Right now, your components are not using redux store properly.
When you use mapStateToProps, you can access every redux store reducer. You can access every prop in them and these will be sent via props in your connected component. For instance:
//redux
import { connect } from 'react-redux'
import styles from './Styles'
class HomeScreen extends React.Component {
constructor() {
super();
this.state = {
};
}
toggleOpenCircleModal = () => {
if(this.props.action === 'openModal') {
this.props.openModal();
} else {
this.props.closeModal();
}
}
render() {
const { action } = this.props; // this.props.action is coming from Redux Store
return (
<SafeAreaView>
{action} // this will be 'openModal'
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return { action: state.action } // this will be available in HomeScreen as props.action
}
function mapDispatchToProps(dispatch) {
return {
openModal: () =>
dispatch({
type: "OPEN_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
You can read more on https://react-redux.js.org/using-react-redux/connect-mapstate.
The same goes for mapDispatchToProps. In your case, openModal will be available in props.openModal in your HomeScreen component. You can read more on https://react-redux.js.org/using-react-redux/connect-mapdispatch
Based on this, in your AddCircleModal component, you should be using props.action to evaluate if the modal should be visible. (props.action === 'openModal').
If you want to open or close your modal, you'll just need to call the openModal or closeModal dispatch call in your component. In HomeScreen component, in your function toggleOpenCircleModal, you will call openModal() or closeModal() depending on props.action === 'openModal'.
Lastly, you should be using just a boolean value to check for the modal visibility, instead of a string, if that's the only purpose for your reducer.
const initialState = false;
const modalToggle = (state = initialState, action) => {
switch (action.type) {
case "OPEN_MODAL":
return true;
case "CLOSE_MODAL":
return false;
default:
return state
}
}
export default modalToggle

How to pass CSS styles to my Angular 2 component?

I've built an Angular2/4 component that is, basically, a masked date input. I use it in place of a textbox input, and have some code behind it to treat date conversions. It works well enough, but now i want to apply a CSS style and have it changing the input.
I want to write <app-date-input class='someCssClass'></app-date-input> and that class be attributed to my internal input.
My code for the component follows:
date-input.component.html
import { Component, Input, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
import { DatePipe } from "#angular/common";
import * as moment from 'moment';
date-input.component.ts
import { AppConstantsService } from "../../_services";
#Component({
selector: 'app-date-input',
templateUrl: './date-input.component.html',
styleUrls: ['./date-input.component.css'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateInputComponent),
multi: true
}]
})
export class DateInputComponent implements ControlValueAccessor {
#Input()
public valor: Date;
#Input()
public readonly: boolean;
public dataString: string;
constructor(public appConstants: AppConstantsService) {
}
atribuirData(dataEntrada: string) {
if (!dataEntrada || dataEntrada == '') {
this.valor = null;
this.propagateChange(this.valor);
return;
}
let parts = dataEntrada.split('/');
try {
let newDate = moment(parts[2] + '-' + parts[1] + '-' + parts[0]).toDate();
// let newDate = new Date(+parts[2], +parts[1]-1, +parts[0]);
this.valor = newDate;
this.propagateChange(this.valor);
} catch (error) {
// return dataEntrada;
console.log(error);
}
}
writeValue(value: any) {
this.valor = value;
const datePipe = new DatePipe('pt-BR');
this.dataString = datePipe.transform(this.valor, 'dd/MM/yyyy');
}
propagateChange = (_: any) => { };
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched() {
}
}
Although there are multiple ways to achieve this
Setting styles in the Child Component
<app-date-input [childClass]='someCssClass'></app-date-input>
Now in DateInputComponent, create an #Input() property and in the HTML for the component, you can do something like
'<input [class]="input Variable Name">'

How to find unused style definitions in React?

Is there a way to automagically find unused style definitions in a React based project, like in this example?
Before:
const styles = StyleSheet.create({
name: {
color: '#e5e5e5'
}
});
const Hello = React.createClass({
render: function() {
return <Text style={styles.name}>Hello {this.props.name}</Text>;
}
});
After:
Developer has changed styles and "name" is not required any more. How can this kind of dead style code be automatically found?
const styles = StyleSheet.create({
name: { // Redundant now!
color: '#e5e5e5'
},
foo: {
color: '#e2e3b5'
}
});
const Hello = React.createClass({
render: function() {
return <Text style={styles.foo}>Hello {this.props.name}</Text>;
}
});
Possible solution
1) helpers.js
// helpers.js
import ReactTestUtils from 'react-addons-test-utils'
export function unusedStyles(component, styles) {
const renderer = ReactTestUtils.createRenderer();
renderer.render(component);
const rendered = renderer.getRenderOutput();
const myStylesInUse = stylesInUse(rendered);
return filterStyles(styles, myStylesInUse);
}
function stylesInUse(el) {
var arr = [];
function go(el) {
const { children, style } = el.props;
const childrenType = Object.prototype.toString.call(children);
style && arr.push(style)
if(childrenType === '[object Object]') {
go(children);
} else if(childrenType === '[object Array]') {
children.forEach(child => { go(child) });
}
}
go(el);
return arr;
}
function filterStyles(styles, compStyles) {
const arr = [];
for(let key in styles) {
const found = compStyles.find(item => item === styles[key]);
if(!found) arr.push(key)
}
return arr;
}
2) Component.js
import { unusedStyles } from './helpers';
const styles = StyleSheet.create({
one: {
color: 'one'
},
two: {
color: 'two'
},
three: {
color: 'three'
}
});
class Hello extends Component {
render() {
return (
<div style={styles.one}>
<div style={style.two}>Hello!</div>
</div>
)
}
}
// here you can test your styles
const myUnusedStyles = unusedStyles(<Hello />, styles)
// => ['three']
if(myUnusedStyles.length) {
console.log('UNUSED STYLES DETECTED', myUnusedStyles);
}
export default Hello

How to pass a context (this) in a Tracker.autorun function?

This class will display this = undefined. How to pass the context in?
class MeteorAccount implements IService{
constructor() {
Tracker.autorun(function () {
//observe
Meteor.userId());
console.log(this);//undefined
});
}
}
You have two options:
The Function.bind function:
class MeteorAccount implements IService{
constructor() {
Tracker.autorun(function() {
//observe
Meteor.userId());
console.log(this);//undefined
}.bind(this));
}
}
Or the arrow function:
class MeteorAccount implements IService{
constructor() {
Tracker.autorun(() => {
//observe
Meteor.userId());
console.log(this);//undefined
});
}
}
Edit
There's another option, I just don't like it, but it's how people used to do it before the arrow function (not sure why not the bind option):
class MeteorAccount implements IService{
constructor() {
var self = this;
Tracker.autorun(function() {
//observe
Meteor.userId());
console.log(self);//undefined
});
}
}

Dynamic Children Injection and Redux Binding

We are trying to inject dynamic children into a react app that is using React-Redux and Redux, and are experiencing an issue with the binding on the children props. I've distilled the problem into the following example code (JSFiddle). The issue is that the original rendered element updates just fine, but the dynamically injected portion does not. The strange thing is that the update is picked up in the redux store, and is fed to the props correctly.
const initialState = {
renderProperty: "Red Fish",
getChildrenProperty: "Blue Fish",
}
function MainReducer(state = initialState, action) {
switch (action.type) {
case 'PROBLEM_CHILD_INSIDE_RENDER':
return Object.assign({}, state, {
renderProperty: action.mutatedProperty
})
case 'PROBLEM_CHILD_INSIDE_GET_CHILDREN':
return Object.assign({}, state, {
getChildrenProperty: action.mutatedProperty
})
default:
return state
}
}
const store = Redux.createStore(MainReducer);
function mapStateToProps(state) {
return {
renderProperty : state.renderProperty,
getChildrenProperty : state.getChildrenProperty
}
}
function mapDispatchToProps(dispatch) {
return {
actions: Redux.bindActionCreators(actionCreators, dispatch)
};
}
class ProblemChild extends React.Component {
constructor() {
super();
this.childrenInjected = false;
this.state = {children: null};
}
/**
* Add children in setDynamicChildren() versus within render()
*/
componentDidMount() {
this.setDynamicChildren();
}
setDynamicChildren() {
this.setState({
children: this.getChildren()
});
}
getChildren() {
var me = this;
console.log(this);
return (
<div>
<br/>
<button style={{marginBottom: '10px'}}
onClick={me._handleGetChildrenAction.bind(me)} >UI State Change Action for prop in getChildren()</button>
<br/>
<span>prop in getChildren(): <b>{me.props.getChildrenProperty}</b></span>
</div>
)
}
render() {
var me = this,
childrenInjected = me.childrenInjected;
console.log(this.props);
if(me.state.children && !me.childrenInjected) {
return (
<div >
<button style={{marginBottom: '10px'}}
onClick={me._handleRenderAction.bind(me)} > UI State Change Action for prop in render()</button>
<br/>
<span>prop in render(): <b>{me.props.renderProperty}</b></span>
<br/>
{this.state.children} <br/>
</div>
)
}
else {
return (
<div>placeholder, yo</div>
)
}
}
_handleRenderAction() {
var me = this;
store.dispatch(actionForPropInsideRender('One Fish!'));
}
_handleGetChildrenAction() {
var me = this;
store.dispatch(actionForPropInsideGetChildren('Two Fish!'));
}
}
ProblemChild = ReactRedux.connect(mapStateToProps,mapDispatchToProps)(ProblemChild);
function actionForPropInsideRender(mutatedProperty) {
return {
type: 'PROBLEM_CHILD_INSIDE_RENDER',
mutatedProperty
}
}
function actionForPropInsideGetChildren(mutatedProperty) {
return {
type: 'PROBLEM_CHILD_INSIDE_GET_CHILDREN',
mutatedProperty
}
}
const actionCreators = {actionCreatorForPropInsideRender, actionCreatorForPropInsideGetChildren};
function actionCreatorForPropInsideRender(state, mutatedProperty) {
let newState = state.setIn(['uiState', 'renderProperty'], mutatedProperty),
nodeValue;
nodeValue = newState.getIn(['uiState', 'renderProperty']);
return newState;
}
function actionCreatorForPropInsideGetChildren(state, mutatedProperty) {
let newState = state.setIn(['uiState', 'getChildrenProperty'], mutatedProperty),
nodeValue;
nodeValue = newState.getIn(['uiState', 'getChildrenProperty']);
return newState;
}
ReactDOM.render(
<div>
<ReactRedux.Provider store={store}>
<ProblemChild />
</ReactRedux.Provider>
</div>,
document.getElementById('container')
);
setDynamicChildren() {
this.setState({
children: this.getChildren()
});
}
Is there a reason your intermixing redux state and local class state changes? From my experience this is asking for weird behaviour. I would swap this.setState for this.props.dispatch(action...) to update the redux store state. Do you still have issues when the complete state is now in redux? Otherwise a snapshot of redux dev tools state changes would be helpful in these cases.

Resources