I get confused by the 'state' and 'login' members of a class definition from a running example as below:
class Login extends React.Component {
state = {
redirectToReferrer: false
};
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
const { from } = this.props.location.state
|| { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
return <Redirect to={from} />;
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
For the 'login', I wan to get confirmation that this is a function member of the Login class, right? I can understand the motivation of using an arrow function is a matter of binding of 'this', but I did not see this syntax appeared in my ES6 book. It looks like in the top level of {}, it just defined a variable which is assigned with an arrow function.
For the 'state', this looks like a simple assignment, but I know it must be defining a member of the 'Login' since there is a 'this.state' reference. But I don't understand the syntax, my ES6 book says any instance property must be defined in constructor of the class. Is there any other special meaning of this kind of definition?
The standard way of defining initial state in React is like this:
class Login extends React.Component {
constructor(props){
super(props);
this.state = {
redirectToReferrer: false
};
}
....
}
But, there are some libraries like unstated, that allow you to define state like this:
// BookContainer.js
import { Container } from 'unstated'
class BookContainer extends Container {
state = {
books: [],
booksVisible: false
}
addBook = book => {
const books = [...this.state.books, book]
this.setState({ books })
}
toggleVisibility = () => {
this.setState({
booksVisible: !this.state.booksVisible
})
}
}
export {
BookContainer
}
EDIT: Regarding to the login method, as you already told, is about binding the this
This:
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
Is the same as doing this:
class Login extends React.Component {
constructor(props){
super(props);
this.state = {
redirectToReferrer: false
};
this.login = this.login.bind(this); // Bind the this
}
login(){
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true }); // This is not undefined
});
}
}
You can find more info in the official unstated page
I have a simple search bar which uses a react-autosuggest. When I create a suggestion, I want to attach an onClick handler. This onClick has been passed down from a parent class. When the suggestion is rendered however, this is undefined and therefore the click handler is not attached.
I have attached the component below, the logic which is not working is in the renderSuggestion method.
import Autosuggest from 'react-autosuggest'
import React from 'react'
export class SearchBar extends React.Component {
static getSuggestionValue(suggestion) {
return suggestion;
}
static escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: [],
listOfValues: this.props.tickers
};
}
onChange = (event, { newValue, method }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: this.getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
renderSuggestion(suggestion) {
return (
<span onClick={() => this.props.clickHandler(suggestion)}>{suggestion}</span>
);
}
getSuggestions(value) {
const escapedValue = SearchBar.escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return this.state.listOfValues.filter(ticker => regex.test(ticker));
}
render() {
const { value, suggestions } = this.state;
const inputProps = {
placeholder: "Search for stocks...",
value,
onChange: this.onChange
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={SearchBar.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
inputProps={inputProps} />
);
}
}
This is becuase you need to bind "this" to your function.
If you add this code to your constructor
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: [],
listOfValues: this.props.tickers
};
//this line of code binds this to your function so you can use it
this.renderSuggestion = this.renderSuggestion.bind(this);
}
It should work. More info can be found at https://reactjs.org/docs/handling-events.html
In the scope of renderSuggestion, this isn't referring to the instance of the class.
Turning renderSuggestion into an arrow function like you've done elsewhere will ensure that this refers to the instance of the class.
renderSuggestion = (suggestion) => {
return (
<span onClick={() => this.props.clickHandler(suggestion)}>{suggestion}</span>
);
}
I'm working on jest unit testing using react-test-renderer.The test cases fails and showing this error
"TypeError: this.props.myMaterials.fetch is not a function"
where this.props.notes.fetch is inside the componentWillMount.Is there any solution to fix this without using enzyme?
myComponent.jsx :
class myComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
column: this.getColumns(),
pageNotFound: false
};
}
componentWillMount() {
this.props.notes.fetch(this.props.courseId);
window.addEventListener('resize', this.handleResizeEvent);
this.handleError = EventBus.on(constants.NOTES_NOT_FOUND, () => {
this.setState({ pageNotFound: true });
});
}
componentDidMount() {
window.addEventListener('resize', this.handleResizeEvent);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResizeEvent);
this.handleError();
}
handleResizeEvent = () => {
this.setState({ column: this.getColumns() });
};
getColumns = () => (window.innerWidth > (constants.NOTES_MAX_COLUMNS * constants.NOTES_WIDTH) ?
constants.NOTES_MAX_COLUMNS :
Math.floor(window.innerWidth / constants.NOTES_WIDTH))
callback = (msg, data) => {
}
render() {
const { notes, language } = this.props;
if (this.state.pageNotFound) {
return (<div className="emptyMessage"><span>Empty</span></div>);
}
if (notes.loading) {
return (<Progress/>);
}
// To Refresh Child component will receive props
const lists = [...notes.cards];
return (
<div className="notesContainer" >
<NoteBook notesList={lists} callback={this.callback} coloums={this.state.column} />
</div>
);
}
}
myComponent.propTypes = {
notes: PropTypes.object,
courseId: PropTypes.string,
language: PropTypes.shape(shapes.language)
};
export default withRouter(myComponent);
myComponent.test.jsx:
const tree = renderer.create(
<myComponent.WrappedComponent/>).toJSON();
expect(tree).toMatchSnapshot();
});
Its pretty evident from the error that while testing you are not supplying the prop notes which is being used in your componentWillMount function. Pass it when you are creating an instance for testing and it should work.
All you need to do is this
const notes = {
fetch: jest.fn()
}
const tree = renderer.create(
<myComponent.WrappedComponent notes={notes}/>).toJSON();
expect(tree).toMatchSnapshot();
});
One more thing that you should take care is that your component names must begin with Uppercase characters.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
column: this.getColumns(),
pageNotFound: false
};
}
componentWillMount() {
this.props.notes.fetch(this.props.courseId);
window.addEventListener('resize', this.handleResizeEvent);
this.handleError = EventBus.on(constants.NOTES_NOT_FOUND, () => {
this.setState({ pageNotFound: true });
});
}
componentDidMount() {
window.addEventListener('resize', this.handleResizeEvent);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResizeEvent);
this.handleError();
}
handleResizeEvent = () => {
this.setState({ column: this.getColumns() });
};
getColumns = () => (window.innerWidth > (constants.NOTES_MAX_COLUMNS * constants.NOTES_WIDTH) ?
constants.NOTES_MAX_COLUMNS :
Math.floor(window.innerWidth / constants.NOTES_WIDTH))
callback = (msg, data) => {
}
render() {
const { notes, language } = this.props;
if (this.state.pageNotFound) {
return (<div className="emptyMessage"><span>Empty</span></div>);
}
if (notes.loading) {
return (<Progress/>);
}
// To Refresh Child component will receive props
const lists = [...notes.cards];
return (
<div className="notesContainer" >
<NoteBook notesList={lists} callback={this.callback} coloums={this.state.column} />
</div>
);
}
}
MyComponent.propTypes = {
notes: PropTypes.object,
courseId: PropTypes.string,
language: PropTypes.shape(shapes.language)
};
export default withRouter(MyComponent);
Have you tried giving your component a stub notes.fetch function?
Let isFetched = false;
const fakeNotes = {
fetch: () => isFetched = true
}
That way you can test that fetch is called without making a request. I'm not sure, but the test runner is running in node, and I think you may need to require fetch in node, and so the real notes may be trying to use the browser's fetch that does not exist.
I'm not an expert, but I believe it is good practice to use a fakes for side effects/dependencies anyway, unless the test specifically is testing the side effect/dependency.
Pass notes as props to your component like <myComponent.WrappedComponent notes={<here>} /> and also put a check like this.props.notes && this.props.notes.fetch so that even if your props aren't passed you don't get an error.
I'm implementing search with pagination in React. So far I found few examples of it, but all they use code with double setState(), before and after AJAX call to backend. For example my current solution is:
import React from "react"
import PropTypes from "prop-types"
import SearchField from "components/SearchField"
import SearchResults from "components/SearchResults"
import Item from "models/Item"
class Search extends React.Component {
constructor() {
super()
this.state = {
query: "",
page: 1,
foundItems: []
}
this.handleSearch = this.handleSearch.bind(this)
this.handlePageChange = this.handlePageChange.bind(this)
}
updateSearchResults() {
const query = this.state.query
const params = {
page: this.state.page
}
Item.search(query, params).then((foundItems) => {
this.setState({ foundItems })
})
}
handleSearch(event) {
this.setState({
query: event.target.value
}, this.updateSearchResults)
}
handlePageChange(data) {
this.setState({
page: data.selected + 1
}, this.updateSearchResults)
}
render() {
return (
<div className="search">
<SearchField onSearch={this.handleSearch} />
<SearchResults
onPageChange={this.handlePageChange}
onSelect={this.props.onSelect}
items={this.state.foundItems}
/>
</div>
)
}
}
Search.propTypes = {
onSelect: PropTypes.func.isRequired
}
export default Search
I know that I can change interface of updateSearchResults to receive query and page as arguments and then I can avoid first setState to pass values there, but it doesn't look like a good solution, because when list of search parameters will grow (sorting order, page size, filters for example) then it'll get a bit clumsy. Plus I don't like idea of manual state pre-management in handleSearch and handlePageChange functions in this way. I'm looking for a better implementation.
I am not fully sure what you are asking, but you can optimise your code a bit by doing the following:
class Search extends React.Component {
constructor() {
super()
this.page = 1;
this.query = "";
this.state = {
foundItems: []
}
this.handlePageChange = this.handlePageChange.bind(this)
}
updateSearchResults(event) {
if(typeof event === "object")
this.query = event.target.value;
const params = {
page: this.page
}
Item.search(this.query, params).then((foundItems) => {
this.setState({ foundItems })
})
}
handlePageChange(data) {
this.page = data.selected + 1;
this.updateSearchResults();
}
render() {
return (
<div className="search">
<SearchField onSearch={this.updateSearchResults} />
<SearchResults
onPageChange={this.handlePageChange}
onSelect={this.props.onSelect}
items={this.state.foundItems}
/>
</div>
)
}
}
I got one form who is used to Create, Read, Update and Delete. I created 3 components with the same form but I pass them different props. I got CreateForm.js, ViewForm.js (readonly with the delete button) and UpdateForm.js.
I used to work with PHP, so I always did these in one form.
I use React and Redux to manage the store.
When I'm in the CreateForm component, I pass to my sub-components this props createForm={true} to not fill the inputs with a value and don't disable them. In my ViewForm component, I pass this props readonly="readonly".
And I got another problem with a textarea who is filled with a value and is not updatable. React textarea with value is readonly but need to be updated
What's the best structure to have only one component which handles these different states of the form?
Do you have any advice, tutorials, videos, demos to share?
I found the Redux Form package. It does a really good job!
So, you can use Redux with React-Redux.
First you have to create a form component (obviously):
import React from 'react';
import { reduxForm } from 'redux-form';
import validateContact from '../utils/validateContact';
class ContactForm extends React.Component {
render() {
const { fields: {name, address, phone}, handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit}>
<label>Name</label>
<input type="text" {...name}/>
{name.error && name.touched && <div>{name.error}</div>}
<label>Address</label>
<input type="text" {...address} />
{address.error && address.touched && <div>{address.error}</div>}
<label>Phone</label>
<input type="text" {...phone}/>
{phone.error && phone.touched && <div>{phone.error}</div>}
<button onClick={handleSubmit}>Submit</button>
</form>
);
}
}
ContactForm = reduxForm({
form: 'contact', // the name of your form and the key to
// where your form's state will be mounted
fields: ['name', 'address', 'phone'], // a list of all your fields in your form
validate: validateContact // a synchronous validation function
})(ContactForm);
export default ContactForm;
After this, you connect the component which handles the form:
import React from 'react';
import { connect } from 'react-redux';
import { initialize } from 'redux-form';
import ContactForm from './ContactForm.react';
class App extends React.Component {
handleSubmit(data) {
console.log('Submission received!', data);
this.props.dispatch(initialize('contact', {})); // clear form
}
render() {
return (
<div id="app">
<h1>App</h1>
<ContactForm onSubmit={this.handleSubmit.bind(this)}/>
</div>
);
}
}
export default connect()(App);
And add the redux-form reducer in your combined reducers:
import { combineReducers } from 'redux';
import { appReducer } from './app-reducers';
import { reducer as formReducer } from 'redux-form';
let reducers = combineReducers({
appReducer, form: formReducer // this is the form reducer
});
export default reducers;
And the validator module looks like this:
export default function validateContact(data, props) {
const errors = {};
if(!data.name) {
errors.name = 'Required';
}
if(data.address && data.address.length > 50) {
errors.address = 'Must be fewer than 50 characters';
}
if(!data.phone) {
errors.phone = 'Required';
} else if(!/\d{3}-\d{3}-\d{4}/.test(data.phone)) {
errors.phone = 'Phone must match the form "999-999-9999"'
}
return errors;
}
After the form is completed, when you want to fill all the fields with some values, you can use the initialize function:
componentWillMount() {
this.props.dispatch(initialize('contact', {
name: 'test'
}, ['name', 'address', 'phone']));
}
Another way to populate the forms is to set the initialValues.
ContactForm = reduxForm({
form: 'contact', // the name of your form and the key to
fields: ['name', 'address', 'phone'], // a list of all your fields in your form
validate: validateContact // a synchronous validation function
}, state => ({
initialValues: {
name: state.user.name,
address: state.user.address,
phone: state.user.phone,
},
}))(ContactForm);
If you got any other way to handle this, just leave a message! Thank you.
UPDATE: its 2018 and I'll only ever use Formik (or Formik-like libraries)
There is also react-redux-form (step-by-step), which seems exchange some of redux-form's javascript (& boilerplate) with markup declaration. It looks good, but I've not used it yet.
A cut and paste from the readme:
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { modelReducer, formReducer } from 'react-redux-form';
import MyForm from './components/my-form-component';
const store = createStore(combineReducers({
user: modelReducer('user', { name: '' }),
userForm: formReducer('user')
}));
class App extends React.Component {
render() {
return (
<Provider store={ store }>
<MyForm />
</Provider>
);
}
}
./components/my-form-component.js
import React from 'react';
import { connect } from 'react-redux';
import { Field, Form } from 'react-redux-form';
class MyForm extends React.Component {
handleSubmit(val) {
// Do anything you want with the form value
console.log(val);
}
render() {
let { user } = this.props;
return (
<Form model="user" onSubmit={(val) => this.handleSubmit(val)}>
<h1>Hello, { user.name }!</h1>
<Field model="user.name">
<input type="text" />
</Field>
<button>Submit!</button>
</Form>
);
}
}
export default connect(state => ({ user: state.user }))(MyForm);
Edit: Comparison
The react-redux-form docs provide a comparison vs redux-form:
https://davidkpiano.github.io/react-redux-form/docs/guides/compare-redux-form.html
For those who doesn't care about an enormous library for handling form related issues, I would recommend redux-form-utils.
It can generate value and change handlers for your form controls, generate reducers of the form, handy action creators to clear certain(or all) fields, etc.
All you need to do is assemble them in your code.
By using redux-form-utils, you end up with form manipulation like following:
import { createForm } from 'redux-form-utils';
#createForm({
form: 'my-form',
fields: ['name', 'address', 'gender']
})
class Form extends React.Component {
render() {
const { name, address, gender } = this.props.fields;
return (
<form className="form">
<input name="name" {...name} />
<input name="address" {...address} />
<select {...gender}>
<option value="male" />
<option value="female" />
</select>
</form>
);
}
}
However, this library only solves problem C and U, for R and D, maybe a more integrated Table component is to antipate.
Just another thing for those who want to create fully controlled form component without using oversized library.
ReduxFormHelper - a small ES6 class, less than 100 lines:
class ReduxFormHelper {
constructor(props = {}) {
let {formModel, onUpdateForm} = props
this.props = typeof formModel === 'object' &&
typeof onUpdateForm === 'function' && {formModel, onUpdateForm}
}
resetForm (defaults = {}) {
if (!this.props) return false
let {formModel, onUpdateForm} = this.props
let data = {}, errors = {_flag: false}
for (let name in formModel) {
data[name] = name in defaults? defaults[name] :
('default' in formModel[name]? formModel[name].default : '')
errors[name] = false
}
onUpdateForm(data, errors)
}
processField (event) {
if (!this.props || !event.target) return false
let {formModel, onUpdateForm} = this.props
let {name, value, error, within} = this._processField(event.target, formModel)
let data = {}, errors = {_flag: false}
if (name) {
value !== false && within && (data[name] = value)
errors[name] = error
}
onUpdateForm(data, errors)
return !error && data
}
processForm (event) {
if (!this.props || !event.target) return false
let form = event.target
if (!form || !form.elements) return false
let fields = form.elements
let {formModel, onUpdateForm} = this.props
let data = {}, errors = {}, ret = {}, flag = false
for (let n = fields.length, i = 0; i < n; i++) {
let {name, value, error, within} = this._processField(fields[i], formModel)
if (name) {
value !== false && within && (data[name] = value)
value !== false && !error && (ret[name] = value)
errors[name] = error
error && (flag = true)
}
}
errors._flag = flag
onUpdateForm(data, errors)
return !flag && ret
}
_processField (field, formModel) {
if (!field || !field.name || !('value' in field))
return {name: false, value: false, error: false, within: false}
let name = field.name
let value = field.value
if (!formModel || !formModel[name])
return {name, value, error: false, within: false}
let model = formModel[name]
if (model.required && value === '')
return {name, value, error: 'missing', within: true}
if (model.validate && value !== '') {
let fn = model.validate
if (typeof fn === 'function' && !fn(value))
return {name, value, error: 'invalid', within: true}
}
if (model.numeric && isNaN(value = Number(value)))
return {name, value: 0, error: 'invalid', within: true}
return {name, value, error: false, within: true}
}
}
It doesn't do all the work for you. However it facilitates creation, validation and handling of a controlled form component.
You may just copy & paste the above code into your project or instead, include the respective library - redux-form-helper (plug!).
How to use
The first step is add specific data to Redux state which will represent the state of our form.
These data will include current field values as well as set of error flags for each field in the form.
The form state may be added to an existing reducer or defined in a separate reducer.
Furthermore it's necessary to define specific action initiating update of the form state as well as respective action creator.
Action example:
export const FORM_UPDATE = 'FORM_UPDATE'
export const doFormUpdate = (data, errors) => {
return { type: FORM_UPDATE, data, errors }
}
...
Reducer example:
...
const initialState = {
formData: {
field1: '',
...
},
formErrors: {
},
...
}
export default function reducer (state = initialState, action) {
switch (action.type) {
case FORM_UPDATE:
return {
...ret,
formData: Object.assign({}, formData, action.data || {}),
formErrors: Object.assign({}, formErrors, action.errors || {})
}
...
}
}
The second and final step is create a container component for our form and connect it with respective part of Redux state and actions.
Also we need to define a form model specifying validation of form fields.
Now we instantiate ReduxFormHelper object as a member of the component and pass there our form model and a callback dispatching update of the form state.
Then in the component's render() method we have to bind each field's onChange and the form's onSubmit events with processField() and processForm() methods respectively as well as display error blocks for each field depending on the form error flags in the state.
The example below uses CSS from Twitter Bootstrap framework.
Container Component example:
import React, {Component} from 'react';
import {connect} from 'react-redux'
import ReduxFormHelper from 'redux-form-helper'
class MyForm extends Component {
constructor(props) {
super(props);
this.helper = new ReduxFormHelper(props)
this.helper.resetForm();
}
onChange(e) {
this.helper.processField(e)
}
onSubmit(e) {
e.preventDefault()
let {onSubmitForm} = this.props
let ret = this.helper.processForm(e)
ret && onSubmitForm(ret)
}
render() {
let {formData, formErrors} = this.props
return (
<div>
{!!formErrors._flag &&
<div className="alert" role="alert">
Form has one or more errors.
</div>
}
<form onSubmit={this.onSubmit.bind(this)} >
<div className={'form-group' + (formErrors['field1']? ' has-error': '')}>
<label>Field 1 *</label>
<input type="text" name="field1" value={formData.field1} onChange={this.onChange.bind(this)} className="form-control" />
{!!formErrors['field1'] &&
<span className="help-block">
{formErrors['field1'] === 'invalid'? 'Must be a string of 2-50 characters' : 'Required field'}
</span>
}
</div>
...
<button type="submit" className="btn btn-default">Submit</button>
</form>
</div>
)
}
}
const formModel = {
field1: {
required: true,
validate: (value) => value.length >= 2 && value.length <= 50
},
...
}
function mapStateToProps (state) {
return {
formData: state.formData, formErrors: state.formErrors,
formModel
}
}
function mapDispatchToProps (dispatch) {
return {
onUpdateForm: (data, errors) => {
dispatch(doFormUpdate(data, errors))
},
onSubmitForm: (data) => {
// dispatch some action which somehow updates state with form data
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyForm)
Demo