How to determine Material-UI toggle ON/OFF state from DOM? - toggle

Inspecting toggles on http://www.material-ui.com/#/components/toggle there is an input tag of type 'checkbox' in DOM. 'Toggled by default' has attribute 'checked' but when I turn it to OFF state then attributes in underling 'input' tag does not change and 'checked' is still present.
How to determine toggle ON/OFF state from DOM?
p.s.
I am writing automated functional tests using Selenium web driver.

If you can assign a custom data-* prop to each toggle, try this:
handleToggle = (event) => {
// here's your checked Value
event.target.getAttribute('data-isToggled')
}
// ...React boilerplate
<Toggle
label="Toggled by default"
onToggle={this.handleToggle}
data-isToggled={this.state.Toggled}
toggled={this.state.Toggled}
/>

You don't directly check toggle state from the DOM. You have to use javascript callbacks to determine if a toggle switch is on or off.
Class ToggleComponent Example
export default class ToggleComponent extends React.Component {
constructor(props) {
super(props);
this.state = {Toggled: true};
}
handleToggle() {
this.setState({Toggled: !this.state.Toggled});
console.log(this.state.Toggled)
}
render() {
return (
<div>
<Toggle
label="Toggled by default"
defaultToggled={this.state.Toggled}
onToggle={this.handleToggle.bind(this)}
toggle={this.state.Toggled}
/>
</div>
);
}
}
Look at: https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html

Related

How to add meta data to a specific word and turn it into a custom type while using decorators with a regex strategy?

I’m struggling at modeling a certain use case of mine with Draft.js.
I’m highlighting certain words/phrases in the editor with a composite decorator by using a regex strategy.
What I’m trying to archive is: if a user clicks on a decorated word, I want to toggle it’s state and store it somehow.
How would I do this? Any clues?
At a higher level, it’s basically a way to ignore certain decorated words, even if they match the regex strategy
I thought that maybe entities could help me do the job here, they would allow me to store such meta information (ex: ignore) on the decorated word, right?
When I faced the similar issue I used the store of the rendered component for storing a condition.
You used a functional component here:
renderAnnotation(type){
return (props)=> {
return (<span className={type} onClick={this.ignoreDecoratedWord.bind(this, props)}>{props.children}</span>);
};
}
You can change it and use standard react component:
renderAnnotation(type){
return (props)=> {
return (<HighlightedWord type={type} {...props} />);
};
}
The HighlightedWord component is:
class HighlightedWord extends React.Component {
constructor() {
super();
this.state = { enable: true }
}
toggleStatus = () => {
this.setState({ enable: !this.state.enable });
}
render() {
return (
<span className={this.state.enable ? this.props.type : ''} onClick={this.toggleStatus}>{this.props.children}</span>
)
}
}
We toggle the highlighting status after the click event.
Check this demo.

Lifecycle for controlled textinputs

