Redux- Form: Unable to Dispatch Action on Submit - reactjs

I'm using redux-form in my React / Redux application, and I'm trying to figure out how to dispatch an action on submit.
I have been able to trigger the handleSubmit function, and the submit function that I pass to it is executed, but the 'submitFormValues' action is not called.
I've tried using mapDispatchToProps and connect(), but that doesn't work either.
The Redux-Form actions execute (START_SUBMIT, STOP_SUBMIT, SET_SUBMIT_SUCCEEDED), but my own action creator is never executed.
Here's the form:
import React from "react";
import { Field, reduxForm } from "redux-form";
import {submitFormValues} from "../../../actions/BasicFormActions";
function submit(values) {
//Can log the values to the console, but submitFormValues actionCreator does not appear to be dispatched.
return new Promise(function(resolve) { resolve(submitFormValues(values))} )
}
const renderField = ({ input, label, type, meta: {touched, error} }) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
const BasicForm = (props) => {
const { error, handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit(submit)}>
<Field name="firstName" type="text" component={renderField} label="First Name"/>
<Field name="lastName" type="text" component={renderField} label="Last Name"/>
{error && <strong>{error}</strong>}
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>Clear</button>
</div>
</form>
)
}
export default reduxForm({
form: "basicForm",
submit
})(BasicForm)
Here's the action Creator (uses Thunk). I'm able to dispatch these actions successfully, just not from the form.
export const submitFormValues = (values) => (dispatch) =>
getData("submitApproveForm", values).then(response => {
dispatch(formSubmissionError(response))
}).catch(error => {
throw (error);
});
const formSubmissionError = (response) =>
({
type: types.FORM_SUBMISSION_ERROR,
basicFormResponse: { ...response}
});
export const getData = (apiName, args) =>
fetch(settings.basePath + getUrl(apiName, args))
.then(response =>
response.json()
).catch(error => {
return error;
});
Finally my reducer:
import * as types from "../actions/ActionsTypes";
import initialState from "./initialState";
const basicFormReducer = (state = initialState.basicFormResponse, action) => {
switch (action.type) {
case types.FORM_SUBMISSION_ERROR:
return {...state, "errors": action.basicFormResponse}; // returns a new state
case types.FORM_SUBMISSION_SUCCESS:
return {...state, "successes": action.basicFormResponse}; // returns a new state
default:
return state;
}
};
export default basicFormReducer;
Thank you in advance for your help.

Redux Form does not dispatch anything in the onSubmit callback. This is because we make no assumptions regarding how you would like to deal with your form submissions.
You can, however, use the dispatch argument and... dispatch your action!
function submit(values, dispatch) {
return dispatch(submitFormValues(values));
}

Related

Trying to pass parent to child in dumb component reusable component Redux

