How to preventDefault() on form onSubmit in react/redux project - reactjs

I have a component with an input that when submitted is meant to pass the input text to store. I can't figure out how to preventDefault() when I submit the form.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addItem } from '../actions';
const ItemInput = (props) => {
return (
<div>
<form onSubmit={() => props.addItem('test')}>
<input
placeholder="addItem"
/>
</form>
</div>
);
}
const mapStateToProps = (state) => {
return { addItem: state.addItem };
}
export default connect(mapStateToProps, {
addItem: addItem
})(ItemInput);
I know how to do this in react, but it doesn't seem to work the same way, I keep getting an unexpected token error that doesn't make any sense, probably because the syntax just doesn't work the same way with redux and store. This also isn't a button issue, I'm submitting the form after pressing return.

This part of your code is just a function, you can expand it as you want:
<form onSubmit={() => props.addItem('test')}>
So, you can do:
<form onSubmit={e => {
e.preventDefault();
props.addItem('test');
}}>
Or move this handler into a function:
const handleSubmit = e => {
e.preventDefault();
props.addItem('test');
}
// ...
<form onSubmit={handleSubmit}>

Related

Redux Form is not firing off submit function

I have set up a redux form but it does not seem to be firing off onSubmit the actual submitHandle function.
Please see the code below
import React, { Component } from "react";
import { connect } from "react-redux";
import { hideTransferLicenseWindow, setProgressBarValue } from "../../redux/actions/LicenseActions";
import { Field, reduxForm } from 'redux-form'
export class LicenseTransfer extends Component {
componentDidMount() {
console.log(this.props)
}
renderInput = ({ input, customValue, autoFocus }) => {
return (
<input
className="uk-input"
{...input}
value={customValue}
autoFocus={autoFocus}
/>
)
}
onFormSubmit = (values) => {
console.log('Clicked submit')
}
render() {
const { licenseOperations } = this.props;
return (
<div className="app-section transfer-license-window">
<button
onClick={() => this.props.hideTransferLicenseWindow()}
uk-close=""
className="uk-alert-close"
></button>
<form onSubmit={this.props.handleSubmit(this.onFormSubmit)}>
<div className="field">
<label>From:</label>
<Field
name="transferLicenseFromEmail"
component={this.renderInput}
customValue={this.props.userEmail}
/>
</div>
<div className="field">
<label>To:</label>
<Field
name="transferLicenseToEmail"
component={this.renderInput}
autoFocus={true}
/>
</div>
</form>
</div>
);
}
}
const transferLicenseFormWrapper = reduxForm({
form: 'transferLicense',
})(LicenseTransfer)
const mapStateToProps = (state) => {
return {
userEmail: state.user.user.email,
licenseOperations: state.licenseOperations,
};
};
export default connect(mapStateToProps, { hideTransferLicenseWindow, setProgressBarValue })(
transferLicenseFormWrapper
);
So it should log form values on submitting the form but it does not react nor gives any errors/
I have similar form set up in another component which works just fine. Spent good amount of time playing the game of finding differences but this does not makes sense to me.
Thanks
Ok I figured it out.
For those who might have the same issue, make sure to place your submit button inside the Form, if you want to be able to submit by pressing "Enter".
If you just want to submit with a mouse click on button only, it is sufficient to leave the button outside of the form (not sure if there are any other consequences).

Page reloading while dispatching a form data

