i create simple signin form for my website.
But i have a little problem.
When i type login and password and try send it to server i got error
Uncaught TypeError: this.props.signinUser is not a function
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import * as actions from '../../actions';
class Signin extends Component{
constructor(props){
super(props);
this.state= {};
this.onSubmit = this.onSubmit.bind(this);
}
renderField(field){
return(
<div className="form-group">
<label>{field.label}</label>
<input
className="form-control"
type="text"
{...field.input}
/>
</div>
);
}
onSubmit(e){
e.preventDefault();
let {login,password} = this.state;
//this.props.login(login,password);
console.log({login,password});
this.props.signinUser({login,password});
this.setState({
login: '',
password: ''
})
}
render(){
let {login,password} = this.state;
return(
<form onSubmit={this.onSubmit}>
<Field
label="Login:"
name="login"
component={this.renderField}
onChange={e => this.setState({login: e.target.value})}
/>
<Field
label="Password:"
name="password"
component={this.renderField}
onChange={e => this.setState({password: e.target.value})}
/>
<button action="submit" className="btn btn-primary"> Sign In </button>
</form>
);
}
}
function mapStateToProps(state) {
return { form: state.form };
}
export default reduxForm({
form: 'signin'
}, mapStateToProps, actions)(Signin);
And Actions.
import axios from 'axios';
export function signinUser({login,password}){
return function(dispatch){
axios.post('http://localhost:8080/auth', { login, password});
};
}
and finally reducer.
import { combineReducers } from 'redux';
import { reducer as fromReducer } from 'redux-form';
const rootReducer = combineReducers({
form: fromReducer
});
export default rootReducer;
I use react 16, react redux v5 and react-router v3, react form v7!
I think its problem with connect function from ReactForm!
The problem lies here
export default reduxForm({
form: 'signin'
}, mapStateToProps, actions)(Signin);
This should be
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
....
//remainder of the code
....
mapDispatchToProps =(dispatch)=>{
return {
actions : bindActionCreators(actions , dispatch)
};
}
Signin= connect(
mapStateToProps,
mapDispatchToProps
)(Signin);
export default reduxForm({
form: 'signin'
})(Signin);
You would need to change the call this.props.signinUser to this.props.actions.signinUser
More info at : https://redux-form.com/7.0.4/docs/faq/howtoconnect.md/
Related
I'm trying to make an edit user profile form in React + Redux Form + Firebase, my problem is that I can't enter any text in generated form in Redux Form. I try to enter some text to this but it's disabled...
Actually, initially I fetch user profile data (and it works). Then I'm trying to build edit form for editing profile.
class Settings extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchProfile();
}
renderField = field => {
const {
input,
label,
type,
meta: { touched, error }
} = field;
return (
<div className="formItem">
<p>{label}</p>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
);
};
onSubmit = values => {
const { uid } = this.props.room;
this.props.updateProfile(uid, values);
};
render() {
const { handleSubmit, pristine, submitting, invalid, room } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit)}>
<Field
label="Your name"
name="name"
type="text"
value={user.name}
component={this.renderField}
/>
<div className="formBtn">
<input
type="submit"
className="btn btnPrimary"
value="Save Changes"
disabled={pristine || submitting || invalid}
/>
</div>
</form>
</div>
);
}
}
const validate = values => {
const errors = {};
console.log(values);
if (!values.name) errors.name = "Enter your name";
return errors;
};
const mapDispatchToProps = { fetchProfile, updateProfile };
function mapStateToProps({ users }) {
return { user: users };
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(
reduxForm({ validate, form: "profileForm", enableReinitialize: true })(
Settings
)
);
You do not need to provide value to Field component, instead you need to pass values as initialValues in mapStateToProps:
function mapStateToProps({ users }) {
return {
user: users,
initialValues: {
name: users.name,
},
};
}
import { createStore, applyMiddleware, combineReducers } from "redux";
import reduxThunk from "redux-thunk";
import { reducer as formReducer } from "redux-form";
const rootReducer = combineReducers({
form: formReducer
});
const store = createStore(rootReducer, applyMiddleware(reduxThunk));
export default store;
I have added formReducer, it works.
I have this code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchWeather } from '../actions/index';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = { term: '' };
this.onInputChange = this.onInputChange.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onInputChange(event) {
this.setState({ term: event.target.value });
}
onFormSubmit(event) {
event.preventDefault();
this.props.fetchWeather(this.state.term);
this.setState({ term: '' });
}
render() {
return (
<form onSubmit={this.onFormSubmit} className="input-group">
<input
placeholder="Get a five-day forecast in your favorite cities"
className="form-control"
value={this.state.term}
onChange={this.onInputChange}
/>
<span className='input-group-btn'>
<button type="submit" className="btn btn-secondary">
Submit
</button>
</span>
</form>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchWeather }, dispatch)
}
export default connect(null, mapDispatchToProps)(SearchBar);
At one point in this tutorial, the author says that mapDispatchToProps hooks up our Action Creator fetchWeather to our component. But does it? Doesn’t it dispatch the action from our Action Creator to our reducers?
What is actually making this available in our component as props?
You can have
const mapDispatchToProps = { fetchWeather };
instead of
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchWeather }, dispatch)
}
Anyway this is the block that makes your action creator available via this.props.
I'm refactoring some code on my first react app, which is a simple login form that redirects and renders a list of items on successful login. I have a form container, connected to the redux store, that should render a login form - decorated with reduxForm. I don't understand why the form is not rendering.
index.jsx
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('app')
);
App.jsx
import {LoginFormContainer} from './components/Login/LoginFormContainer'
class App extends Component {
render() {
return (
<Switch>
<Route exact path='/' component={LoginFormContainer}/>
</Switch>
)
}
}
export default App
LoginFormContainer.jsx
import LoginFormComponent from './LoginFormComponent';
import {reduxForm} from 'redux-form/immutable';
import {withRouter} from 'react-router-dom';
let LoginFormContainer = reduxForm({
form: 'login',
})(LoginFormComponent)
const mapStateToProps = null
const mapDispatchToProps = dispatch => {
return {
onSubmit: loginFormValues => {
dispatch(loginUser(loginFormValues))
}
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) =>
Object.assign({}, stateProps, dispatchProps, ownProps)
LoginFormContainer = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(LoginFormContainer))
export default LoginFormContainer
LoginFormComponent.jsx
class LoginForm extends Component {
render() {
return (
<Row>
<Col xs={12} md={12}>
<form>
<FormGroup>
<Field
name="username"
type="email"
component={renderField}
label="Username"
validate={[required]}
/>
</FormGroup>
<FormGroup>
<Field
name="password"
type="password"
component={renderField}
label="Password"
validate={[required]} />
</FormGroup>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</Col>
</Row>
)
}
}
export default LoginForm
Simply removing the curly braces from the import of LoginFormContainer in App.jsx fixed the issue. import LoginFormContainer from './components/Login/LoginFormContainer'
Just a few minor exports/imports missing.
LoginFormContainer.jsx
import LoginFormComponent from './login_form_component';
import {reduxForm} from 'redux-form/immutable';
import {withRouter} from 'react-router-dom';
import { connect } from 'react-redux'; // missing import
// need to export these variables to be used below
export const LoginFormContainer = reduxForm({
form: 'login',
})(LoginFormComponent)
export const mapStateToProps = null
export const mapDispatchToProps = dispatch => {
return {
onSubmit: loginFormValues => {
dispatch(loginUser(loginFormValues))
}
}
}
// this is not needed
// export const mergeProps = (stateProps, dispatchProps, ownProps) =>
// Object.assign({}, stateProps, dispatchProps, ownProps)
// withRouter is not needed unless you need any of the
// functionality here https://reacttraining.com/react-router/web/api/withRouter
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer)
LoginFormComponent.jsx
Not sure if you just did not include the imports for this file. Make sure to have this in there
import React, { Component } from 'react';
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);
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);