Mapping actions and state to ReduxForm - reactjs

I recently upgraded from Redux Form 5.3.1 to Redux Form 6.2 and I've not been able to dispatch my custom action creator on the form submit; it shows up as not a function. The formProps are however, correct when inspected and the handleFormSubmit is called correctly. It's just that it doesn't recognize any actions as mapped to properties.
Update
Fairly confident, it's the change in the api of reduxForm call. https://github.com/erikras/redux-form/issues/2013
This might be a solution:
https://gist.github.com/insin/bbf116e8ea10ef38447b
The code from Redux Form 6.2:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import { Field, reduxForm } from 'redux-form';
import InputField from '../input-field/index.js';
class Signup extends Component {
handleFormSubmit(formProps) {
// PROBLEM -> Uncaught TypeError: this.props.signupUser is not a function
this.props.signupUser(formProps);
}
render() {
const { handleSubmit, submitting } = this.props;
return (
<form onSubmit={ handleSubmit(this.handleFormSubmit.bind(this)) } >
<Field name="username" type="text" component={ InputField } label="Username" />
<Field name="email" type="email" component={ InputField } label="Email" />
<Field name="password" type="password" component={ InputField } label="Password" />
<Field name="password_confirmation" type="password" component={ InputField } label="Confirmation" />
<div>
<button type="submit" disabled={ submitting }>Submit</button>
</div>
</form>
);
}
}
function mapStateToProps({ auth }) {
return { errorMessage: auth.errors };
}
export default reduxForm({
form: 'signup',
warn,
validate
}, mapStateToProps, actions)(Signup);
signupUser action creator
export function signupUser(props) {
return dispatch => {
axios.post(`${apiRoot}users`, { user: { ...props } })
.then(response => {
const { status, errors, access_token, username } = response.data;
if (status === 'created') {
// handler
}
else {
dispatch(authError(errors));
}
})
.catch(err => dispatch(authError(err.message)));
}
}
Previously working code (5.3.1):
class Signup extends Component {
handleFormSubmit(formProps) {
this.props.signupUser(formProps);
}
render() {
const {
handleSubmit,
fields: {
email,
password,
password_confirmation,
username,
}
} = this.props;
return (
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<fieldset className="form-group">
<label>Username:</label>
<input className="form-control" {...username} />
{username.touched && username.error && <div className="error">{username.error}</div>}
</fieldset>
<fieldset className="form-group">
<label>Email:</label>
<input className="form-control" {...email} />
{email.touched && email.error && <div className="error">{email.error}</div>}
</fieldset>
<fieldset className="form-group">
<label>Password:</label>
<input type="password" className="form-control" {...password} />
{password.touched && password.error && <div className="error">{password.error}</div>}
</fieldset>
<fieldset className="form-group">
<label>Confirm Password:</label>
<input type="password" className="form-control" {...password_confirmation} />
{password_confirmation.touched && password_confirmation.error && <div className="error">{password_confirmation.error}</div>}
</fieldset>
<button action="submit">Sign up</button>
</form>
);
}
As you can see, apart from error handling they're very similar. Obviously, it's a major version change, I'm just not seeing why the action creator will be undefined. I attempted to change the connect call to use a mapDispatchToProp function but had the same result. When I inspect the props by throwing a debugger, none of the functions are mapped to the props. What happened?
Is there a way to capture the form handler submit? I can't think of a situation where you wouldn't want to capture the form submit.

The 6+ version introduced a change to how the reduxForm api works. Instead of it taking the form
export default reduxForm({
form: 'name-of-form',
fields: ['field1', 'field2'],
// other configs
}, mapStateToProps, actions)(ComponentName);
Instead, you should use the redux connect function like this if you want to connect redux properties and actions:
const form = reduxForm({
form: 'name-of-form',
// other configs
});
export default connect(mapStateToProps, actions)(form(ComponentName));
This is working for me now.

My way of connecting:
import { connect } from 'react-redux';
class Signup extends Component {
// ...
}
const SignupForm = reduxForm({
form: 'signup',
warn,
validate
})(Signup);
export default connect(
({ auth }) => ({
errorMessage: auth.errors
}),
{
...actions
}
)(SignupForm);

The API for reduxForm() changed from 5.x to 6.x.
With 5.x you use to be able to do exactly what you're doing now:
import * as actions from '../../actions';
function mapStateToProps({ auth }) {
return { errorMessage: auth.errors };
}
export default reduxForm({
form: 'signup',
warn,
validate
}, mapStateToProps, actions)(Signup);
With 6.x they only let you pass in the config object. However, the official redux-form documentation for 6.2.0 (see bottom of page) recommends the following:
import * as actions from '../../actions';
function mapStateToProps({ auth }) {
return { errorMessage: auth.errors };
}
Signup = reduxForm({
form: 'signup',
warn,
validate
})(SupportForm);
export default connect(mapStateToProps, actions)(Signup);

Related

React redux-form - error while testing - "TypeError: handleSubmit is not a function"

Im struggling to find a solution when testing a redux-form component. The problem is that only when I test simply if the component is rendering it gives me an error: "TypeError: handleSubmit is not a function", but the app is working fine, as expected.
I've tried to solve it just to make handleSubmit a function and not taking it from props, but then the app doesn't work. When the submit form is correct it must navigate to /landing page, but instead just re-render the login component.
The component:
import React, { Component } from 'react'
//import { Link } from 'react-router-dom'
import { Field, reduxForm } from 'redux-form'
import '../../style/style.css'
export class Login extends Component {
renderField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="username_field" placeholder={field.label} type="text" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
renderPasswordField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="password_field" placeholder={field.label} type="password" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
onSubmit(values) {
this.props.history.push('/landing')
}
// DOESN'T WORK!!!
// handleSubmit(formValues){
// //console.log(formValues);
// }
render() {
const { handleSubmit } = this.props
return (
<div>
<div className="login-form">
<form onSubmit={ /*this.*/handleSubmit(this.onSubmit.bind(this))}>
<h2 className="text-center">TQI Log in</h2>
<div className="form-group">
<Field id="username" name="username" label="username" component={this.renderField} />
</div>
<div className="form-group">
<Field id="password" name="password" label="password" component={this.renderPasswordField} />
</div>
<div className="form-group">
<button id="login_button" type="submit" className="btn btn-primary btn-block">Login </button>
</div>
</form>
</div>
</div>
);
}
}
function validate(values) {
const errors = {}
const dummyData = {
username: 'admin',
password: '123'
}
// Validate the inputs from values
if(!values.username) {
errors.username = "Enter a username"
} else if(values.username !== dummyData.username){
errors.username = "Wrong username"
}
if(!values.password) {
errors.password = "Enter a password"
} else if( values.username === dummyData.username && values.password !== dummyData.password){
errors.password = "Wrong password"
}
// if errors is empty, the form is fine to submit
// If errors has *any* properties, redux forms assumes form is invalid
return errors
}
export default reduxForm({
validate,
form: 'LoginForm'
})(Login)
The testing file:
import React from 'react'
import { shallow } from 'enzyme'
import { Login } from './login'
describe('<Login />', () => {
it('render the <Login /> component without crashing', () => {
const wrapper = shallow(<Login />)
expect(wrapper.length).toEqual(1)
})
// it('navigate to /landing page when the form is submit correctly', () => {
// })
})
You are consuming a prop function from your HOC reduxForm on your render method. But on the test file, you are importing the component without the HOC on top of it, which means that prop/function is not available. You have to provide Login with a mock handleSubmit prop function.
Try:
import React from 'react'
import { shallow } from 'enzyme'
import { Login } from './login'
describe('<Login />', () => {
it('render the <Login /> component without crashing', () => {
const wrapper = shallow(<Login handleSubmit={() => {}} />)
expect(wrapper.length).toEqual(1)
})
})
you need pass onSubmit on initialization:
export default reduxForm({
validate,
form: 'LoginForm',
onSubmit: this.onSubmit // here
})(Login)
or in props:
<Component>
<Login onSubmit={this.onSubmit}/>
</Component>
First export the redux-form decorated class
export const DecoratedLogin = reduxForm({
validate,
form: 'LoginForm'
})(Login);
Then use it instead of the plain class.
import { DecoratedLogin } from './login'
and you should be able to access the props from redux-form.

Uncaught TypeError: dispatch(...).then is not a function

Container component
import { connect } from 'react-redux';
import { signUpUser } from '../actions/userActions';
import Register from '../components/register';
function mapStateToProps(state) {
return {
user: state.user
};
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Register);
Register form
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router-dom';
import { reduxForm, Field, SubmissionError } from 'redux-form';
import { signUpUser } from '../actions/userActions';
//Client side validation
function validate(values) {
var errors = {};
var hasErrors = false;
return hasErrors && errors;
}
//For any field errors upon submission (i.e. not instant check)
const validateAndSignUpUser = (values, dispatch) => {
//console.log(values);
return dispatch(signUpUser(values))
.then((response) => {
console.log(response);
});
};
class SignUpForm extends Component {
render() {
const { handleSubmit } = this.props;
return (
<div className="col-md-6 col-md-offset-3">
<h2>Register</h2>
<form onSubmit={ handleSubmit(validateAndSignUpUser) }>
<div className ='form-group'>
<label htmlFor="firstname">Name</label>
<Field name="firstname" type="text" component= "input"/>
</div>
<div className ='form-group'>
<label htmlFor="username">Username</label>
<Field name="username" type="text" component= "input"/>
</div>
<div className ='form-group'>
<label htmlFor="password">Password</label>
<Field name="password" type="text" component= "input"/>
</div>
<div className="form-group">
<button className="btn btn-primary">Register</button>
<Link to="/" className="btn btn-error"> Cancel </Link>
</div>
</form>
</div>
)
}
}
export default reduxForm({
form: 'SignUpForm', // a unique identifier for this form
validate
})(SignUpForm)
Actions
import axios from 'axios';
export function signUpUser(user) {
console.log(user);
const url = `https://jsonplaceholder.typicode.com/posts`
const request = axios.get(url);
return {
type: 'Register_User',
payload: request
};
}
When I submit this form I am getting following error.
This app uses thunk, setup form reducer in combined reducer.
Where am I going wrong? I am new to redux-form and thunk
Uncaught TypeError: dispatch(...).then is not a function
The return value of dispatch is the return value of the inner function and in your case an object and not a promise. (https://github.com/reduxjs/redux-thunk#composition)
You have to return axios.get(...) (which basically returns a Promise) directly in the action in order to call then() on the return value of dispatch like you did in your example.
What I would suggest doing is to not put the request for the signup in a separate action because it's easier to handle the request right in the submit function of redux form. Otherwise it could be difficult to handle responses from the server with validation messages. I also think that you won't need to reuse the action in any other place, right? If you need to change something in the state after the registration, you can simply create another action like "signedUpUser" and pass some data to it.

How to reset form on submission with Redux Form?

I'm creating a basic portfolio website, that has a Contact form section, where people can send me a message to my email via Formspree. I'm using Redux Forms for this and the form works, but I'd like to reset the form fields and add some kind of 'success' message on successful submit.
I've tried 2 methods: 1) calling this.props.resetForm() and 2) calling dispatch(reset('ContactForm'). Neither have worked.
Here is the code:
contact.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { sendMail } from '../actions';
class Contact extends Component {
renderField(field) {
const { meta: { touched, error } } = field;
const className = `form-group ${touched && error && 'has-error'}`;
return (
<div className={className}>
<label>{field.label}</label>
{field.type == 'text' ?
<input
className='form-control'
type={field.type}
{...field.input}
/> :
<textarea
className='form-control'
rows='5'
{...field.input}
/>}
<div className='help-block'>
{touched && error}
</div>
</div>
);
}
onSubmit(values) {
this.props.sendMail(values, () => {
this.props.resetForm();
});
}
render() {
const { handleSubmit } = this.props;
return (
<div id='contact'>
<h1>Contact</h1>
<p>Feel free to drop me a mail. Whether it's work-related or about coding, tech, entrepreneurship, travel, football or life in general. I'm always looking to connect with other people.</p>
<div id='contact-form'>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label='Name:'
name='name'
type='text'
component={this.renderField}
/>
<Field
label='Email:'
name='email'
type='text'
component={this.renderField}
/>
<Field
label='Message:'
name='message'
type='textarea'
component={this.renderField}
/>
<button
type='submit'
className='btn btn-primary'
>
Submit
</button>
</form>
</div>
</div>
);
}
}
function validate(values) {
const errors = {};
if (!values.name) {
errors.name = "Enter a name"
}
if (!values.email) {
errors.email = "Enter an email"
}
if (!values.message) {
errors.message = "Enter a message"
}
return errors;
}
export default reduxForm({
form: 'ContactForm',
validate
})(
connect(null, { sendMail })(Contact)
);
actions/index.js:
import axios from 'axios';
import { reset } from 'redux-form';
export const MAIL_SENT = 'MAIL_SENT';
const ROOT_URL='https://formspree.io/'
const EMAIL = 'xxxxxx#gmail.com'
export function sendMail(values) {
const request = axios.post(`${ROOT_URL}${EMAIL}`, values);
return {
type: MAIL_SENT,
payload: true
};
dispatch(reset('ContactForm'));
}
You need to add a constructor to the class contact.
class Contact extends Component {
constructor(props) {
super(props);
}
...
};
Putting dispatch(reset('ContactForm')) after return syntax, it would never get called. By the way, the action creator is supposed to be a pure function. Adding dispatch action to it is not a good idea.
Hope this help.
Use
this.props.reset()
Using this works on resetting the form after submit.

Redux-form: Field value from state isn't submitted

I have a field (source-file) in redux-form which is being updated by a change in the application state. The state value is being properly delivered to the field but when clicking submit, only the first field (name) is submitted (which I fill in interactively).
What am I doing wrong here?
import React, { Component, PropTypes } from 'react';
import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import * as actions from '../../actions/job_actions';
import UploadPage from './upload_page';
const renderField = field => (
<div>
<label>{field.input.label}</label>
<input {...field.input}/>
{field.touched && field.error && <div className="error">{field.error}</div>}
</div> );
class JobForm extends Component {
handleFormSubmit(formProps) {
this.props.createJob(formProps); }
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<label>Title</label>
<Field name="name" component={renderField} type="text" />
<label>Input File</label>
<Field name="source_file" component={() => {
return (
<div class="input-row">
<input type="text" value={this.props.uploadedFile} />
</div>
)
}} />
<button type="submit" className="btn btn-
primary">Submit</button>
</form>
</div>
);
};
}
const form = reduxForm({ form: 'JobForm' });
function mapStateToProps({uploadedFile}) { return { uploadedFile }; }
export default connect(mapStateToProps, actions)(form(JobForm));
If you want redux-form to include your field, then you'll need to render it as an actual field (as you do with your renderField).
If you don't want to treat it as an actual field, then the redux-form author suggests injecting the values from state within your submit handler. Maybe something like this:
handleFormSubmit(formProps) {
this.props.createJob({ ...formProps, source_file: this.props.uploadedFile} );
}

