Is it OK to put propTypes and defaultProps as static props inside React class? - class

This is the way I've been doing it for quite some time now:
export default class AttachmentCreator extends Component {
render() {
return <div>
<RaisedButton primary label="Add Attachment" />
</div>
}
}
AttachmentCreator.propTypes = {
id: PropTypes.string,
};
But I've seen people doing it this way:
export default class AttachmentCreator extends Component {
static propTypes = {
id: PropTypes.string,
};
render() {
return <div>
<RaisedButton primary label="Add Attachment" />
</div>
}
}
And in fact I've seen people setting initial state outside the constructor as well. Is this good practice? It's been bugging me, but I remember a discussion somewhere where someone said that setting default props as a static is not a good idea - I just don't remember why.

In fact, it's exactly the same in terms of performance. React.JS is a relatively new technology, so it's not clear yet what are considered good practices or don't. If you want to trust someone, check this AirBNB's styleguide:
https://github.com/airbnb/javascript/tree/master/react#ordering
import React, { PropTypes } from 'react';
const propTypes = {
id: PropTypes.number.isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string,
};
const defaultProps = {
text: 'Hello World',
};
class Link extends React.Component {
static methodsAreOk() {
return true;
}
render() {
return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>
}
}
Link.propTypes = propTypes;
Link.defaultProps = defaultProps;
export default Link;
They are declaring a const with the propTypes object literals, keep the class pretty clean and assign them later to the static properties. I personally like this approach :)

Oh yes, it's totaly legit with Babel (or other transpilers)
class DataLoader extends React.Component {
static propTypes = {
onFinishedLoading: PropTypes.func
}
static defaultProps = {
onFinishedLoading: () => {}
}
}
...as it gets transpiled to this anyway.
class DataLoader extends React.Component {}
DataLoader.propTypes = {
onFinishedLoading: PropTypes.func
};
DataLoader.defaultProps = {
onFinishedLoading: () => {}
};
Static fields get transpiled as properties on the class object under the hood.
Look here at Babel REPL.
Moreover, assigning state or other class fields directly in the class body gets transpiled into the constructor.

non-function properties are not currently supported for es2015 classes. its a proposal for es2016. the second method is considerably more convenient, but a plugin would be required to support the syntax (theres a very common babel plugin for it).
On the other end, a hand full of open source projects are beginning to treat proptypes like TypeScript interfaces, or ActionConstants and actually create separate folders/files that house various component prop types and are then imported into the component.
So in summary, both syntaxes are ok to use. but if you want to only use strictly ES2015, the latter syntax is not yet supported in the specification.

If the component is state-less, meaning it does not change it's own state at all, you should declare it as a stateless component (export default function MyComponent(props)) and declare the propTypes outside.
Whether it's good practice to put initial state declaration in constructor is up to you. In our project we declare initial state in componentWillMount() just because we do not like the super(props) boilerplate you have to use with the constructor.

Related

Material-UI: "The key provided to the classes property is not implemented"

