Value not emitting from child to parent component using custom events - javascript

I have a problem with two web components created using lit-element v3.2.0 and I'm using custom events to emit the input value from the child component up to the parent component.
The form-input component is a reusable input that extracts the input value and emits it to the parent component with a custom event named "new-value" which is dispatched when the user writes in the field.
The form-container component contains the form-input, in here I'm binding the custom event "new-value" to a method called "updateInputValue" which should reassign the inputValue property with the emitted value from the child component, but instead is stuck with whatever value initialized in the parent constructor.
form-container.js
static get properties() {
return {
inputValue: { type: String },
items: { type: Array },
}
}
constructor() {
super()
this.inputValue = ''
this.items = []
}
render() {
return html`<form>
<h1>My form container</h1>
<form-input
#new-value=${this.updateInputValue}
fieldName="name"
id="name"
label="Name"
placeholder="Enter anything"
value="${this.inputValue}"
></form-input>
<button #click=${this.addNewItem} type="submit">Add</button>
<form-list .items="${this.items}"></form-list>
</form>`
}
updateInputValue(e) {
// Update input value with the value emitted from the form-input
this.inputValue = e.detail
}
addNewItem(e) {
// Add new item to the list
e.preventDefault()
console.log('add new item with the following value:', this.inputValue)
form-input.js
static get properties() {
return {
value: { type: String },
fieldName: { type: String },
label: { type: String },
placeholder: { type: String },
type: { type: String },
}
}
constructor() {
super()
}
render() {
return html`
<div>
<label for="name">${this.label}</label>
<input
#input=${this.dispatchEvent}
id="${this.fieldName}"
name="${this.fieldName}"
placeholder="${this.placeholder}"
type="${this.type || 'text'}"
value="${this.value}"
/>
</div>
`
}
dispatchEvent(e) {
// Emit the new value from the input to the parent component
const target = e.target
if (target) {
this.dispatchEvent(
new CustomEvent('new-value', {
detail: target.value,
})
)
}
}
Any help will be very much appreciated.

You are overwriting the dispatchEvent method and calling yourself. Rename the dispatchEvent Method and give it a meaningful name. It works perfectly.
<script type="module">
import {
LitElement,
html, css
} from "https://unpkg.com/lit-element/lit-element.js?module";
class FormInput extends LitElement {
static get properties() {
return {
value: { type: String },
fieldName: { type: String },
label: { type: String },
placeholder: { type: String },
type: { type: String },
};
}
render() {
return html`
<div>
<label for="name">${this.label}</label>
<input
#input=${this.changedInput}
id="${this.fieldName}"
name="${this.fieldName}"
placeholder="${this.placeholder}"
type="${this.type || 'text'}"
value="${this.value}"
/>
</div>
`
}
changedInput(e) {
// Emit the new value from the input to the parent component
console.log(e.target.value);
const myevent = new CustomEvent('my-event', {
bubbles: true,
composed: true,
detail: {
value: e.target.value
}
})
this.dispatchEvent(
myevent
);
}
}
class FormContainer extends LitElement {
static get properties() {
return {
name: {
inputValue: { type: String },
}
};
}
updateInputValue(e) {
console.log('received ' + e.detail.value);
this.inputValue = e.detail.value;
}
addNewItem(e) {
// Add new item to the list
e.preventDefault()
console.log('add new item with the following value:', this.inputValue);
}
render() {
return html`<form>
<h1>My form container</h1>
<form-input
#my-event="${this.updateInputValue}"
fieldName="name"
id="name"
label="Name"
placeholder="Enter anything"
value="${this.inputValue}"
></form-input>
<button #click=${this.addNewItem} type="submit">Add</button>
<form-list .items="${this.items}"></form-list>
</form>
`;
}
}
customElements.define("form-container", FormContainer);
customElements.define("form-input", FormInput);
</script>
<form-container></form-container>

Related

text input (input type="text") value is not updating after changing property using an event with LitElement library

The source code:
import { LitElement, html, css } from '../vendor/lit-2.4.0/lit-all.min.js';
export class SearchInput extends LitElement {
static get properties() {
return {
src: { type: String },
items: { type: Array }
}
};
static styles = css`
`;
constructor() {
super();
this.items = [
{ text: 'Hola' },
{ text: 'mundo!' }
];
this.selectedItem = null;
this.text = 'foo';
}
selectItem(item) {
this.selectedItem = item;
this.text = this.selectedItem.text;
}
render() {
return html`
<div class="control">
<input class="input" type="text" value="${this.text}">
<ul class="result-list">
${this.items.map((item) => html`<li #click="${this.selectItem(item)}">${item.text}</li>`)}
</ul>
</div>
`;
}
}
customElements.define('search-input', SearchInput);
The text input (input type="text") value is not updating after changing property (this.text) using an event (this.selectItem) with LitElement library.
I tried it in browser but there is no error in browser console.
I expect that input value update after changing property with the event.
Thanks for the question! There are a few minor issues resulting in the value not updating.
One issue is that this.text is not a reactive property, so changing it isn't scheduling a re-render. Fix is to add text to the static properties.
The second issue is that your event listener click handler is the result of calling this.selectItems(item) and not a function, fixed with: #click=${() => this.selectItems(item)}.
Bonus: You may want to change the value attribute expression to a property expression using the live directive, .value="${live(this.text)}". I suggested this because the native input browser element always updates its contents if you update the value property, but only updates before a user has interacted with it when updating the value attribute. And the live directive is useful to tell Lit to dirty check the live DOM value in the input element.
Your code with the minor fixes: https://lit.dev/playground/#gist=a23dfbcdfbfcfb7de28b1f7255aaa8ee
or running in StackOverflow:
<script type="module">
import { LitElement, html, live } from 'https://cdn.jsdelivr.net/gh/lit/dist#2/all/lit-all.min.js';
class SearchInput extends LitElement {
static get properties() {
return {
src: { type: String },
items: { type: Array },
text: { type: String }, // <- Added this to make `this.text` a reactive property.
}
};
constructor() {
super();
this.items = [
{ text: 'Hola' },
{ text: 'mundo!' },
{ text: 'click these' },
];
this.selectedItem = null;
this.text = 'foo';
}
selectItem(item) {
this.selectedItem = item;
this.text = this.selectedItem.text;
}
render() {
return html`
<div class="control">
<!-- live directive is needed because user can edit the value of the input.
This tells Lit to dirty check against the live DOM value. -->
<input class="input" type="text" .value="${live(this.text)}">
<ul class="result-list">
<!-- Click event is a function -->
${this.items.map((item) =>
html`<li #click="${() => this.selectItem(item)}">${item.text}</li>`)}
</ul>
</div>
`;
}
}
customElements.define('search-input', SearchInput);
</script>
<search-input></search-input>

Toggling button issue in polymer

Im trying to toggle the displaying of message using a button.
Below is my code.
class DisplayMessage extends PolymerElement {
// DO YOUR CHANGES HERE
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
<button on-click="toggle">Toggle Message</button> <br />
<template is="dom-if" if="{{user.authorise }}">
<br />
<span>I should now display message.</span>
</template>
`;
}
toggle() {
// DO YOUR CHANGES HERE
// hint: use set method to do the required changes
//console.log(this.user);
//this.user = !this.user
}
static get properties() {
return {
prop1: {
type: String,
value: 'user',
},
user: {
type: Object,
value: function () {
return { authorise: false}; // HINT: USE BOOLEAN VALUES TO HIDE THE MESSAGE BY DEFAULT
},
notify: true,
},
};
}
}
window.customElements.define('display-message', DisplayMessage);
I tried thinking for like hours, but couldn't solve. The requirement her is on clicking the button, the click handler toggle should change the value of authorize in user property to true. And on clicking again to false and so on. I need to use set method within toggle method. I'm not getting how to do this. Please help me on this.
Thanks in advance.
Why use a library/dependency for such a small component, that can be done with native code
<display-message id=Message name=Cr5>You are authorized</display-message>
<script>
customElements.define("display-message", class extends HTMLElement {
static get observedAttributes() {
return ["name", "authorized"]
}
connectedCallback() {
this.innerHTML = `<h2>Hello <span>${this.getAttribute("name")}</span></h2><button>Toggle message</button><br><div style=display:none>${this.innerHTML}</div>`;
this.querySelector('button').onclick = () => this._toggle();
}
_toggle(state) {
let style = this.querySelector('div').style;
style.display = state || style.display == "none" ? "inherit" : "none";
this.toggleAttribute("authorized", state);
console.log(Message.name, Message.authorized);
}
get name() { return this.getAttribute("name") }
set name(value) {
this.querySelector('span').innerHTML = value;
this.setAttribute("name", value);
}
get authorized() { return this.hasAttribute("authorized") }
set authorized(value) { this._toggle(value) }
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue) this[name] = newValue;
}
})
Message.name = "Cr5";
Message.authorized = true;
</script>
class DisplayMessage extends PolymerElement {
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
<button on-click="toggle">Toggle Message</button> <br />
<template is="dom-if" if="{{user.authorise}}">
<br />
<span>I should now display message.</span>
</template>
`;
}
toggle() {
if(this.user.authorise==false)
{
this.set('user.authorise', true);
}
else
{
this.set('user.authorise', false);
}
}
static get properties() {
return {
prop1: {
type: String,
value: 'user',
},
user: {
type: Object,
value: function () {
return { authorise: false };
},
},
};
}
}
window.customElements.define('display-message', DisplayMessage);

How do I access a value in a child component in vue

Vcode is in a child component.
data() {
return {
vcode: null,
};
},
I need to access this value in a parent component method.
verifyCode() {
const code = this.vcode
}
Attempting this returns undefined. How do I access this value?
Update
I tried the suggestions and I still get an undefined value
Input field on child component
<input
class="form-control mt-5"
v-model.trim="vcode"
:class="{'is-invalid' : $v.vcode.$error }"
#input="$v.vcode.$touch(), vcodenum = $event.target.value"
placeholder="Enter your 6 digit code"
/>
On the parent component I added the following where it references the child component
<step2 ref="step2" #on-validate="mergePartialModels" v-on:vcodenum="vcode = $event"></step2>
My method in the parent component
verifyCode() {
const code = this.vcode
console.log(code)
}
I still get undefined.
I also tried this:
Child component
<input
class="form-control mt-5"
v-model.trim="vcode"
:class="{ 'is-invalid': $v.vcode.$error }"
#input="$v.vcode.$touch(), onInput"
placeholder="Enter your 6 digit code"
/>
Props
props: {
value: {
type: [String, Number, Boolean],
default: "",
},
},
method
onInput(e) {
this.$emit('on-input', e.target.value)
},
Parent
<step2 ref="step2" #on-validate="mergePartialModels" :value="vcode" #on-input="handleInput"></step2>
data() {
return {
vcode: null
};
},
method
handleInput(value) {
this.vcode = value
console.log(this.vcode)
},
The value ends up outputting null.
If I use the v-bind I get this error:
:value="value" conflicts with v-model on the same element because the latter already expands to a value binding internally
You can listen to the child's input event and send the value to the parent.
// InputComponent.vue
<input :value="value" #input="onInput" />
....
props: {
value: {
type: [String, Number, Boolean] // Add any custom types,
default: ''
}
},
methods: {
onInput(e) {
this.$emit('on-input', e.target.value)
}
}
// Parent.vue
<InputComponent :value="vCode" #on-input="handleInput" />
....
data() {
return {
vcode: null
}
},
methods: {
handleInput(value) {
this.vode = value
}
}

React.JS Typescript - OnChange says "A component is changing a controlled input of type text to be uncontrolled in OnChange" for State Object

Good day,
I'm new with react.js I'm trying to create a basic data binding using onChange of the input. The problem is, I'm assigning to object with it's properties. Not directly to the property.
Now I'm receiving the error Warning: A component is changing a controlled input of type text to be uncontrolled. when I type-in a character in my inputs.
Here's my code:
interface IProps { }
interface IFloorInfo {
id: number
name: string,
type: string,
condition: string
}
interface IFloorInfoState {
floor: IFloorInfo
}
export default class Floors extends React.Component<IProps, IFloorInfoState> {
state: IFloorInfoState
constructor(props: any){
super(props)
this.state = {
floor: {
id: 0,
name: '',
type: '',
condition: ''
}
}
}
...
render() {
return (
<div>
<input type="text" value={this.state.floor.name} onChange={(e)=>this.inputChanges(e)} />
<input type="text" value={this.state.floor.type} onChange={(e)=>this.inputChanges(e)} />
<input type="text" value={this.state.floor.condition} onChange={(e)=>this.inputChanges(e)} />
</div>
)
}
}
Now this is my inputChanges method that detects if there's a changes in the input
inputChanges = (e:any) => {
this.setState({ floor: e.target.value });
}
Thank you in advance.
The problem is with your following code. According to this code, your state will be {floor: "input value"}
inputChanges = (e:any) => {
this.setState({ floor: e.target.value });
}
But what you actually want is
inputChanges = (e:any) => {
// copying all values of floor from current state;
var currentFloorState = {...this.state.floor};
// setting the current input value from user
var name = e.target.name;
currentFloorState[name] = e.target.value;
this.setState({ floor: currentFloorState });
}
As for multiple properties:
You can add name property to your element and use it in your changeHandler
render() {
return (
<div>
<input type="text" value={this.state.floor.name} name="floor" onChange={(e)=>this.inputChanges(e)} />
<input type="text" value={this.state.floor.type} name="type" onChange={(e)=>this.inputChanges(e)} />
</div>
)
}
For demo, you can refer this https://codesandbox.io/s/jolly-ritchie-e1z52
In this code, you don't specify which property that you want to bind.
inputChanges = (e:any) => {
this.setState({ floor: e.target.value });
}
What you can do, is something like this.
inputChanges = (e:any) => {
this.setState({
...this.state,
floor: { ... this.state.floor, [e.currentTarget.name]: e.currentTarget.value}
})
}
Basically, you're binding whatever property that matches inside your this.state.floor object.

How can I modify event payload within a directive?

I have custom input component that uses v-model directive, so on input it emits input event with value, and v-mask directive, that modifies value by conforming current input value to the mask and emitting another input event with modified value. However this approach fires two input events, and toggling two model changes - one raw, and one masked. Can I modify existing input event value within a directive?
const maskDirective = (() => {
const state = new Map();
return {
bind: (el) => {
const element = el instanceof HTMLInputElement ? el : el.querySelector('input');
const textMaskInput = createTextMaskInputElement({
inputElement: element,
mask: TextMasks.phoneNumber,
});
state.set('element', element);
state.set('input', textMaskInput);
},
update: () => {
const textMaskInput = state.get('input');
const element = state.get('element');
const {
state: { previousConformedValue },
} = textMaskInput;
textMaskInput.update();
// otherwise there's call stack size exceeded error, because it constantly fires input event from component, catches it, and fires event from directive
if (previousConformedValue !== element.value) {
const event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
}
},
};
})();
<template>
<div
:class="{ 'is-disabled': disabled }"
class="c-base-input"
>
<input
ref="control"
v-bind="$attrs"
:class="{
'has-leading-icon': $slots['leading-icon'],
'has-trailing-icon': $slots['trailing-icon'],
'has-prepend-content': $slots['prepend'],
'has-append-content': $slots['append'],
'has-value': value !== null,
}"
:disabled="disabled"
:value="value"
:type="type"
class="c-base-input__control"
#input="onInput($event.target.value)"
>
<div
v-if="$slots['leading-icon']"
class="c-base-input__icon is-leading"
>
<slot name="leading-icon" />
</div>
<div
v-if="$slots['trailing-icon']"
class="c-base-input__icon is-trailing"
>
<slot name="trailing-icon" />
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
type: {
type: String,
default: 'text',
validator: value => ['text', 'tel', 'email', 'password'].indexOf(value) !== -1,
},
},
methods: {
onInput(value) {
if (value === '') {
this.$emit('input', null);
} else {
this.$emit('input', value);
}
},
},
};
</script>

Categories

Resources