In my case I use Redux and want to make a reusable component that can pass from parent to child component when onChange function is being called. But not sure how to do that in dumb component, so I want to have an input field that when a user type some letters/numbers in that field. It should call the parent onChange function and based on that it should find the right component and pass that value to that component. Allmost any components except the CheckBox do have an onChange function. Is there a way to write that DRY and in a reusable way. There are more codes in the snippets below, please scroll down more.
const Parent = () => {
const checkForm = () => {
console.log('checkForm');
}
const onChange = (e) => {
getValue(e.target.value, e.target.name);
}
return (
<div className="App">
<form onSubmit={checkForm}>
<Name onChange={onChange}/>
<Surname onChange={onChange}/>
<Email onChange={onChange}/>
<PromoCode onChange={onChange}/>
<CheckBox/>
<button type="submit">Submit</button>
</form>
</div>
);
}
const mapDispatchToProps = dispatch => ({
getValue: (inputValue, componentName) => dispatch(getValue(inputValue, componentName))
});
const Name = () => {
return (
<div>
Name
<input
name='name'
type="text"
// value={this.state.name}
// onBlur={nameOnBlur}
onChange={onChange}
placeholder="Enter first name"
/>
</div>
)
}
const mapDispatchToProps = dispatch => ({
getValue: (inputValue, componentName) => dispatch(getValue(inputValue, componentName))
});
const mapStateToProps = (state) => {
return {
curInputValue: state.form.name || []
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Name);
export const form = (state = initialState, action) => {
switch(action.type) {
case types.GET_VALUE: {
return {
...state,
[action.componentName]: action.value
}
}
default:
return state;
}
}
To summarize the question I believe you are asking:
How do I write an onChange handler that can handle updating a variety of textfields inputs.
This could look something like:
const [formData, setFormData] = useState({name: '', surname: '', etc.})
const handleChange = (name) => event => {
setFormData({...state, [name]: event.target.value })
}
return (
<div>
<Name value={name} onChange={handleChange('name')} />
<Surname value={surname} onChange={handleChange('surname')} />
{...content}
</div> )
The key thing to note is that we store state in an object, one event handler is called for each time and it changes based on the param used.
If you are interested in using redux with this (I would advise against that unless you absolutely have to given the complexity it adds), the code would look similar but you would instead have to dispatch for each onChange fired and and the value would then be something like value={props.form.name}

Redux form Validation Field does not work correctly

I use redux form, in my app, and I got an issue when try to validate on Field Validate Level. I try to use a synchronous validation function in reduxForm().
Previously all working correctly, I use default submit on Button click instead of passing onSubmit, because I need to pass some props to onSubmit, and I decided use from mapDispatchToProps directly. Now on save it validation does not pass error to field
export const validate = ({name})=> {
let errors = {}
if (!name)
errors.name = <FormattedMessage id={'common.error.empty'} />
return errors
}
const returnToList = ({history, filter}) => history.push({pathname: '/setup/rooms', state: filter})
export class AddOrEditRoom extends Component {
render = () => {
let {intl, submit, onSubmit, handleSubmit, filter, currentRoomValues} = this.props
debugger
const actions =
<Fragment>
<Button
variant='contained'
name='cancel'
onClick={() => returnToList(this.props)}
>
<FormattedMessage id='common.cancel' />
</Button>
<Button
name='save'
onClick={() => onSubmit({currentRoomValues, filter})}
/*onClick={submit} - previous implementation, but I need pass props `filter` to onSumbit*/
>
<FormattedMessage id='common.save' />
</Button>
</Fragment>
return (
<Page title={<FormattedMessage id='rooms.roomInfo' />} actions={actions} footer={actions}>
{isLoadingInProgress && <LinearProgress variant='indeterminate' />}
<form onSubmit={handleSubmit}>
<Field
required
name='name'
label={<FormattedMessage id='name' />}
component={renderTextField}
/>
</form>
</Page>
)
}
}
export const mapStateToProps = (state, {match: {params: {roomId}}}) => {
let initialValues = roomId && state.entities.rooms[roomId]
const form = `room-${roomId}`
const selector = formValueSelector(form)
return {
roomId,
initialValues,
form,
filter: filterSelector(state.rooms),
currentRoomValues: {...initialValues, name: selector(state, 'name')},
}}
export const mapDispatchToProps = (dispatch, {match: {params: {roomId}}, history}) => ({
init: () => dispatch(RoomActions.get(roomId)),
onSubmit: async ({currentRoomValues, filter}) => {
const {success, response} = await dispatch(RoomActions.createOrUpdate({...currentRoomValues, ...getTrimmedStringFields(currentRoomValues)}))
if (!success) {
const statusCode = response?.result?.statusCode
const error = {}
if (statusCode === ValidationStatus.DuplicateName)
error.name = <FormattedMessage id='rooms.duplicateName' />
else
error._error = <FormattedMessage id='rooms.failedMessageWithStatusCode' values={{statusCode}} />
throw new SubmissionError(error)
}
returnToList({history, filter})
},
})
export default compose(
connect(mapStateToProps, mapDispatchToProps),
reduxForm({validate, enableReinitialize: true, keepDirtyOnReinitialize: true}),
initializable,
)(AddOrEditRoom)
renderTextField
export const renderTextField = ({input, label, meta: {touched, error}, ...other}) =>
<TextField
label={label}
error={!!(touched && error)}
helperText={!!(touched && error) && error}
{...input}
{...other}
/>
So I found a problem, I just need to pass my own function to handleSubmit to button and it will run validation, both sync and async, and, if the form is valid, it will call this.props.onSubmit(data) with the contents of the form data.
onClick={handleSubmit(room => onSubmit({currentRoomValues, filter})}

unable to throw SubmissionError in redux form

I'm trying to make a SubmissionError show up in my redux-form form (if backend server response isn't status code 200), but I'm not sure how to probably due to my beginner level experience in React, Redux, React Redux, Redux Form, and Axios. I've already tried a lot of things (and currently reverted back from them) by looking through various SO posts and Github issues. If someone/people can help me out, that'd be greatly appreciated!
The current error I'm getting is this promise uncaught message:
error screenshot
Although there are more files in my project, here are the relevant ones here (minus the import statements):
ssoLoginPage.js
const renderField = (field) => {
const { input, label, name, type, meta: { touched, error } } = field;
const placeholder = `Enter ${label} here`;
return(
<div className="slds-form-element slds-m-bottom--large">
<label className="slds-form-element__label" htmlFor={label}>
{label}
</label>
<div className="slds-form-element__control">
<input
key={name}
id={name}
className="-input"
type={type}
placeholder={placeholder}
{...field.input}
/>
</div>
{touched && error && <span>{error}</span>}
</div>
);
};
class SsoLoginPage extends React.Component {
constructor(props) {
super(props);
}
onSubmit(values) {
this.props.ssoLogin(values, () => {
throw new SubmissionError({_error: "One or more credentials are incorrect"});
});
}
render() {
console.log("ssoLoginStatus:",this.props.ssoLoginStatus);
const { error, handleSubmit} = this.props;
return (
<IconSettings iconPath="/slds-static-2.6.1/assets/icons">
<div>
<Modal
title="SSO Login"
isOpen={!this.props.ssoLoginStatus}
onRequestClose={this.toggleOpen}
dismissOnClickOutside={false}
>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
name="username"
type="text"
component={renderField}
label="Username"
/>
<Field
name="password"
type="password"
component={renderField}
label="Password"
/>
<Field
name="verificationCode"
type="password"
component={renderField}
label="Verification Code"
/>
{error && <strong>{error}</strong>}
<Button type="submit" label="Login" variant="brand" />
</form>
</Modal>
</div>
</IconSettings>
);
}
}
function mapStateToProps({ssoLoginStatus}) {
return ssoLoginStatus;
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ssoLogin},dispatch);
}
export default reduxForm({
form: "ssoForm" // a unique identifier for this form
})(
connect(mapStateToProps,mapDispatchToProps)(SsoLoginPage)
);
settings.setAppElement("#root");
reducersIndex.js
const rootReducer = combineReducers({
ssoLoginStatus: ssoReducer,
form: formReducer
});
export default rootReducer;
ssoReducer.js
const ssoProcess = (state, action) => {
console.log("ssoLogin action:",action);
switch(action.payload.status) {
case 200:
//everything's okay
console.log("reaching 200 case?");
return {...state, "ssoLoginStatus": true};
default:
//not sure what to do here currently
return state;
}
};
export default function(
state={"ssoLoginStatus": false}, action) {
console.log("action type:",action.type);
switch (action.type) {
case SSO_LOGIN:
console.log("return something here hopefully");
return ssoProcess(state, action);
default:
return state;
}
}
actionsIndex.js
export const SSO_LOGIN = "SSO_LOGIN";
export const BASE_URL = "http://localhost:8080";
export function ssoLogin (values, callback) {
console.log("went inside ssologin action creator!",
values.username,
values.password,
values.verificationCode);
const url = `${BASE_URL}/ssologin?username=${values.username}&password=${values.password}&verificationCode=${values.verificationCode}`;
console.log("url w/ params:",url);
const request = axios
.post(url,{responseType: "json"})
.then(response => {
console.log("axios response: ",response);
return response;
})
.catch(error => {
console.log("sso error: ",error);
return callback();
});
return {
type: SSO_LOGIN,
payload: request
};
}

