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))) }>
Related
i am using react hook form to create a from with multiple pages
i am able to create it and it working with all filed except file-input-type how do i pass i file from another page and finaly pass it to api in the final page
i a have actualy 3 pages i have only added the 1st and final page (fist page has the file input filed and final page has the api to which it must be submitted)
form with file upload field
import { useForm } from "react-hook-form";
export default function Form(props) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
<input style={styles.file} type="file" />
</div>
<input {...register("name", { required: true })}
name="husband"
value={props.getState("name")}
onChange={props.handleChange}
style={styles.input}
type="text"
placeholder="Name"
/>
<input onClick={handleSubmit(props.next)}
type="submit"
value="Next"
/>
form with submit button and api to which it must be uploaded
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const submitValue = (e) => {
// e.preventDefault();
props.state.patient = "true";
const data = props.state;
axios
.post("registration/", data)
.then(() => {
alert("updated data");
window.location = "/clogin";
})
.catch((error) => {
//var my_obj_str = JSON.stringify(error.response.data);
alert(JSON.stringify(error.response.data));
});
};
codesandbox
https://codesandbox.io/s/wizardly-worker-zicnr?file=/src/App.js
There are 2 options
Single form wraps all the steps
You can wrap the <Steps /> component with one form. Make the <Step />s components stateless that accepts onInputChange which will called upon input changes.
onInputChange call setValue to update the form's state.
When the form submitted, you have the file (among other inputs) so you can send it to the server.
import { useEffect } from "react";
import { Steps, StepsProvider, useSteps } from "react-step-builder";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, setValue } = useForm();
useEffect(() => {
register("myFile");
}, [register]);
const onInputChange = (e) => {
setValue(e.target.name, e.target.files[0]);
};
const onSubmit = (data) => {
alert(`Your file name: ${data.myFile.name}, size: ${data.myFile.size}`);
};
return (
<StepsProvider>
<form onSubmit={handleSubmit(onSubmit)}>
<MySteps onInputChange={onInputChange} />
</form>
</StepsProvider>
);
}
const MySteps = ({ onInputChange }) => {
const { next, prev } = useSteps();
return (
<Steps>
<div>
<h1>Step 1</h1>
<input type="file" name="myFile" onChange={onInputChange} />
<button onClick={next}>Next</button>
</div>
<div>
<h1>Step 2</h1>
<button>Submit</button>
</div>
</Steps>
);
};
https://codesandbox.io/s/gifted-wozniak-of14l?file=/src/App.js
Multiple forms in each step
If you want need to have a form inside each step, you can pass the step's data up to the parent when upon step's form submission. Still the parent has the form state so it can handle when all the steps completed
import { useRef } from "react";
import { Steps, StepsProvider, useSteps } from "react-step-builder";
import { useForm } from "react-hook-form";
export default function App() {
const formState = useRef();
const onStepComplete = (data) => {
formState.current = {
...formState.current,
...data
};
};
const onComplete = (data) => {
onStepComplete(data);
const {
name,
myFile: [file]
} = formState.current;
alert(
`Your name: ${name} Your file name: ${file.name}, size: ${file.size}`
);
};
return (
<StepsProvider>
<MySteps onStepComplete={onStepComplete} onComplete={onComplete} />
</StepsProvider>
);
}
const MySteps = ({ onStepComplete, onComplete }) => {
return (
<Steps>
<Step1 onStepComplete={onStepComplete} />
<Step2 onComplete={onComplete} />
</Steps>
);
};
const Step1 = ({ onStepComplete }) => {
const { register, handleSubmit } = useForm();
const { next } = useSteps();
const onSubmit = (data) => {
onStepComplete(data);
next();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Step 1</h1>
<input type="file" {...register("myFile")} />
<button>Next</button>
</form>
);
};
const Step2 = ({ onComplete }) => {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
onComplete(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Step 2</h1>
<input type="text" {...register("name")} />
<button>Submit</button>
</form>
);
};
https://codesandbox.io/s/condescending-leaf-6gzoj?file=/src/App.js
Passing down the function and tracking changes at parent level is not a great idea. react-hook-form provides a form context option which allows you to do this independently. Such that errors, onChange are handled in each step separately. But when you need to submit the data you can get all of those in the parent component.
Refer to this documentation: https://react-hook-form.com/api/useformcontext/
Note: Many people make the mistake of placing the FormProvider inside the parent component. Remember that FormProvider should wrap the Parent component as well.
I am using react with antd framework. I have created a form and handling the submit:
My forms.js:
import React from 'react';
import { Form, Input, Button, notification } from 'antd';
import axios from 'axios';
class CustomForm extends React.Component {
handleFormSubmit = (event, requestType, articleId) => {
const title = event.target.elements.title.value;
const content = event.target.elements.content.value;
notification.open({
message: 'Success',
description:
'Your Response is submitted',
onClick: () => {
console.log('Notification Clicked!');
},
onCancel: () => {
}
});
// eslint-disable-next-line
switch (requestType) {
case 'post':
return axios.post('http://localhost:8000/api/', {
title:title,
content:content
}
)
.then (res => console.log(res))
.catch(err => console.error(err))
case 'put':
return axios.put(`http://localhost:8000/api/${articleId}/`, {
title:title,
content:content
})
.then (res => console.log(res))
.catch(err => console.error(err))
}
}
render(){
return (
<div>
<Form onSubmitCapture={(event) => this.handleFormSubmit(event, this.props.requestType, this.props.articleId)} >
<Form.Item label="Title">
<Input name='title' placeholder="Input some title" />
</Form.Item>
<Form.Item label="Content">
<Input name='content' placeholder="Input some content" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType='submit' >{this.props.btntext}</Button>
</Form.Item>
</Form>
</div>
)
}
};
export default CustomForm;
Using this I can get input from the user and show a success notification. But I want to reload my page after 5 seconds when I get the notification when I try to use this code window.location.reload(true) in my handleFormSubmit it is not allowing the notification to take place.
You can use setTimeout(() => window.location.reload(true), 5000); this code
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
};
}
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
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));
}