I am using the withStyles() HOC to override some MUI component styles, theme and breakpoints.
There is obviously something I do not understand here as I keep getting errors such as this one:
Warning: Material-UI: the key tab provided to the classes property
is not implemented in Items.
You can only override one of the following:
card,details,content,cover,avatar,lock
Example code: https://codesandbox.io/s/6xwz50kxn3
I have a <List /> component and its child <Items />.
My intention is to apply the styles in the demo.js file only to the <List /> component, and the styles in the demoChild.js to the <Items /> Component.
I would really appreciate an explanation of what I'm doing wrong, and maybe a solution?
Note: I have found other posts with the same error, but they seem to have something different to my example.
The warnings are caused by this line in your demo.js file:
<Items {...this.props} items={items} />
You're spreading all of List's props down into your Items. One of these props is classes, containing all of the CSS classes you define in demo.js. Since those are intended for List, they include CSS classes that are implemented by List but not Items. Since Items is receiving this prop, it's reading it as you trying to override classes that aren't available and warning you about it.
You can fix this problem by spreading only the unused props:
// Use other to capture only the props you're not using in List
const { classes, headerIsHidden, ...other } = this.props;
// Then spread only those unused props
<Items {...other} items={items} /
Then, you won't be spreading classes object into Items, so you won't get any warnings about classes that aren't implemented.
In my case, I want to reuse multiple styles in different files, so I wrote a helper function:
import { withStyles } from '#material-ui/core/styles'
// Fixed: material-ui "The key provided to the classes property is not implemented"
const withMultipleStyles = (...params) => {
return withStyles((theme) => {
var styles = {}
for (var len = params.length, key = 0; key < len; key++) {
styles = Object.assign(styles, params[key](theme));
}
return styles
})
}
export default withMultipleStyles
Usage:
import React from 'react'
import compose from 'recompose/compose'
import { connect } from 'react-redux'
import { style1, style2, withMultipleStyles } from '../../styles'
class your_class_name_here extends React.Component {
// your implementation
}
export default compose(
withMultipleStyles(style1, style2),
withWidth(),
connect(mapStateToProps, mapDispatchToProps)
)(your_class_name_here)

Mobx React Form - How to implement Custom onSubmit

I am using mobx-react-from and i have a a problem to figure how i can use an action that i have in my store inside obSubmit hook ....
the mobx form is working ok .. i can see the inputs and the validation
and when i submit the form all i want is to use an action from store ...
my AutStore file :
import {observable,action} from 'mobx';
class AuthStore {
constructor(store) {
this.store = store
}
#action authLogin=(form)=>{
this.store.firebaseAuth.signInWithEmailAndPassword().then(()=>{
}).catch(()=>{
})
}
}
export default AuthStore
my AuthForm File :
import {observable, action} from 'mobx';
import MobxReactForm from 'mobx-react-form';
import {formFields,formValidation} from './formSettings';
const fields = [
formFields.email,
formFields.password
];
const hooks = {
onSuccess(form) {
// Here i want to use an action - authLogin from my AuthStore
console.log('Form Values!', form.values());
},
onError(form) {
console.log('All form errors', form.errors());
}
};
const AuthForm = new MobxReactForm({fields}, {plugins:formValidation,
hooks});
export default AuthForm
i would like to know how can i connect all together thanks !!!
I haven't used mobx-react-form before but have used mobx and react extensively. There's a couple ways to do this. The way I have done it is as follows, assuming Webpack & ES6 & React 14 here. Instantiate the store, and use a Provider around the component that hosts the form.
import { Provider } from 'mobx-react'
import React, { Component, PropTypes } from 'react'
import AuthStore from '{your auth store rel path}'
import FormComponent from '{your form component rel path}'
// instantiate store
const myAuthStore = new AuthStore()
// i don't think the constructor for AuthStore needs a store arg.
export default class SingleFormApplication extends Component {
render() {
return (
<Provider store={myAuthStore} >
<FormComponent />
</Provider>
)
}
}
Your FormComponent class will need to take advantage of both the observer and inject methods of the mobx-react package that will wrap it in a higher order component that both injects the store object as a prop and registers a listener on the store for changes that will rerender the component. I typically use the annotation syntax and it looks like this.
#inject('{name of provider store prop to inject}') #observer
export default class Example extends Component {
constructor(props) {
super(props)
this.store = this.props.store
}
}
Finally, with the store injected, you can now pass an action from the store into an AuthForm method, which I would advise you modify accordingly. Have the AuthForm file export a method that takes an onSuccess method as an arg and returns the mobx-react-form object. I would also modify your store action to simply take the email and password as an arg instead of the whole form. In the FormComponent try this:
import { formWithSuccessAction } from '{rel path to auth form}'
then in constructor after this.store = this.props.store assignment...
this.form = formWithSuccessAction(this.store.authLogin)
then in your render method of the FormComponent use the this.form class variable to render a form as you would in the mobx-react-form docs.
To be as clear as possible, the AuthForm.formWithSuccessAction method should look something like this:
const formWithSuccessAction = (storeSuccessAction) => {
const fields = [
formFields.email,
formFields.password
];
const hooks = {
onSuccess(form) {
// Here i want to use an action - authLogin from my AuthStore
console.log('Form Values!', form.values());
// get email and password in separate vars or not, up to you
storeSuccessAction(email, password)
},
onError(form) {
console.log('All form errors', form.errors());
}
};
const AuthForm = new MobxReactForm({fields}, {plugins:formValidation,
hooks});
return AuthForm
}
Hopefully this helps you on your way.