Action creators not showing in "this.props" with redux-form 6.0.0-rc.3

When I used redux-form 5.3.1 I was able to access my action creators. But as I needed Material-UI, I updated it to 6.0.0-rc.3.
Changes from 5.3.1 to 6.0.0:
Removed fields from render():
const { handleSubmit, fields: { email, password, passwordConfirm }} = this.props;
Removed errors validation from under inputs:
{email.touched && email.error && <div className="error">{email.error}</div>}
Removed fields array from export default:
export default reduxForm({
form: 'signup',
fields: ['email', 'password', 'passwordConfirm'],
validate
}, mapStateToProps, actions)(Signup);
Added wrappers for Material-UI components:
import { renderTextField, renderCheckbox, renderSelectField, renderDatePicker } from '../material-ui-wrapper';
Code:
1 - console.log(this.props) logs no action creator - should log signupUser function
'use strict';
import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import * as actions from '../../actions';
import { renderTextField } from '../material-ui-wrapper';
import {Card, CardActions, CardHeader, CardText} from 'material-ui/Card';
import FlatButton from 'material-ui/FlatButton';
class Signup extends Component {
handleFormSubmit(formProps) {
console.log(this.props);
this.props.signupUser(formProps);
}
renderAlert() {
if (this.props.errorMessage) {
return (
<div className="error">
{this.props.errorMessage}
</div>
);
}
}
render() {
const { handleSubmit, pristine, submitting } = this.props;
return (
<form id="form" onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<Card>
<CardHeader title="Cadastre-se"/>
<CardText>
<Field name="email" component={renderTextField} label={"Email"} fieldType="text"/>
<Field name="password" component={renderTextField} label={"Senha"} fieldType="password"/>
<Field name="passwordConfirm" component={renderTextField} label={"Confirmação de senha"} fieldType="password"/>
{this.renderAlert()}
</CardText>
<CardActions>
<FlatButton type="submit" label="Criar!"/>
</CardActions>
</Card>
</form>
);
}
}
function validate(formProps) {
const errors = {};
if (!formProps.email || !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(formProps.email)) {
errors.email = 'Por favor, informe um email válido';
}
if (formProps.password !== formProps.passwordConfirm) {
errors.password = 'Senhas devem ser iguais.';
}
if (!formProps.password) {
errors.password = 'Por favor, informe uma senha.';
}
if (!formProps.passwordConfirm) {
errors.passwordConfirm = 'Por favor, confirme a sua senha.';
}
return errors;
}
function mapStateToProps(state) {
return { errorMessage: state.auth.error };
}
export default reduxForm({
form: 'signup',
validate
}, mapStateToProps, actions)(Signup);
EDIT
Changed:
export default reduxForm({
form: 'signup',
validate
}, mapStateToProps, actions)(Signup);
To:
Signup = reduxForm({
form: 'signup',
validate
})(Signup);
export default Signup = connect(mapStateToProps, actions)(Signup);
It worked. Is it the most correct way to fix this?
Instead of:
export default reduxForm({
form: 'signup',
validate
}, mapStateToProps, actions)(Signup);
Use:
Signup = reduxForm({
form: 'signup',
validate})(Signup);
export default Signup = connect(mapStateToProps, actions)(Signup);
PS: Might be a work around
You can use compose like follow
import { compose } from 'redux'
....
export default compose(
reduxForm({
form: 'survey',
}),
connect(mapStateToProps, actions)
)(Signup)
To get those values into your redux-form-decorated component, you will
need to connect() to the Redux state yourself and map from the data
reducer to the initialValues prop.
http://redux-form.com/6.0.0-alpha.4/examples/initializeFromState/
You can use constructor to bind method with your existing props.
you do not need to pass actions and initial state.
class Signup extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit(formProps) {
console.log(this.props);
this.props.signupUser(formProps);
}
render() {
const { handleSubmit, pristine, submitting } = this.props;
return (
<form id="form" onSubmit={this.handleFormSubmit}>
<Card>
<CardHeader title="Cadastre-se"/>
<CardText>
<Field name="email" component={renderTextField} label={"Email"} fieldType="text"/>
<Field name="password" component={renderTextField} label={"Senha"} fieldType="password"/>
<Field name="passwordConfirm" component={renderTextField} label={"Confirmação de senha"} fieldType="password"/>
</CardText>
<CardActions>
<FlatButton type="submit" label="Criar!"/>
</CardActions>
</Card>
</form>
);
}
}
}
Signup = reduxForm({
form: 'signup',
validate})(Signup);
export default Signup = connect()(Signup);

Resources