Why is Redux Form not setting the value for submitting?

Below is my RequestAnInvite redux-form. The problem is when I submit the form, submitting is never changed to true. You can see I have a log below, which is always outputting false.
What am I doing wrong with redux-form to cause submitting to never set to true when I submit the form?
class RequestAnInvite extends React.Component {
componentDidMount() {
this.props.dispatch(loadTitles());
}
handleSubmit(data) {
console.log(data);this.props.dispatch(requestInvitationsActions.createInvitationRequest(data));
}
render() {
const { handleSubmit, submitting } = this.props;
console.log('submitting: ' + submitting);
return (
<div className="container-fluid h-100">
<form onSubmit={handleSubmit(this.handleSubmit.bind(this))}>
<Field
name="email"
type="text"
component={renderField}
label="Email"
placeholder="xxx#acme.com"
/>
<p>submitting: {submitting}</p>
<div className="form-group form-group-actions">
<button type="submit" className="btn btn-primary" disabled={submitting}>
{submitting ? 'Requesting...' : 'Request an Invite'}
</button>
</div>
</form>
</div>
);
}
}
RequestAnInvite = reduxForm({
form: 'RequestAnInvite',
validate,
})(RequestAnInvite);
const mapStateToProps = state => {
return {
titles: state.titles
};
};
const mapDispatchToProps = (dispatch) => bindActionCreators({
...requestInvitationsActions,
}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(RequestAnInvite);
Update 1
handleSubmit(data) {
this.props.createInvitationRequest(data)
.then((response) => {
console.log(response)
}, (error) => {
});
}
From redux-form docs:
Whether or not your form is currently submitting. This prop will only work if you have passed an onSubmit function that returns a promise. It will be true until the promise is resolved or rejected.
Your handleSubmit is just dispatching an action so it has no way of knowing when it is submitting

Redux form navigate on submit successfully

I'm struggling to understand where is the best practice to handle success submit and navigate to the next page.
I'm working on a login form:
class LogInComponent extends Component {
render() {
const {dispatch} = this.props;
const {loginError, handleSubmit, pristine, reset, submitting} = this.props;
return (
<form onSubmit={handleSubmit((values) => dispatch(login(values))) }>
<Field name="username" type="text" component={renderField} label="Username" />
<Field name="password" type="password" component={renderField} label="Password" />
{loginError && <strong>{loginError}</strong>}
<div>
<button type="submit" disabled={submitting}>Log In</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>Clear Values</button>
</div>
</form>
)
}
And the action:
export function login(values) {
const email = values.username;
const password = values.password;
return dispatch => {
dispatch(loginSubmit());
firebaseAuth.signInWithEmailAndPassword(email, password).then(function(user) {
dispatch(signInSuccess(user));
}).catch(function(error) {
dispatch(signInError(error));
});
};
}
export function loginSubmit() {
return {
type: SIGN_IN_SUBMIT
};
}
export function signInError(error) {
return {
type: SIGN_IN_ERROR,
payload: error.message
};
}
export function signInSuccess(user) {
return {
type: SIGN_IN_SUCCESS,
payload: user
};
}
If the response was successful, I would like to navigate to the next page. But where should the navigation be? not from the reducer or action, so only from component, but the action does not return response by design..
Am I missing something?
Create a composing function to couple your login code and navigation logic, and dispatch that function on form submit.
Modify the actions file as below:
import { browserHistory } from './react-router';
// no export needed, this is a #private function
function login(values) {
const email = values.username;
const password = values.password;
return dispatch => {
dispatch(loginSubmit());
return firebaseAuth.signInWithEmailAndPassword(email, password)
.then((user) => dispatch(signInSuccess(user)))
.catch((error) => dispatch(signInError(error)));
};
}
export function loginAndRedirect(loginParams) {
return dispatch => dispatch(login(loginParams))
.then(() => browserHistory.push('/success/path'))
.catch(() => browserHistory.push('/failure/path'));
}
// ... other actions
Now in our component we will do this:
<form onSubmit={handleSubmit((values) => dispatch(loginAndRedirect(values))) }>

Resources