How do I get intellisense to work inside es6 classes?

I have a class that extends React. Component like so:
class Main extends Component {
static propTypes = {
flavor: string.isRequired,
iceCreamMap: object.isRequired,
restUrl: string.isRequired,
token: string.isRequired,
username: string.isRequired
};
}
I want the methods of Component.prototype to show up in intellisense but it is not working. It however will work when I do. React.Component.prototype.

Angular 2 + ngrx(redux) + forms

How do you handle Angular 2 forms in unidirectional data flow? Especially with validation between several parent/child components?
I am using ngrx/store and model driven forms with form builder.. Is it possible to do something similar like form reducer in React and make it as a part of Store?
Do you have some articles about it?
I have created a library called ngrx-forms that does exactly what you want. You can get it on npm via:
npm install ngrx-forms --save
I recommend checking out the full README on the github page, but below you can find some examples of what you need to do to get the library up and running once installed.
Import the module:
import { StoreModule } from '#ngrx/store';
import { NgrxFormsModule } from 'ngrx-forms';
import { reducers } from './reducer';
#NgModule({
declarations: [
AppComponent,
],
imports: [
NgrxFormsModule,
StoreModule.forRoot(reducers),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Add a group state somewhere in your state tree via createFormGroupState and call the formGroupReducer inside your reducer:
import { Action } from '#ngrx/store';
import { FormGroupState, createFormGroupState, formGroupReducer } from 'ngrx-forms';
export interface MyFormValue {
someTextInput: string;
someCheckbox: boolean;
nested: {
someNumber: number;
};
}
const FORM_ID = 'some globally unique string';
const initialFormState = createFormGroupState<MyFormValue>(FORM_ID, {
someTextInput: '',
someCheckbox: false,
nested: {
someNumber: 0,
},
});
export interface AppState {
someOtherField: string;
myForm: FormGroupState<MyFormValue>;
}
const initialState: AppState = {
someOtherField: '',
myForm: initialFormState,
};
export function appReducer(state = initialState, action: Action): AppState {
const myForm = formGroupReducer(state.myForm, action);
if (myForm !== state.myForm) {
state = { ...state, myForm };
}
switch (action.type) {
case 'some action type':
// modify state
return state;
default: {
return state;
}
}
}
Expose the form state inside your component:
import { Component } from '#angular/core';
import { Store } from '#ngrx/store';
import { FormGroupState } from 'ngrx-forms';
import { Observable } from 'rxjs/Observable';
import { MyFormValue } from './reducer';
#Component({
selector: 'my-component',
templateUrl: './my-component.html',
})
export class MyComponent {
formState$: Observable<FormGroupState<MyFormValue>>;
constructor(private store: Store<AppState>) {
this.formState$ = store.select(s => s.myForm);
}
}
Set the control states in your template:
<form novalidate [ngrxFormState]="(formState$ | async)">
<input type="text"
[ngrxFormControlState]="(formState$ | async).controls.someTextInput">
<input type="checkbox"
[ngrxFormControlState]="(formState$ | async).controls.someCheckbox">
<input type="number"
[ngrxFormControlState]="(formState$ | async).controls.nested.controls.someNumber">
</form>
This is a fairly old question, but I couldn't find a great solution in my own quest for working with ngrx + reactive forms in Angular. As a result, I'll post my research here with hope that it may help someone else. My solution can be broken down into two parts, and I pray you (oh weathered soul) find it applicable to your problem:
1) Monitor the form element/s (for example, "keyup" event for a typical text input), and update the State from that event. This strategy comes straight out of the book search component in the ngrx example app. We can now successfully populate the State as our form changes. Awesome! 50% done!
2) The angular reactive forms guide demonstrates creating the form group in the constructor. I have seen some other people do it inside ngOnInit, but this is too late in the lifecycle for our needs (I tried, I failed). Now that we have our form group established, setup ngOnChanges to capture any changes pushed from the state, and then update the form group using patchValue. For example:
ngOnChanges(changes: SimpleChanges) {
if (changes.valueICareAbout1) {
this.myForm.patchValue({
valueICareAbout1: changes.valueICareAbout1.currentValue
});
}
if (changes.valueICareAbout2) {
this.myForm.patchValue({
valueICareAbout2: changes.valueICareAbout2.currentValue
});
}
}
In the applications I built with Angular 2, the following guideline seemed to work well:
Parent components pass data down to children via data binding. Child components request data changes by emitting output events to parent components. It is the parent components responsibility to act accordingly.
In a hierarchical component structure, data changes are handled by the lowest component that depends on the data. If there's another component higher up or a sibling that depends on the same data item, pass changes up by emitting events and leave the handling to a higher component.
This scheme works well because, for any data that is relevant to more than one component, there is a single component responsible for performing changes. Changes bubble down automatically. Components are reusable, and changes in the component tree can be easily adapted.
With regard to validation, any component in the ladder between the lowest component emitting a data change request up to the highest component that finally handles the change, any component can effectively cancel the change by not passing it higher up. In most applications, I'd opt for validating data changes at the origin of the change though.
Naturally, child components can still have internal state and need not communicate changes - unless changes are relevant to the parent component.
Form data is inherently a very local state, especially for Angular since ngModel binds to local component variables. The top devs that I know recommend keeping the data for the form localized to that component (ie just use ngModel with local variables). This is because un-submitted form data is almost never shared by various components across your whole application. When the user submits the form then you can dispatch an action with a payload containing the form data to a parent component, to the store, or even to an ngrx/effect to be posted to a server.

