I am trying to make a memo pad where a user can write in a textarea and save it.
When the page opens, I want the same text to remain in the textarea.
I don't want to use the onChange event because it gets rendered with every input
So I am trying to use onSubmit and useEffect.
I am new to react and trying to understand hooks. But after reading all the documents I am still lost.
I am using Ionic framework and below is my code,
<IonCard>
<form className="memo" onSubmit={handleSubmit(text)}>
<IonItemDivider><IonLabel>관리자용 메모판</IonLabel></IonItemDivider>
<IonItem>
<IonTextarea id="memoTextarea" rows={28} value={text} name="memoTextarea" ref={register({})} ></IonTextarea>
</IonItem>
<IonButton type="submit" fill="solid">저장</IonButton>
</form>
</IonCard>
Can anybody help me, how will I implement the useEffect code ?
useEffect(()=>{
}, []);
What do I put inside handleSubmit()?
react-hook-form uses uncontrolled components by default. Uncontrolled means you don't have to control the state yourself, the components handle it for you (in this case IonTextarea). It also means that your parent component doesn't re-render because it doesn't have to reflect the changes from the dependency state since it doesn't have any.
You can make sure the component is uncontrollable by not initializing the value in the input component. You can confirm that your component doesn't re-render by logging in the render method.
<IonTextarea
id="memoTextarea"
rows={28}
// comment the following line will make the component uncontrollable
// and your component never re-renders when you type
// value={text}
name="memoTextarea"
ref={r => register(r)}
></IonTextarea>
Live Demo
Related
I am attempting to create a search bar using a custom HTML component for predictive text input. The way this component is built, it generates several plain HTML children that I need to act on to get full features. Specifically, I need to execute a blur action on one of the generated elements when the user presses escape or enter.
I got it to work using a ref on the custom component and calling getElementsByClassName on the ref, but using getElementsByClassName does not seem like the best solution. It pierces through the virtual and has odd side effects when testing.
This is a snippet of the component being rendered:
<predictive-input id='header-search-bar-input' type='search'
value={this.state.keywords}
ref={(ref: any) => this.predictiveInput = ref}
onKeyDown={(e: React.KeyboardEvent<any>) => this.handleKeyDown(e)}>
</predictive-input>
and the keyDown handler:
private handleKeyDown(e: React.KeyboardEvent<any>) {
// must access the underlying input element of the kat-predictive-input
let input: HTMLElement = this.predictiveInput.getElementsByClassName('header-row-text value')[0] as HTMLElement;
if (e.key === 'Escape') {
// blur the predictive input when the user presses escape
input.blur();
} else if (e.key === 'Enter') {
// commit the search when user presses enter
input.blur();
// handles action of making actual search, using search bar contents
this.commitSearch();
}
}
The element renders two children, one for the bar itself and one for the predictive dropdown. The classes of the underlying in the first are 'header-row-text' and 'value', so the element is correctly selected, but I am worried that this is violating proper React style.
I am using React 16.2, so only callback refs are available. I would rather avoid upgrading, but if a 16.3+ solution is compelling enough, I could consider it.
If you don't have any control over the input then this is the best approach in my opinion.
It's not ideal, but as you're stuck with a 3rd party component you can only choose from the methods that are available to you. In this case, your only real options are to find the element based on its class, or its position in the hierarchy. Both might change if the package is updated, but if I had to choose which would be more stable, I'd go for className.
Here is the link for an example of the issue I will attempt to describe. In the chips autocomplete example, click the text box to select a new fruit.
Now, before clicking anywhere else, click again on the text box as you did before.
This should result in no options showing up. The issue here is that the user must either begin keying in a new selection or first click another element in the window before matchip will show the options to choose from. I am wondering if there is a way to fix this issue. I would like a user to be able to choose a selection from the list and then immediately click the text box as they had before and make a new selection.
I'm using mat-chip-list inside an outer *ngFor iterating over a FormArray.
Here is what I'have done. It's pretty efficient :
<input
#validatorInput
#operationTrigger="matAutocompleteTrigger"
[formControl]="contactCtrl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
(blur)="contactCtrl.setValue(''); validatorInput.value='';"
(click)="contactCtrl.setValue(''); validatorInput.value=''; operationTrigger.openPanel()">
The trick is
Always clear your html input and your (shared) formControl with an empty and not null value each time the blur and click events occur.
Do NOT do this 'clear' on the input focus event. (Because when you delete the last chip, the input is auto-focus and you will have the famous Expression has changed after it was checked.
Call operationTrigger.openPanel(); when the user click on the input
Setting contactCtrl.setValue(''); allows your autocomplete panel to be automatically opened when you call operationTrigger.openPanel()
Setting validatorInput.value=''; is just a way to properly sync your formControl with the html input to ensure a good UX/UI behavior.
Inside my formArray, the formControl is the same for all the inputs but it does not matter since the user can only manipulate one input at a given time
Since you didn't post your code and you mention the example on the material site I'm going to do it as a fork of the stackblitz example they have on their site.
But this will allow you to open the autocomplete panel again despite having had the cursor there and choosing an option previously.
// Using MatAutocompleteTrigger will give you access to the API that will allow you to
// to open the panel or keep it open
...
#ViewChild(MatAutocompleteTrigger, {static: false}) trigger: MatAutocompleteTrigger;
...
ngAfterViewInit() {
fromEvent(this.fruitInput.nativeElement, 'click')
.pipe(
tap(() => {
this.trigger.openPanel()
})
).subscribe()
}
Link to the full stackblitz:
https://stackblitz.com/edit/angular-sb38ig
Am building a Wordpress plugin and am Using Vue Js.
I set up a variable that will be set to true on a button click
Then another div which is a modal that will remaining hidden with v-if as long as the variable is false. The is also a form in the div modal(pop up)
The problem is that once the value changes, on button pressed, the form submits immediately.
This has been happening but I usually ignore it because there was always a required field which will prevent the form from submitting automatically.
Vue.js Data object
{
selected_to_show : false,
}
The modal div
<div v-if="selected_to_show === true" class='mp-modal'>
<form on:submit.prevent="xhrSubmit()">
<form>
</div>
<button v-on:click="selected_to_show = true"></button>
This works but once the modal opens, the form submits immediately.
Note: There is only two button elements in the form where are all set to type="button"
The target is to prevent the form from submitting automatically when shown
If any one still uses this approach to re-render vue apps, my advice is, don't.
The best way to re-render the app is by doing
this.forceUpdate();
This will re-render the vue app instead of modifying data properties of the vue instance which are utilized during rendering.
However, don't overuse it.
Most times when your view is not re-rendering naturally, its probably because you are doing something wrong.
I am new to Angular and have run into a problem that seems to have a javascript work around but they aren't very elegant.
I have a model with an array property. I ngfor the list property to build some html selection options. This is all working nicely. The problem comes when I am trying to set default value...the html elements don't have a load event.
I tried numerous html elements and they don't appear to have a load event either but I certainly could be doing it wrong.
I have seen a solution to put javascript tag right after the html and I could do that but I was really looking for a more elegant way in Angular.
I saw this SO post and thought that was my answer but there is a warning given that I agree with and thus it doesn't appear to be a good solution.
Regardless I tried it just to see if it would work and I got:
Failed to execute 'setAttribute' on 'Element': '{{loadDefaults()}}' is not a valid attribute name
<span {{loadDefaults()}} ></span>
So how can I fire an AS2 function in the component to load the default values?
HTML (btw this is NOT a full page load so there is no body tag):
<tr>
<td *ngFor="let loc of locOptions;">
<span>{{loc.text}}</span>
<input type="radio" name="radiogroup" [value]="loc.value" (change)="onSelectionChange(loc.value)">
</td>
</tr>
Edit
I thought perhaps mistakenly that ngoninit would fire too soon...before the html elements are rendered.
So perhaps what is being suggested is that I add a boolean is default to the model and bind THAT as the element is rendered.
In your ngonit function set this.locOptions to your default values. The value can be changed later on in any function and the change will be reflected in the view. Hope this helps you.
You should use ngOnInit to init you data, and call retrieve your data from your component :
defaults : any;
ngOnInit {
this.defaults = loadDefaults();
}
loadDefaults() {
//get data
}
HTML :
<span>{{defaults}}</span>
I am trying to use Material design lite with an Ember.js application and got the form working somehow. However, when the user navigates from one page to another page containing the form or inputs, the inputs do not seem to behave as expected. For an example here, when the page loads first time to home page, input works fine but when we switch between sign-in and home pages, inputs fallbacks to basic form and material design animation is lost.
Not sure if this issue is related to Ember.js or Material design but any help would be highly appreciated.
MDL requires elements to be initialized to get special effects such as buttons with ripples, or animated input boxes. They are initialized by default on page load, but elements inserted by views or components will not be initialized. The simplest approach is to initialize them on didInsertElement.
A more general approach would be a mixin which handles this for you, as in:
// mixins/mdl-button.js
export default Ember.Mixin.create() {
initializeMdlButtons: function() {
var buttons = this.get('element').querySelectorAll('.mdl-button');
[].forEach.call(buttons, button => componentHandler.upgradeElement(button));
}.on('didInsertElement')
Then in your component using buttons
import MdlButton from 'app/mixins/mdl-button';
export default Ember.Component.extend(MdlButton, {
...
});
Or, you could apply this to all components with
Ember.Component.reopen(MdlButton);
You will need to create handlers to initialize the required JS for each MDL component. You have two possibilities:
Use the JavaScript that comes with MDL, though it will become toublesome on some of the components.
Implement JS on your own per component, and use ideas of the JS internally in MDL.
I used 2. This is why I have written a ember-addon specifically to create ember components out of MDL.
It's pretty simple.
ember install ember-mdl
Demo / docs: http://peec.github.io/ember-mdl/
Example of implementation is in the dummy app
Or you can just do componentHandler.upgradeDom() on didInsertElement. Which based on their documentation
Searches existing DOM for elements of our component type and upgrades
them * if they have not already been upgraded.
initializeItems: function () {
componentHandler.upgradeDom();
}.on('didInsertElement')
Thanks #torazaburo for your suggestation. I had to modify mixin to get it working. In my case i have textfield input and needed to modify the mixing. Here is my solution if someone still needs.
// app/mixins/textfield-support.js
import Ember from 'ember';
export default Ember.Mixin.create({
initializeMdlTextfield: function() {
componentHandler.upgradeElement(this.get('element'), 'MaterialTextfield');
}.on('didInsertElement')
});
Then we can extend the mixing in our component as below.
// app/components/mdl-textfield-input.js
import Ember from 'ember';
import layout from '../templates/components/mdl-textfield-input';
import mdlTextfield from '../mixins/textfield-support';
export default Ember.Component.extend(mdlTextfield, {
tagName : 'div',
attributeBindings : ['disabled', 'type', 'name' ],
hasFloatingLabel : false,
containerClassNames : '',
labelText : null,
classNames : ['mdl-textfield', 'mdl-js-textfield'],
classNameBindings: [
'hasFloatingLabel:mdl-textfield--floating-label',
'containerClassNames'
],
layout
});
Component template would look something like this.
// app/templates/components/mdl-textfield-input.hbs
{{yield}}
{{input id=name value=value type=type disabled=disable classNames="mdl-textfield__input"}}
<label class="mdl-textfield__label" for={{name}}>{{labelText}}</label>
<span class="mdl-textfield__error">{{error}}</span>
And use this component as below.
{{mdl-textfield-input
name='username'
value=model.username
labelText='Username'
hasFloatingLabel=true
type='text'
containerClassNames ='mdl-cell--12-col'
}}