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.
Related
Everything Seems Fine to me, the Console logs work in the Action and in the Reducer but the Chrome Redux tools show no action being trigged and no state updates.
Here is my Component Class
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { SimpleGrid,Box,Select,Input,InputGroup,InputRightAddon,Text,Heading ,Button,NumberInput,NumberInputField,} from "#chakra-ui/core";
import { Footer } from '../../layout/Main/components';
import {submitUserInput} from '../../utils/actions/userInput'
import PropTypes from 'prop-types';
class UserInput extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
yieldStress:0,
thickness:0,
diameter:0,
pMAX:0
}
this.handleChange = this.handleChange.bind(this)
}
handleSubmit=(event)=>{
event.preventDefault();
console.log(this.state);
this.props.submitUserInput(this.state);
}
handleChange=(event)=>{
this.setState({[event.target.name]:event.target.value});
}
render(){
const materialsList = this.props.materials.map((material)=>(
<option value={material.yieldStress} key={material.id}>{material.name}</option>
));
return (
<Box>
<Heading as="h4" size="md">Cylinder Details</Heading>
<Text>{this.props.thickness}</Text>
<form onSubmit={this.handleSubmit} >
<Select placeholder="Select Material of Cylinder" isRequired name="yieldStress" onChange={this.handleChange}>
{materialsList}
</Select>
<SimpleGrid columns={2} spacing={8} padding="16px 0px">
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Thickness of Cylinder" roundedRight="0" isRequired name="thickness" onChange={this.handleChange}/>
</InputGroup>
</Box>
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Diameter of Cylinder" roundedRight="0" isRequired name="diameter"onChange={this.handleChange}/>
</InputGroup>
</Box>
</SimpleGrid>
<SimpleGrid columns={2} spacing={8} padding="0px 0px">
<Box>
<InputGroup>
<InputRightAddon children={<Text paddingTop="12px">(N/mm)<sup>2</sup></Text>} padding="0 4px"/>
<NumberInput precision={2} step={0.2} >
<NumberInputField placeholder="Maximum pressure " roundedRight="0" isRequired name="pMAX" onChange={this.handleChange}/>
</NumberInput>
</InputGroup>
</Box>
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Factor of Saftey" roundedRight="0" isRequired name="FOS"onChange={this.handleChange}/>
</InputGroup>
</Box>
</SimpleGrid>
<Box padding="8px 0" >
<Button variantColor="teal" float="right" border="0" type="submit">Submit</Button>
</Box>
</form>
<Footer />
</Box>
)
}
}
UserInput.protoTypes = {
submitUserInput: PropTypes.func.isRequired,
thickness: PropTypes.string.isRequired,
}
const mapStateToProps = (state) => ({
thickness: state.userInput.thickness
})
const mapDispatchToProps = dispatch => {
return {
submitUserInput: (userInput)=> dispatch(submitUserInput(userInput))
}}
export default connect(mapStateToProps, mapDispatchToProps)(UserInput)
Here is My Action File
import { SUBMIT_REQUEST_SENT,SUBMIT_SUCCESS,SUBMIT_FAILURE} from '../types'
export const submitUserInput = ({yieldStress, FOS, diameter,pMAX,thickness }) => async (dispatch) => {
dispatch(submitRequestSent());
try {
console.log("Printing object in action")
console.log({yieldStress, FOS, diameter,pMAX,thickness }) //This is Printed
dispatch({
type: SUBMIT_SUCCESS,
payload:{yieldStress, FOS, diameter,pMAX,thickness }
}
);
}
catch (error) {
dispatch({
type: SUBMIT_FAILURE,
payload: error.message
})
throw error;
}
}
export const submitRequestSent = () =>
({
type: SUBMIT_REQUEST_SENT,
})
And Here is the UserInput Reducer
import {
SUBMIT_REQUEST_SENT,SUBMIT_SUCCESS,SUBMIT_FAILURE
} from '../../actions/types'
const initialState = {
userInput:{
yieldStress:0,
FOS:0,
diameter:0,
pMAX:0,
thickness:0
}
}
export default function (state = initialState, action) {
switch (action.type) {
case SUBMIT_REQUEST_SENT:
return {
...state,
}
case SUBMIT_SUCCESS:
console.log("Submit has been sucessfull And below is the Payload"); //This is Printed
console.log(action.payload)//This is Printed
return {
...state,
userInput:action.payload
}
case SUBMIT_FAILURE:
return {
...state,
error: action.payload
}
default:
return state;
}
}
And my root Reducer
import {combineReducers} from 'redux';
import authReducer from '../authReducer'
import userInputReducer from '../userInputReducer';
export default combineReducers(
{
auth:authReducer,
userInput:userInputReducer
}
)
I will Appreciate any applicable solution/Explanation..thanks
Within your state you have a property called userInput, which is the state section controlled by userInputReducer. That section has its own property which is also called userInput that stores the actual data. It that what you intended?
If so, you need to map properties like this:
const mapStateToProps = (state) => ({
thickness: state.userInput.userInput.thickness
})
If not, you need to rework your userInputReducer and initialState to remove the double nesting.
Everything Seems Fine to me, the Console logs work in the Action and in the Reducer but the Chrome Redux tools show no action being trigged and no state updates.
This sounds to me like an issue with dispatching an asynchronous thunk action. Redux cannot handle these types of actions without the appropriate middleware. When you create the store, it should be something like this:
import reducer from "./reducer";
import {createStore, applyMiddleware} from "redux";
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
I copy and posted your reducer code with a store set up as above. I used a dummy component ( a button that increments thickness when clicked ) and I was not able to reproduce your issue. That leads me to believe that the issue is in the configuration of the store itself.
I am using react/redux with redux-form and for some reason the input values are not showing on my edit form.
I console.log my post and it shows that they are there, but for some reason it is not working. I will post code below.
edit component:
import React , { Component } from 'react';
import * as actions from '../../actions/posts_actions';
import { reduxForm, Field } from 'redux-form';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
class EditPost extends Component {
componentDidMount() {
const {id} = this.props.match.params;
this.props.getOnePost(id);
}
renderField(field) {
const { meta: {touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<label><strong>{field.label}:</strong></label>
<input
className="form-control"
type={field.type}
value={field.value}
{...field.input}
/>
<div className="text-help">
{ touched ? error : ''}
</div>
</div>
)
}
onSubmit(values) {
const {id} = this.props.match.params;
this.props.updatePost(values, id, () => {
this.props.history.push(`/posts/${id}`);
});
}
render() {
const {handleSubmit} = this.props;
const {post} = this.props;
if(!post) {
return <div> Loading... </div>;
}
console.log(post);
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Title"
name="title"
type="text"
value={post.title}
component={this.renderField}
/>
<Field
label="Content"
name="content"
type="text"
value={post.content}
component={this.renderField}
/>
<button type="submit" className="btn btn-success">Submit</button>
<Link to={`/posts/${post._id}`} className="btn btn-danger">Cancel</Link>
</form>
);
}
}
function validate(values) {
const errors = {};
if(!values.title) {
errors.title = "Enter a title!";
}
if(!values.content) {
errors.content = "Enter some content please!";
}
return errors;
}
function mapStateToProps({ posts }, ownProps) {
return { post: posts[ownProps.match.params.id] };
}
export default reduxForm({
validate,
form: 'editform'
})(connect(mapStateToProps, actions)(EditPost));
Instead of setting the value directly, pass an initialValues prop:
function mapStateToProps({ posts }, ownProps) {
const post = posts[ownProps.match.params.id]
return {
post,
initialValues: {
...post
}
};
}
remove value props from Fields in your form. So long as the properties on your Post object are the same as the name of the field you want to populate, you can just use object spread. If they are different, you'll have to map them appropriately
initialValues: {
contentField: post.content
}
<Field name="contentField" />
initial values have to be set before you use the reduxForm enhancer. Also, because I know it'll come up (and trust me, it always comes up eventually), if you want your form values to update if your model updates, you'll have add enableReinitialize: true to reduxForm's config
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);
i try to figure out what's wrong in my code. can someone help me find out whats wrong. i use webstorm for text editing, and it's show "unresolve variable" in
this.props.loginUser(value);
is there any related to this?
this is my code :
import React, { Component } from 'react'
import {reduxForm, Field} from 'redux-form';
import { loginUser } from '../actions/index';
import { Stores } from '../Stores';
import {connect} from 'react-redux';
const validate = values => {
const errors = {};
if (!values.email) {
errors.email = 'Required'
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address'
}
if (!values.password) {
errors.password = 'Required'
}
return errors
};
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} className="form-control"/>
{touched && error && <span className="alert alert-danger">{error}</span>}
</div>
</div>
);
function submit(value){
console.log(value);
this.props.loginUser(value); //didn't work
// Stores.dispatch(loginUser({email,password})); //this method work
}
class LoginV6 extends Component{
render() {
const {handleSubmit, pristine, reset, submitting} = this.props;
return (
<div className="row">
<div className="col-md-6">
<form onSubmit={handleSubmit(submit)}>
<div className="form-group">
<Field
name="email"
type="text"
component={renderField}
label="Email"
/>
</div>
<div className="form-group">
<Field
name="password"
type="password"
component={renderField}
label="Password"
/>
</div>
<div>
<button type="submit" className="btn btn-primary" disabled={pristine||submitting}>
Login
</button>
<button type="button" className="btn btn-primary" disabled={pristine || submitting}
onClick={reset}>
Clear Values
</button>
</div>
</form>
</div>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
loginUser() {
dispatch({loginUser(value));
}
}
};
function mapStateToProps(state) {
return {
errorMessage: state.auth.error,
authenticated:state.auth.authenticated
}
}
LoginV6 = reduxForm({
form:'LoginV6',
validate
})(LoginV6);
export default LoginV6 = connect(mapStateToProps, mapDispatchToProps)(LoginV6);
this is my action code :
import axios from 'axios';
import jwtdecode from 'jwt-decode';
import {browserHistory} from 'react-router';
import {
AUTH_USER,
AUTH_ERROR,
USER_INFO_SUCCESS,
USER_INFO,
LOGOUT_USER,
GET_TABPANEL,
GET_SETUP_TABTITLES,
} from './types';
const ROOT_URL = 'http://localhost:8000';
// User and Auth actions
//
export function loginUser({email,password}){
return function(dispatch){
axios.post(`${ROOT_URL}/api/auth/login`,{email,password})
.then(response => {
dispatch({type: AUTH_USER,
payload:response.data.token
});
localStorage.setItem('laravel_user_token',response.data.token);
console.log('Login Success');
browserHistory.push("/");
}).catch(()=>{
dispatch(authError("Empty Required Field"));
});
}
}
if i use Stores.dispatch(loginUser(value)); it works.
Change your code like this:
class LoginV6 extends Component{
constructor(){
this.submit = this.submit.bind(this); //Because ES6 is not auto bind
}
submit(value){
//...your code
this.props.loginUser(value); //it will work
// or "loginUser" is an action
this.props.dispatch(loginUser(value)); //see the new function mapDispatchToProps
}
render(){
//...your code
}
}
// ....
// instead map a new object, we map dispatch directly
// with this function, we avoid duplicated/confused about actions in case you have many actions
const mapDispatchToProps = (dispatch) => {
return {
dispatch
}
};
i already fix this by change my code to this :
class LoginV6 extends Component{
submit = (data) => {
console.log(data);
this.props.loginUser(data);
}
render() {
const {handleSubmit, pristine, reset, submitting} = this.props;
return (
<div className="row">
<div className="col-md-6">
<form onSubmit={handleSubmit(this.submit)}>
..............................the rest of code..............................................
const mapDispatchToProps = (dispatch) => {
return {
loginUser: (data) => {
dispatch(loginUser(data))
}
}
}
function mapStateToProps(state) {
return {
errorMessage: state.auth.error,
authenticated:state.auth.authenticated
}
}
LoginV6 = reduxForm({
form:'LoginV6',
validate
})(LoginV6);
export default LoginV6 = connect(mapStateToProps, mapDispatchToProps)(LoginV6);
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);