This is a class component that is using state and setState, but i would like to use useState. Also, aside from being able to use a functional component rather than a class base component, what are the benefits?
import React, {Component} from "react"
class Toggler extends Component {
state = {
on: this.props.defaultOnValue
}
toggle = () => {
this.setState(prevState => {
return {
on: !prevState.on
}
})
}
static
defaultProps = {
defaultOnValue: false
}
render() {
return (
<div>
{this.props.render(this.state.on, this.toggle)}
</div>
)
}
}
export default Toggler
So far...
import React, {useState} from "react"
function Toggler() {
const [on, setOn] = useState(this.props.defaultOnValue)
toggle = () => {
setOn( {/* what should go here? */} )
}
static
defaultProps = {
defaultOnValue: false
}
return (
<div>
{this.props.render(on, toggle)}
</div>
)
}
export default Toggler
Toggling your state value is as simple as inverting the current value returned from useState.
Functional components receive the props passed to them as an argument, so no need to use this.props. Also, to set static properties on a functional component you can just set them on the function itself.
function Toggler(props) {
const [on, setOn] = useState(props.defaultOnValue)
const toggle = () => {
setOn(!on)
}
return <div>{props.render(on, toggle)}</div>
}
Toggler.defaultProps = {
defaultOnValue: false
}
export default Toggler
In terms of benefits, it's up to you to decide whether you think it's worth it for your case. There's no specific benefit to using useState rather than a class based component, however if you want to start using other hooks, or integrate with third party libraries using hooks, you may wish to convert some of your components to functional ones where necessary.
Related
I'm using #types/xrm and attempting to test a method call with sinon. Unfortunately I am hitting quite a few issues due to the complex nature of the return and call I need to mock. I can find really simple examples of sinon stubbing or spying on calls, but nothing more complex than that.
I have the following simple code:
export class AccountForm {
static Fields = class {
static PrimaryContact = "primarycontactid";
static Name = "name";
}
public onLoad(context: Xrm.Events.EventContext): void {
// Get the form context
const formContext = context.getFormContext();
// Get the attributes required
const primaryContact = formContext.getAttribute(AccountForm.Fields.PrimaryContact);
const name = formContext.getAttribute(AccountForm.Fields.Name);
// Add our onchange events
primaryContact.addOnChange(this.onChangePrimaryContact);
name.addOnChange(this.onChangeName);
}
public async onChangePrimaryContact(context: Xrm.Events.EventContext): Promise<void> {
alert("Do something");
}
public async onChangeName(context: Xrm.Events.EventContext): Promise<void> {
alert("Do something else");
}
}
I want to test that an onchange event has been registered to both fields. Ideally, I'd like to check it's the RIGHT onchange, but I'll settle with the fact that it's been called once.
The "easy" way has been to check that the addOnChange method was called twice, this is as below:
import {AttributeMock, XrmMockGenerator} from "xrm-mock";
import * as sinon from "sinon";
import { AccountForm } from "../../src/entities/Account/Form";
describe("Account Form Tests", () => {
describe("Check Onload", () => {
beforeEach(() => {
XrmMockGenerator.initialise();
XrmMockGenerator.Attribute.createString("name", "");
XrmMockGenerator.Attribute.createLookup("primarycontactid", []);
});
it("should register onChange functions", () => {
// Arrange
let formContext = XrmMockGenerator.getFormContext();
let context = XrmMockGenerator.getEventContext();
// Stub
const attributeStub = sinon.stub(AttributeMock.prototype, "addOnChange");
// Act
let form = new AccountForm();
form.onLoad(context);
// Assert
expect(attributeStub.calledTwice).toBeTruthy();
});
});
});
But this is not very resilient, as it is not checking WHICH attributes the onChange functions were added to, or what function was registered.
I've tried stubbing the ForContext's "GetAttribute", but looks like it's requiring me to mock the entire return object, as otherwise, the stub does not return anything? I can get around this with using spy, but still can't work out how to check the attribute that the onChange is being added to and what the function is
Am I missing something obvious here?
I have implemented conditional Validation in my Component.
I am trying to separate the validation logic from my component to another component/service.
Demo Link : http://StackBlitz%20https://stackblitz.com/edit/angular-amr86r
You can easily apply conditional validation using #rxweb/reactive-form-validators. I have applied required validation through both validator as well as decorator based approach. Please refer to the working example.
Using Decorator based approach:
You just need to apply #required() decorator with the condition you want to apply on your model-property.
Here is the complete model code:
import { required } from "#rxweb/reactive-form-validators"
export class UserInfo {
#required()
firstName: string;
#required({conditionalExpression:'x => x.firstName == "Bharat"' })
lastName: string;
}
Using Validator based approach:
For validator based approach, you just need to mention RxwebValidators.required() with the condition you want to apply on your form-control.
Here is the complete component code:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder } from "#angular/forms"
import { RxwebValidators } from '#rxweb/reactive-form-validators';
#Component({
selector: 'app-validator-based-validator',
templateUrl: './validator-based.component.html'
})
export class ValidatorBasedValidationComponent implements OnInit {
validatorBasedFormGroup: FormGroup
constructor(
private formBuilder: FormBuilder )
{ }
ngOnInit() {
this.validatorBasedFormGroup = this.formBuilder.group({
firstName:['', RxwebValidators.required()],
lastName:['', RxwebValidators.required({conditionalExpression:'x => x.firstName == "Bharat"' })]
});
}
}
Note : if you will use validator approach then, You can create a function in a separate file and use that respective function in the condtional expression property. In anyway, the model file will be separated in decorator based approach.
I used to have a component like this:
class BlahDumb extends Component {
toggleMe = () => console.log('toggling')
render() {
...
}
}
I would then use it like this:
class App extends Component {
doIt = () => this.el.toggleMe()
refEl = el => this.el = el;
render() {
return (
<div>
<BlahDumb ref={this.refEl} />
<button onClick={this.doIt} />
</div>
)
}
}
Now this worked well UNTIL I connected Blah it to redux.
I changed Blah to this:
const Blah = connect(function() { ... })(BlahDumb);
Now I can no longer access toggleMe from this.el that I got via ref. Is this bad pattern? Or is there a way to get a childs refs?
Connect your child component passing options argument to connect like
const Blah = connect(
mapStateToProps,
mapDispatchToProps,
null,
{ withRef: true }
)(BlahDumb)
And in your parent component call the method in child using getWrappedInstance() function like.
this.el.getWrappedInstance().toggleMe()
So I have a user-list component and a user-detail component.
The user-list component has an md-table listing all users registered. The user is able to click on a button to see the details of the corresponding entity.
After editing the Name property and saving (for example), the user-detail redirects to the user-list component. But the md-table displays the old information.
How can I trigger an md-table refresh after editing the entity in another component like this scenario?
Here is my user-list component:
export class UserListComponent implements OnInit {
tableDisplayedColumns: string[] = ['userName', 'email', 'options'];
userDataSource: UserDataSource;
constructor(private _userService: UserService, private _router: Router) { }
ngOnInit(): void {
this.userDataSource = new UserDataSource(this._userService);
}}
class UserDataSource extends DataSource<UserVModel> {
constructor(private _userService: UserService) {
super();
}
connect(): Observable<UserVModel[]> {
return this._userService.getAllUsers();
}
disconnect(): void { }}
The table will re-render when the stream provided by connect() emits a new value.
getAllUsers needs to emit a new set of data when it is changed. Otherwise, listen for a separate stream (e.g. dataChanged) from the _userService and use that to call getAllUsers again.
connect(): Observable<UserVModel[]> {
return this._userService.dataChanged
.switchMap(() => this._userService.getAllUsers()
}
Actually the problem was that the User-Detail component was redirecting before the observable had the chance to complete. So I placed the router.navigate as a complete function.
Code Before
onSubmit() {
this._updateSub = this._service.updateUser(this._id, this._userForm.value)
.subscribe(null, err => this.onSubmitError(err));
this._router.navigate(['/user']);
}
Code After
onSubmit() {
this._updateSub = this._service.updateUser(this._id, this._userForm.value)
.subscribe(null, err => this.onSubmitError(err), () => this.afterSubmit());
}
afterSubmit() {
this._router.navigate(['/user']);
}
Prior to this change, I was getting the old values after the redirect. Using the complete function, I get up to date values without the need to use a dataChanged observable in the service.
You could tidy things up and put your router navigation inside the response :
onSubmit() {
this._updateSub = this._service.updateUser(this._id,
this._userForm.value).subscribe(
res => this._router.navigate(['/user']);
err => this.onSubmitError(err), () => this.afterSubmit());
}
What is the recommended way to handle redux-form/CHANGE actions being dispatched by redux-form? I have my own reducer managing the state of this form but I'm not sure if it's best to do the following:
export default reducer (state = initialState, action) {
case 'redux-form/CHANGE':
// return modified state
}
One problem I see is that this reducer would receive every redux-form/CHANGE action. Additionally as far as I can tell ActionTypes.js isn't exported in a way for me to import it so I feel as though this may not be best practice.
You can definitely use redux-form action creators. You just have to connect it to your component.
So in your /components/MyComponent.js
import React from 'react';
import { connect } from 'react-redux';
import { change } from 'redux-form';
class MyComponent extends React.Component {
...
render() {
return <div>
<button onClick={this.props.change('formName', 'fieldName', 'value')}>Change</button>
</div>
}
}
const mapStateToProps = (state, ownProps) => { ... }
export default connect(mapStateToProps, { change })(MyComponent);
You could also use redux-form reducer and extends it to your needs...
In your reducers/index.js]
import { reducer as form } from 'redux-form';
const formPlugin = {
formName: (state, action) => {
...reducer logic
return newState;
}
}
const rootReducer = combineReducers({
...other reducers,
form: form.plugin(formPlugin)
});