I was wondering if the mobx cycle is triggered on observable change only in stores outside of react components in HOClass operation
because there is a top level Provider to mobx stores which is triggering a react component re-render.
ReactDOM.render(<Provider { ...stores } >
<Routes />
</Provider>,
But does that mean I can't add observables directly to react components?
if so that is a bit unfortunate.
#observer
export default class Profile extends React.Component {
#observable name;
componentWillMount() {
this.name = this.props.name;
}
txtChange = (e) => {
this.name = e.target.value;
}
render() {
return (
<div>
<input type="text" onChange={this.txtChange} value={this.name}/>
</div>
)
}
for some reason on my app I cannot update the observable name. On some apps using mobx however I can. Why am I unable to change the value of this and trigger a react state update if the observable is attached to the React.Component itself?
Here I have forms updating in controlled observable fashion but when Provider 'ing my router on my current project under this setup no react rerender is triggered.
https://codepen.io/cheapsteak/pen/wzdvzQ
Help anyone?
There (was) a typo: the function is called txtChange but the component references this.txthange instead. Either way, here's a fixed working example: https://codepen.io/justnobody/pen/RxJmmY?editors=1010

Wrapping a FormControl in Angular (2+)

I'm trying to create a custom form control in Angular (v5). The custom control is essentially a wrapper around an Angular Material component, but with some extra stuff going on.
I've read various tutorials on implementing ControlValueAccessor, but I can't find anything that accounts for writing a component to wrap an existing component.
Ideally, I want a custom component that displays the Angular Material component (with some extra bindings and stuff going on), but to be able to pass in validation from the parent form (e.g. required) and have the Angular Material components handle that.
Example:
Outer component, containing a form and using custom component
<form [formGroup]="myForm">
<div formArrayName="things">
<div *ngFor="let thing of things; let i = index;">
<app-my-custom-control [formControlName]="i"></app-my-custom-control>
</div>
</div>
</form>
Custom component template
Essentially my custom form component just wraps an Angular Material drop-down with autocomplete. I could do this without creating a custom component, but it seems to make sense to do it this way as all the code for handling filtering etc. can live within that component class, rather than being in the container class (which doesn't need to care about the implementation of this).
<mat-form-field>
<input matInput placeholder="Thing" aria-label="Thing" [matAutocomplete]="thingInput">
<mat-autocomplete #thingInput="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{ option }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
So, on the input changing, that value should be used as the form value.
Things I've tried
I've tried a few ways of doing this, all with their own pitfalls:
Simple event binding
Bind to keyup and blur events on the input, and then notify the parent of the change (i.e. call the function that Angular passes into registerOnChange as part of implementing ControlValueAccessor).
That sort of works, but on selecting a value from the dropdown it seems the change events don't fire and you end up in an inconsistent state.
It also doesn't account for validation (e.g. if it's "required", when a value isn;t set the form control will correctly be invalid, but the Angular Material component won't show as such).
Nested form
This is a bit closer. I've created a new form within the custom component class, which has a single control. In the component template, I pass in that form control to the Angular Material component. In the class, I subscribe to valueChanges of that and then propagate the changes back to the parent (via the function passed into registerOnChange).
This sort of works, but feels messy and like there should be a better way.
It also means that any validation applied to my custom form control (by the container component) is ignored, as I've created a new "inner form" that lacks the original validation.
Don't use ControlValueAccessor at all, and instead just pass in the form
As the title says... I tried not doing this the "proper" way, and instead added a binding to the parent form. I then create a form control within the custom component as part of that parent form.
This works for handling value updates, and to an extent validation (but it has to be created as part of the component, not the parent form), but this just feels wrong.
Summary
What's the proper way of handling this? It feels like I'm just stumbling through different anti-patterns, but I can't find anything in the docs to suggest that this is even supported.
Edit:
I've added a helper for doing just this an angular utilities library I've started: s-ng-utils. Using that you can extend WrappedFormControlSuperclass and write:
#Component({
selector: 'my-wrapper',
template: '<input [formControl]="formControl">',
providers: [provideValueAccessor(MyWrapper)],
})
export class MyWrapper extends WrappedFormControlSuperclass<string> {
// ...
}
See some more documentation here.
One solution is to get the #ViewChild() corresponding to the inner form components ControlValueAccessor, and delegating to it in your own component. For example:
#Component({
selector: 'my-wrapper',
template: '<input ngDefaultControl>',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NumberInputComponent),
multi: true,
},
],
})
export class MyWrapper implements ControlValueAccessor {
#ViewChild(DefaultValueAccessor) private valueAccessor: DefaultValueAccessor;
writeValue(obj: any) {
this.valueAccessor.writeValue(obj);
}
registerOnChange(fn: any) {
this.valueAccessor.registerOnChange(fn);
}
registerOnTouched(fn: any) {
this.valueAccessor.registerOnTouched(fn);
}
setDisabledState(isDisabled: boolean) {
this.valueAccessor.setDisabledState(isDisabled);
}
}
The ngDefaultControl in the template above is to manually trigger angular to attach its normal DefaultValueAccessor to the input. This happens automatically if you use <input ngModel>, but we don't want the ngModel here, just the value accessor. You'll need to change DefaultValueAccessor above to whatever the value accessor is for the material dropdown - I'm not familiar with Material myself.
I'm a bit late to the party but here is what I did with wrapping a component which might accept formControlName, formControl, or ngModel
#Component({
selector: 'app-input',
template: '<input [formControl]="control">',
styleUrls: ['./app-input.component.scss']
})
export class AppInputComponent implements OnInit, ControlValueAccessor {
constructor(#Optional() #Self() public ngControl: NgControl) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
control: FormControl;
// These are just to make Angular happy. Not needed since the control is passed to the child input
writeValue(obj: any): void { }
registerOnChange(fn: (_: any) => void): void { }
registerOnTouched(fn: any): void { }
ngOnInit() {
if (this.ngControl instanceof FormControlName) {
const formGroupDirective = this.ngControl.formDirective as FormGroupDirective;
if (formGroupDirective) {
this.control = formGroupDirective.form.controls[this.ngControl.name] as FormControl;
}
} else if (this.ngControl instanceof FormControlDirective) {
this.control = this.ngControl.control;
} else if (this.ngControl instanceof NgModel) {
this.control = this.ngControl.control;
this.control.valueChanges.subscribe(x => this.ngControl.viewToModelUpdate(this.control.value));
} else if (!this.ngControl) {
this.control = new FormControl();
}
}
}
Obviously, don't forget to unsubscribe from this.control.valueChanges
I have actually been wrapping my head around this for a while and I figured out a good solution that is very similar (or the same) as Eric's.
The thing he forgot to account for, is that you can't use the #ViewChild valueAccessor until the view has actually loaded (See #ViewChild docs)
Here is the solution: (I am giving you my example which is wrapping a core angular select directive with NgModel, since you are using a custom formControl, you will need to target that formControl's valueAccessor class)
#Component({
selector: 'my-country-select',
templateUrl: './country-select.component.html',
styleUrls: ['./country-select.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: CountrySelectComponent,
multi: true
}]
})
export class CountrySelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges {
#ViewChild(SelectControlValueAccessor) private valueAccessor: SelectControlValueAccessor;
private country: number;
private formControlChanged: any;
private formControlTouched: any;
public ngAfterViewInit(): void {
this.valueAccessor.registerOnChange(this.formControlChanged);
this.valueAccessor.registerOnTouched(this.formControlTouched);
}
public registerOnChange(fn: any): void {
this.formControlChanged = fn;
}
public registerOnTouched(fn: any): void {
this.formControlTouched = fn;
}
public writeValue(newCountryId: number): void {
this.country = newCountryId;
}
public setDisabledState(isDisabled: boolean): void {
this.valueAccessor.setDisabledState(isDisabled);
}
}
NgForm is providing an easy way to manage your forms without injecting any data in a HTML form. Input data must be injected at the component level not in a classic html tag.
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)>...</form>
Other way is to create a form component where all the data model is binded using ngModel ;)