Create an instance of a React class from a string

I have a string which contains a name of the Class (this is coming from a json file). This string tells my Template Class which layout / template to use for the data (also in json). The issue is my layout is not displaying.
Home.jsx:
//a template or layout.
var Home = React.createClass({
render () {
return (
<div>Home layout</div>
)
}
});
Template.jsx:
var Template = React.createClass({
render: function() {
var Tag = this.props.template; //this is the name of the class eg. 'Home'
return (
<Tag />
);
}
});
I don't get any errors but I also don't see the layout / Home Class. I've checked the props.template and this logs the correct info. Also, I can see the home element in the DOM. However it looks like this:
<div id='template-holder>
<home></home>
</div>
If I change following line to:
var Tag = Home;
//this works but it's not dynamic!
Any ideas, how I can fix this? I'm sure it's either simple fix or I'm doing something stupid. Help would be appreciated. Apologies if this has already been asked (I couldn't find it).
Thanks,
Ewan
This will not work:
var Home = React.createClass({ ... });
var Component = "Home";
React.render(<Component />, ...);
However, this will:
var Home = React.createClass({ ... });
var Component = Home;
React.render(<Component />, ...);
So you simply need to find a way to map between the string "Home" and the component class Home. A simple object will work as a basic registry, and you can build from there if you need more features.
var components = {
"Home": Home,
"Other": OtherComponent
};
var Component = components[this.props.template];
No need to manually map your classes to a dictionary, or "registry", as in Michelle's answer. A wildcard import statement is already a dictionary!
import * as widgets from 'widgets';
const Type = widgets[this.props.template];
...
<Type />
You can make it work with multiple modules by merging all the dictionaries into one:
import * as widgets from 'widgets';
import * as widgets2 from 'widgets2';
const registry = Object.assign({}, widgets, widgets2);
const widget = registry[this.props.template];
I would totally do this to get dynamic dispatch of react components. In fact I think I am in a bunch of projects.
I had the same problem, and found out the solution by myself. I don't know if is the "best pratice" but it works and I'm using it currently in my solution.
You can simply make use of the "evil" eval function to dynamically create an instance of a react component. Something like:
function createComponent(componentName, props, children){
var component = React.createElement(eval(componentName), props, children);
return component;
}
Then, just call it where you want:
var homeComponent = createComponent('Home', [props], [...children]);
If it fits your needs, maybe you can consider something like this.
Hope it helps.
I wanted to know how to create React classes dynamically from a JSON spec loaded from a database and so I did some experimenting and figured it out. My basic idea was that I wanted to define a React app through a GUI instead of typing in code in a text editor.
This is compatible with React 16.3.2. Note React.createClass has been moved into its own module.
Here's condensed version of the essential parts:
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import createReactClass from 'create-react-class'
const spec = {
// getDefaultProps
// getInitialState
// propTypes: { ... }
render () {
return React.createElement('div', null, 'Some text to render')
}
}
const component = createReactClass(spec)
const factory = React.createFactory(component)
const instance = factory({ /* props */ })
const str = ReactDOMServer.renderToStaticMarkup(instance)
console.log(str)
You can see a more complete example here:
https://github.com/brennancheung/02-dynamic-react/blob/master/src/commands/tests/createClass.test.js
Here is the way it will work from a string content without embedding your components as statically linked code into your package, as others have suggested.
import React from 'react';
import { Button } from 'semantic-ui-react';
import createReactClass from 'create-react-class';
export default class Demo extends React.Component {
render() {
const s = "return { render() { return rce('div', null, rce(components['Button'], {content: this.props.propA}), rce(components['Button'], {content: 'hardcoded content'})); } }"
const createComponentSpec = new Function("rce", "components", s);
const componentSpec = createComponentSpec(React.createElement, { "Button": Button });
const component = React.createElement(createReactClass(componentSpec), { propA: "content from property" }, null);
return (
<div>
{component}
</div>
)
}
}
The React class specification is in string s. Note the following:
rce stands for React.createElement and given as a first param when callingcreateComponentSpec.
components is a dictionary of extra component types and given as a second param when callingcreateComponentSpec. This is done so that you can provide components with clashing names.
For example string Button can be resolved to standard HTML button, or button from Semantic UI.
You can easily generate content for s by using https://babeljs.io as described in https://reactjs.org/docs/react-without-jsx.html. Essentially, the string can't contain JSX stuff, and has to be plain JavaScript. That's what BabelJS is doing by translating JSX into JavaScript.
All you need to do is replace React.createElement with rce, and resolve external components via components dictionary (if you don't use external components, that you can skip the dictionary stuff).
Here is equivalent what in the code above. The same <div> with two Semantic UI Buttons in it.
JSX render() code:
function render() {
return (
<div>
<Button content={this.props.propA}/>
<Button content='hardcoded content'/>
</div>
);
}
BabelJS translates it into:
function render() {
return React.createElement("div", null, React.createElement(Button, {
content: this.props.propA
}), React.createElement(Button, {
content: "hardcoded content"
}));
}
And you do replacement as outlined above:
render() { return rce('div', null, rce(components['Button'], {content: this.props.propA}), rce(components['Button'], {content: 'hardcoded content'})); }
Calling createComponentSpec function will create a spec for React class.
Which then converted into actual React class with createReactClass.
And then brought to life with React.createElement.
All you need to do is return it from main component render func.
When you use JSX you can either render HTML tags (strings) or React components (classes).
When you do var Tag = Home, it works because the JSX compiler transforms it to:
var Template = React.createElement(Tag, {});
with the variable Tag in the same scope and being a React class.
var Tag = Home = React.createClass({
render () {
return (
<div>Home layout</div>
)
}
});
When you do
var Tag = this.props.template; // example: Tag = "aClassName"
you are doing
var Template = React.createElement("aClassName", null);
But "aClassName" is not a valid HTML tag.
Look here