I am trying to submit a form data to the reducer via action creators but its not working anyway.The page is reloading anyway.I can't resist.
Below is my code
import React, { Component } from 'react';
import { connect } from 'react-redux'
import {addName} from '../Actions/actionCreators'
class about extends Component {
constructor(props){
super(props)
}
render() {
return (
<div>
<h2> About me </h2>
<form action="" onSubmit={this.submitform}>
<br/>
<input type="text" name="name" placeholder="Your Name" />
<br/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return{
submitform : (e) => {
dispatch(addName(e.target.name.value))
}
}
}
export default connect(null, mapDispatchToProps)(about)
Here addName is the action creator which receives data(name)
if i am using this,also error happens 'this.props.dispatch` is not a function
constructor(props){
super(props)
this.submithandle = this.submithandle.bind(this)
}
submithandle(e, dispatch){
e.preventDefault()
this.props.dispatch(addName(e.target.name.value))
}
What step i can take to submit data via mapDispatchToProps?
You don't understand mapDispatchToProps correctly. mapDispatchToProps will add the functions to the delivered props of the component. The props of about looks like this:
{submitform: (e) => {dispatch(addName(e.target.name.value))}}
To call the submitform prop, you have to access it with this.props.submitform.
If you write that into your onSubmit prop of form, it should work.
You should also set the names of your react components to start with an upper-case so that react can differentiate them between native and new components. So about => About.
Hope this helps. Happy coding.
You need a handler for the onSubmit event.
this.handleSubmit(e)=>{
this.props.addNameAction(e.target.value);
}
then Dispatch the action that you supposedly imported at the top.
const mapDispatchToProps = dispatch => {
return{
addNameAction : (name) => {
dispatch(addName(name))
}
}
}

page reloads when updating redux state/submitting form

I'm trying to set up a simple react-redux flow where an input updates state and a form submits the value in the component's state to a redux action function. However, whenever the form submits, the page reloads and when I add e.preventDefault() to the submit function, I get
TypeError: e.preventDefault is not a function
I've tried adding e.preventDefault() to the submitToRedux function but when I add do, I get TypeError: e.preventDefault is not a function
Here is my Child1.js:
import React, { useState } from "react";
import { changeName } from "../redux/name/name.actions";
import { connect } from "react-redux";
function Child1(state) {
const [name, setName] = useState("");
const changeHandler = e => {
e.preventDefault();
setName(e.target.value);
};
const submitToRedux = e => {
// e.preventDefault();
changeName(name);
};
return (
<div>
<h2>CHILD ONE</h2>
<form onSubmit={submitToRedux(name)}>
<input type="text" onChange={changeHandler} />
<button type="submit">SUBMIT</button>
<h2>name in Child1 state: {name}</h2>
<h2>name in redux: {state.name.name}</h2>
</form>
</div>
);
}
const mapStateToProps = state => ({
name: state.name
});
export default connect(mapStateToProps)(Child1);
App.js:
import React from "react";
import Child1 from "./components/Child1";
function App() {
return (
<div className="App">
<Child1 />
</div>
);
}
export default App;
root-reducer.js:
import { combineReducers } from "redux";
import nameReducer from "./name/nameReducer";
export default combineReducers({
name: nameReducer
});
and nameReducer.js:
import NameActionTypes from "./name.types";
const INITIAL_STATE = {
name: "Mike"
};
const nameReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case NameActionTypes.CHANGE_NAME:
return {
...state,
name: action.payload
};
default:
return state;
}
};
export default nameReducer;
I expect it to update the state.name.name value in Child1.js to whatever is submitted from the state in the Child1.js component when the form is submitted but instead it just reloads the page and since I'm not persisting it to local storage it just remains blank. When I add e.preventDefault() I expect it to stop reloading the page when the form submits but it then says that
e.preventDefault is not a function
It's because you are not passing the submit event to submitToRedux function.
You should pass it to your function like this:
<form onSubmit={(e) => submitToRedux(e, name)}>
and then you handle it in you function like this:
const submitToRedux = (e, name) => {
e.preventDefault();
changeName(name);
};
Here is how child1.js will be:
import React, { useState } from "react";
import { changeName } from "../redux/name/name.actions";
import { connect } from "react-redux";
function Child1(state) {
const [name, setName] = useState("");
const changeHandler = e => {
e.preventDefault();
setName(e.target.value);
};
const submitToRedux = (e, name) => {
e.preventDefault();
changeName(name);
};
return (
<div>
<h2>CHILD ONE</h2>
<form onSubmit={(e) => submitToRedux(e, name)}>
<input type="text" onChange={changeHandler} />
<button type="submit">SUBMIT</button>
<h2>name in Child1 state: {name}</h2>
<h2>name in redux: {state.name.name}</h2>
</form>
</div>
);
}
const mapStateToProps = state => ({
name: state.name
});
export default connect(mapStateToProps)(Child1);
Multiple issue's with your code,
First, you are writing state as argument to Child1 component
function Child1(state) {
which should be,
function Child1(props) {
You should set this props to your state,
const [name, setName] = useState(props.name);
Your input should be controlled,
<input type="text" onChange={changeHandler} value={name} />
You should print name like this,
<h2>name in Child1 state: {name}</h2>
<h2>name in redux: {props.name}</h2>
Your form submit method should be like this,
<form onSubmit={submitToRedux}>
And finally your submitToRedux function,
const submitToRedux = e => {
e.preventDefault(); //Now this will work
changeName(name); //As we have controlled input, we direclty take name from state
};
You just need to pass the function that will get called once the form is submitted.
<form onSubmit={submitToRedux}>
But instead you are actually calling it right away:
<form onSubmit={submitToRedux(name)}>
When you just pass the function, the form will take care of calling it with a submit event as parameter.
In your code the error says the parameter e should contain a function preventDefault, which clearly is not defined in the variable you are passing in as parameter when you do: submitToRedux(name)

Formik form submission with react-testing library

I am looking to fire a submit handler for a LoginForm. However, for some reason, instead of my mock function being called, the actual handler for the component gets fired (calling an external api). How can I ensure that my mock handler gets called instead?
The three components of interest are below (The presentational, container and the test suite)
LoginForm.js
import { Formik, Form, Field } from 'formik';
import { CustomInput } from '..';
const LoginForm = ({ initialValues, handleSubmit, validate }) => {
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={handleSubmit}
>
{({ isSubmitting, handleSubmit }) => {
return (
<Form onSubmit={handleSubmit}>
<div className="d-flex flex-column justify-content-center align-items-center">
<Field
data-testid="usernameOrEmail"
type="text"
name="identifier"
placeholder="Username/Email"
component={CustomInput}
inputClass="mb-4 mt-2 text-monospace"
/>
<Field
data-testid="login-password"
type="password"
name="password"
placeholder="Password"
component={CustomInput}
inputClass="mb-4 mt-4 text-monospace"
/>
<button
data-testid="login-button"
className="btn btn-primary btn-lg mt-3 text-monospace"
type="submit"
disabled={isSubmitting}
style={{ textTransform: 'uppercase', minWidth: '12rem' }}
>
Submit
</button>
</div>
</Form>
)}}
</Formik>
);
};
export default LoginForm;
LoginPage.js
import React, { useContext } from 'react';
import { loginUser } from '../../services';
import { userContext } from '../../contexts';
import { loginValidator } from '../../helpers';
import { setAuthorizationToken, renderAlert } from '../../utils';
import LoginForm from './login-form';
const INITIAL_VALUES = { identifier: '', password: '' };
const LoginPage = props => {
const { handleUserData, handleAuthStatus } = useContext(userContext);
const handleSubmit = async (values, { setSubmitting }) => {
try {
const result = await loginUser(values);
handleAuthStatus(true);
handleUserData(result.data);
setAuthorizationToken(result.data.token);
props.history.push('/habits');
renderAlert('success', 'Login Successful');
} catch (err) {
renderAlert('error', err.message);
}
setSubmitting(false);
};
return (
<LoginForm
initialValues={INITIAL_VALUES}
validate={values => loginValidator(values)}
handleSubmit={handleSubmit}
/>
);
};
export default LoginPage;
LoginPage.spec.js
import React from 'react';
import { cleanup, getByTestId, fireEvent, wait } from 'react-testing-library';
import { renderWithRouter } from '../../../helpers';
import LoginPage from '../login-page';
afterEach(cleanup);
const handleSubmit = jest.fn();
test('<LoginPage /> renders with blank fields', () => {
const { container } = renderWithRouter(<LoginPage />);
const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
const passwordNode = getByTestId(container, 'login-password');
const submitButtonNode = getByTestId(container, 'login-button');
expect(usernameOrEmailNode.tagName).toBe('INPUT');
expect(passwordNode.tagName).toBe('INPUT');
expect(submitButtonNode.tagName).toBe('BUTTON');
expect(usernameOrEmailNode.getAttribute('value')).toBe('');
expect(passwordNode.getAttribute('value')).toBe('');
});
test('Clicking the submit button after entering values', async () => {
const { container } = renderWithRouter(<LoginPage handleSubmit={handleSubmit} />);
const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
const passwordNode = getByTestId(container, 'login-password');
const submitButtonNode = getByTestId(container, 'login-button');
fireEvent.change(usernameOrEmailNode, { target: { value: fakeUser.username }});
fireEvent.change(passwordNode, { target: { value: fakeUser.password }});
fireEvent.click(submitButtonNode);
await wait(() => {
expect(handleSubmit).toHaveBeenCalledTimes(1);
});
expect(usernameOrEmailNode.tagName).toBe('INPUT');
expect(passwordNode.tagName).toBe('INPUT');
expect(submitButtonNode.tagName).toBe('BUTTON');
expect(usernameOrEmailNode.getAttribute('value')).toBe('');
expect(passwordNode.getAttribute('value')).toBe('');
});```
To answer your question, you will need to first make the handleSubmit constant accessible outside LoginPage.js so that it may be mocked and then tested. For example,
LoginPage.js
export const handleSubmit = async (values, { setSubmitting }) => {
... code to handle submission
})
And in your tests - LoginPage.spec.js
jest.unmock('./login-page');
import LoginPage, otherFunctions from '../login-page'
otherFunctions.handleSubmit = jest.fn();
...
test('Clicking the submit button after entering values', () => {
...
fireEvent.click(submitButtonNode);
expect(handleSubmit).toHaveBeenCalledTimes(1);
})
I hope the above fixes your problem.
But, going by the philosophy of unit testing, the above components
must not be tested the way you are doing it. Instead your test setup
should be like this -
Add a new test file called LoginForm.spec.js that tests your LoginForm component. You would test the following in this -
Check if all input fields have been rendered.
Check if the correct handler is called on submit and with the correct parameters.
The existing test file called LoginPage.spec.js would then only test if the particular form was rendered and then you could also test
what the handleSubmit method does individually.
I believe the above would make your tests more clearer and readable
too, because of the separation of concerns and would also allow you to
test more edge cases.

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.

Resources