React simple checkbox component - Warning: setState(...): Can only update a mounted or mounting component

I have a simple checkbox component:
import React, { Component } from 'react';
class Checkbox extends Component {
constructor(props) {
super(props);
this.state = { isChecked: false };
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
}
render() {
return (
<div className="checkbox">
<input type="checkbox" checked={this.state.isChecked} onChange={this.handleCheckboxChange} />
Checkbox
</div>
);
}
handleCheckboxChange(e) {
this.setState({ isChecked: e.target.checked });
}
}
export default Checkbox;
However, toggling the checkbox produces the following error:
Warning: setState(...): Can only update a mounted or mounting
component. This usually means you called setState() on an unmounted
component. This is a no-op. Please check the code for the Checkbox
component.
Am I missing something when creating controlled components?
What happens when you move the handlecheckbox method outside of the render (inbetween constructor and render)?
Figured it out, it was a problem with react-hot-loader. Apparently saving the value of this doesn't work in the constructor with react-hot-loader. The fix is to manually enable the transform-es2015-classes plugin in your babelrc. I had this issue previously but it went away so I assumed it was fixed, but it randomly popped up again.

React JS - Composing generic form with dynamic childrens

I am just a beginner in reactjs. I feel so good about using it, but I have been stuck for a while on a concept that I wanna implement, that would solve me whole lot of other issues.
Concept
I want to create a form component that can have dynamic children passed to it. It won't be a specific form component like LoginForm, ContactForm etc. Rather it would just be Form.
Approach 1
class LoginPage extends Rect.Component {
constructor(props) {
super(props)
this.submit = this.submit.bind(this);
this.validate = this.validate.bind(this);
this.reset = this.reset.bind(this);
}
submit(...args) {
this.refs.form.submit(args);
}
validate(...args) {
this.refs.form.validate(args);
}
reset(...args) {
this.refs.form.reset(args);
}
render() {
return (
<LoginPage>
<Form ref="form" onSubmit={this.submit}>
<Input value="email" pattern="/email_pattern/" onValidate={this.validate} />
<Input value="password" pattern="/password_pattern/" onValidate={this.validate} />
<Button value="submit" action={this.submit} />
<Button value="reset" action={this.reset} />
</Form>
</LoginPage>
}
}
Input onChange calls the validate function that just passes on the args to the Form's validate function. For the Form to know if all it's children's are validated. I pass isValid and targetInputComponent as args to the form.
Button Submit onClick calls the submit function likewise and LoginPage (acts as middleware) passes the call to the Form component. Form check it's state for inValid inputs etc.
Button Reset onClick call is passed to the Form component likewise. But how do the form handle this reset functionality? Input's value is controlled by LoginPage. Form can't change the input's value.
Aproach 2
What I did was add the input's data and validity to the LoginPage state. Now both Form and Inputs just call the Login Page to update it's state. Form and Inputs components are using this state as prop.
class LoginPage extends React.Component {
constructor(props) {
super(props)
this.state = {
formSchema: this.initSchema()
}
this.validateField = this.validateField.bind(this);
this.submitForm = this.submitForm.bind(this);
}
initSchema() {
const formSchema = {
email: {
isValid: false,
value: ''
},
password: {
isValid: false,
value: ''
},
password2: {
isValid: false,
value: ''
}
}
return formSchema;
}
validateField(dataObj, targetComponent) {
const formSchema = Object.assign(this.state.formSchema, dataObj);
this.setState({ formSchema })
}
submitForm(isSuccess) {
console.log(isSuccess);
console.log('Form Submit: ', this.state.formSchema);
throw new Error('Submition Error');
}
reset() {
// Loop through each object in formSchema and set it's value to empty, inputs will validate themselves automatically.
}
render() {
return <div>
<Form ref="formLogin" className="auth-form" onSubmit={this.submitForm} formSchema={this.state.formSchema}>
<h1>
Switch Accounts
</h1>
<Input type="email" name="email" onValidate={this.validateField} value={this.state.formSchema.email.value}
isValid={this.state.formSchema.email.isValid}/>
<Input type="password" name="password" onValidate={this.validateField} value={this.state.formSchema.password.value}
isValid={this.state.formSchema.password.isValid}/>
<Button value="Submit" type="submit" />
</Form>
</div>
}
}
Problem
This approach is making the LoginPage Component quite messy. How will the LoginPage component will handle the forms if I have more than 1 form on the page? There will be even more features to LoginPage like Lists, Grid, Modals etc. This LoginPage shouldn't Handle these situations. Form should be responsible for all the inputs, submition, etc functionality. This form component should be generic to all types of forms. I don't want to create feature forms like LoginForm, ContactForm, etc.
The solution to this issue will aid me a lot in whole lot of other components.
Your approach 2 is the standard way of handling this problem.
Why wouldn't you create a separate <LoginForm>? Doing so can abstract the fields and validation away from other unrelated functionalities on your page.
If later you need a <ContactForm> or any other type of form, it will have different fields and different validation logic, meaning you'll need to build it out regardless.
Short of using something like redux, you're on the right track.
There are numerous ways to make ‘generic’ forms. But in my opinion it is best to keep the forms seperatly.
The reason why I say this, is because one way or another, you still have to implement various validation condition for most fields, such as e-mailaddress or what ever you will use.
I think the best approach is to make the components individual. For example: make a TextField component that handles input stuff like validation. Maybe a Button component for submit and callbacks.
So my advise: keep the forms seperatie. No need to overthink and lose useful time in trying to make things ‘pretty’.
Goodluck!