I'm new to React and still am trying to make sense of a few things. I'm using the react-form-dom library and after looking into the documentation I can't figure out what's going on with my project.
I have created a custom input component that takes in a few parameters. The problem is that when I try to log the value of the inputs I get undefined.
Input.jsx
import React, { useState } from 'react';
import './Input.scss';
export default function Input(props, {register, errors}) {
const [inputVal, setInputVal] = useState('');
const nameChange = (e) => {
setInputVal(e.target.value);
props.nameChange(e.target.value);
}
const legend = <span className='legend'>{props?.legend}</span>;
const showLength = <span className='legend'>{props.maxLength - inputVal.length}</span>;
return (
<div className="input-holder">
<input
ref={register}
type={props.type || 'text'}
id={props.id}
placeholder={props.placeholder}
value={inputVal}
onInput={nameChange}
maxLength={props.maxLength || ""}
/>
{ props.legend ? legend : null}
{ props.showLength && props.maxLength ? showLength : null }
</div>
)
}
Form.jsx
import React from 'react';
import { useForm } from "react-hook-form";
import Button from './Button';
import Input from './Input';
export default function UserForm(props) {
const { register, handleSubmit } = useForm();
function submitForm(data) {
console.log(data)
}
return (
<div className="user-form">
<form onSubmit={handleSubmit(submitForm)}>
<Input
placeholder="Name"
nameChange={(name) => props.userName(name)}
showLength="true"
maxLength="35"
legend="Remaining Char: "
id="name"
register={register}
/>
<Input
placeholder="E-mail"
type="email"
id="email"
register={register}
/>
<Button label="Buy" />
</form>
</div>
)
}
It seems that you are using react-hook-form
Checkout the example here
The onChange you are passing to the input tags are no longer needed with react-hook-form beacuse of register functionality of react-hook-form
also validations will be done react-hook-form
import React from 'react';
import { useForm } from "react-hook-form";
export default function UserForm(props) {
const { register, handleSubmit } = useForm();
const submitForm = (data) => {
console.log(data);
}
const onError = (err) => {
console.error(err);
}
return (
<div className="user-form">
<form onSubmit={handleSubmit(submitForm, onError)}>
<input
{...register("name", { //handles onChange, value, name, ref
maxLength: 35 // Your validations
})}
/>
<button>Buy</button>
</form>
</div>
)
}
Checkout docs for register
Form File Example
Related
I use React JS and Strapi. I am trying to send comment to my API. I worked but without one essential thing : the post for which I am writing a comment. How can I possibly add the id of my post so that the relation is made between my comment and my post ?
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import TextField from '#material-ui/core/TextField';
import { Button } from '#material-ui/core'
import CommentsAPI from '../../Services/CommentsAPI'
export default function CommentForm() {
const [comment, setComment] = useState({})
const {id} = useParams()
const handleSubmit = async (event) => {
event.preventDefault();
try {
CommentsAPI.create(JSON.parse(`{"data":${JSON.stringify(comment)}}`))
} catch (error) {
console.log(error)
}
}
const handleChange = (event) => {
const {name, value} = event.currentTarget
setComment({
...comment,
[name]: value
})
}
return (
<form onSubmit={handleSubmit}>
<div>
<TextField
id="pseudo"
label="Pseudo"
type="text"
onChange={handleChange}
name="pseudo"
/>
</div>
<div>
<TextField
id="comment"
label="Comment"
multiline
minRows={2}
onChange={handleChange}
name="content"
/>
</div>
<div>
<Button variant="contained" color="primary" type="submit">
Send
</Button>
</div>
</form>
)
}
import { URL_COMMENTS } from '../config'
import axios from 'axios'
function create(id_post, comment) {
return axios.post(URL_COMMENTS, id_post, comment)
}
const CommentsAPI = {
create
}
export default CommentsAPI
Thank you for your help.
I am working in React with components, but I am not getting functions working in the Input component.
I have tried searching on component and onChange or onKeyPress but they do not show what I need. Maybe I'm searching wrong? I did get the component Button working, just not Input.
The code for the component is simple.
import React from 'react';
import styles from './Input.module.css';
function Input({inputText}) {
return <input type='number'
placeholder={inputText}
className={styles.input}
/>
}
export default Input;
The full code I want to use for component Input is as follows.
import React, {useState} from 'react';
import styles from './Food.module.css';
import axios from 'axios';
import Nutrients from '../../components/nutrients/Nutrients'
import {ReactComponent as LoadingIcon} from '../../assets/loading.svg'
import Button from "../../components/button/Button";
import Input from "../../components/input/Input";
function Food() {
const [food, setFood] = useState(null);
const [calories, setCalories] = useState('');
const [disabled, setDisabled] = useState(false);
const [error, setError] = useState('');
const [loading, toggleLoading] = useState('');
async function foodData() {
setError('');
toggleLoading('true')
try {
const result = await axios.get(`https://api.spoonacular.com/mealplanner/generate?apiKey=${process.env.REACT_APP_API_KEY}&timeFrame=day&targetCalories=${calories}`);
setFood(result.data);
} catch (error) {
setError('Oops... something went wrong. Please try again.');
console.log('Something went wrong while retrieving the data.', error);
}
toggleLoading(false);
}
function handleChange(e) {
setCalories(e.target.value);
}
function handleKeyPress(e) {
if (e.key === 'Enter') {
if (disabled) {
return;
}
foodData();
setDisabled(true);
}
}
return (
<>
{error && <p className={styles.error}>{error}</p>}
<div>
<section className={styles.food}>
<Input
inputText='Enter caloriessss'
onChange= {handleChange}
onKeyPress={handleKeyPress}
/>
<Button
onClick={() => {
foodData();
setDisabled(true);
}}
buttonText='Click for your daily meals'
/>
</section>
{loading && <LoadingIcon className={styles.loader}/>}
{food && <Nutrients mealsData={food}/>}
</div>
</>
);
}
export default Food;
The thing I want is for the two functions onChange and onKeyPress to work in the Input component.
Anyone has any ideas? I also tried using () and ''.
Because you don't use props onChange and onKeyPress in Input component. Just add it like this:
function Input({ inputText, onChange, onKeyPress }) {
return (
<input
type="number"
placeholder={inputText}
className={styles.input}
onChange={onChange}
onKeyPress={onKeyPress}
/>
);
}
Or shorter way:
function Input({ inputText, ...props }) {
return (
<input
type="number"
placeholder={inputText}
className={styles.input}
{...props}
/>
);
}
React has no way of knowing the onChange and onKeyPress props should be passed to the input inside of your Input component.
You must pass them along explicitly.
// Add them to your destructured props
function Input({inputText, onChange, onKeyPress}) {
return <input type='number'
onChange={onChange} // Pass them to the input
onKeyPress={onKeyPress} // Pass them to the input
placeholder={inputText}
className={styles.input}
/>
}
In the component Input, your return should be: <input></input>
Because JSX needs to closed tags.
I'm using react-hook-form and trying to pass the data from a form to its parent.
For this I'm trying to pass the data via a callback, but it's not working.
When I console.log data inside the parent component, I get undefined.
Parent component
import React from 'react';
import InputForm from './InputForm';
const App = (data) => {
const onFormSubmit = () => {
console.log(data.name);
};
return (
<div className='App'>
<InputForm onceSubmited={() => onFormSubmit()} />
</div>
);
};
export default App;
Child component
import React from 'react';
import { useForm } from 'react-hook-form';
const InputForm = ({ onceSubmited }) => {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data);
onceSubmited(data);
};
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<input
type='text'
name='name'
ref={register}
/>
<input
type='email'
name='email'
ref={register}
/>
<button type='submit'>
Submit
</button>
</Form>
</>
);
};
export default InputForm;
You need to pass the argument in your arrow function. This should make it work:
import React from 'react';
import InputForm from './InputForm';
const App = () => {
const onFormSubmit = (data) => {
console.log(data.name);
};
return (
<div className='App'>
<InputForm onceSubmited={(data) => onFormSubmit(data)} />
</div>
);
};
export default App;
I'm testing a reduxForm component with jest and enzyme and I don't know how i can test methods within this component.
I'm exporting the component and testing it itself, I have access to the methods but the component don't recognize the reduxForm field props like meta, touched, error, input e.t.c.
How I can mock these or test my component with reduxForm, not as a separate one
The tested component is Login component and must render 2 fields.
This is the Login component:
import React, { Component } from 'react'
//import { Link } from 'react-router-dom'
import { Field, reduxForm } from 'redux-form'
import '../../style/style.css'
export class Login extends Component {
renderField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="username_field" placeholder={field.label} type="text" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
renderPasswordField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="password_field" placeholder={field.label} type="password" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
onSubmit(values) {
this.props.history.push('/landing')
}
render() {
const { handleSubmit } = this.props
return (
<div>
<div className="login-form">
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<h2 className="text-center">TQI Log in</h2>
<div className="form-group">
<Field id="username" name="username" label="username" component={this.renderField} />
</div>
<div className="form-group">
<Field id="password" name="password" label="password" component={this.renderPasswordField} />
</div>
<div className="form-group">
<button id="login_button" type="submit" className="btn btn-primary btn-block btn-clicked">Login </button>
</div>
</form>
</div>
</div>
);
}
}
export const validate = (values) => {
const errors = {}
const dummyData = {
username: 'admin',
password: 'admin'
}
// Validate the inputs from values
if(!values.username) {
errors.username = "Enter a username"
} else if(values.username !== dummyData.username){
errors.username = "Wrong username"
}
if(!values.password) {
errors.password = "Enter a password"
} else if( values.username === dummyData.username && values.password !== dummyData.password){
errors.password = "Wrong password"
}
// if errors is empty, the form is fine to submit
// If errors has *any* properties, redux forms assumes form is invalid
return errors
}
export default reduxForm({
validate,
form: 'LoginForm'
})(Login)
This is the testing file:
import React from 'react'
import { shallow, mount } from 'enzyme'
import { Login } from '../../components/login/login'
import { validate } from '../../components/login/login'
const mockHandleSubmit = jest.fn()
const renderField = jest.fn()
const mockRenderPasswordField = jest.fn()
const wrapper = shallow(<Login handleSubmit={mockHandleSubmit} />)
const loginButton = wrapper.find('#login_button')
describe('<Login /> render', () => {
it('render the <Login /> component without crashing', () => {
expect(wrapper.length).toEqual(1)
})
it(' call handlesSubmit when submit button is clicked', () => {
loginButton.simulate('click')
expect(mockHandleSubmit).toHaveBeenCalled()
})
})
describe('renderField and renderPasswordField functions', () => {
it('renderField', () => {
expect(wrapper.instance().renderField()).toEqual(1)
})
})
And the error it gives me
To mount a reduxForm wrapped component, you need to wrap your component in
mount(
<Provider store={store}>
<Login {...props}/>
</Provider>
)
note that you need to create a custom store beforehand like this
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
store = createStore(combineReducers({ form: formReducer }))
You may refer to this repo for how to test redux form properly with integration and unit tests.
https://github.com/tylercollier/redux-form-test
Im struggling to find a solution when testing a redux-form component. The problem is that only when I test simply if the component is rendering it gives me an error: "TypeError: handleSubmit is not a function", but the app is working fine, as expected.
I've tried to solve it just to make handleSubmit a function and not taking it from props, but then the app doesn't work. When the submit form is correct it must navigate to /landing page, but instead just re-render the login component.
The component:
import React, { Component } from 'react'
//import { Link } from 'react-router-dom'
import { Field, reduxForm } from 'redux-form'
import '../../style/style.css'
export class Login extends Component {
renderField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="username_field" placeholder={field.label} type="text" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
renderPasswordField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="password_field" placeholder={field.label} type="password" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
onSubmit(values) {
this.props.history.push('/landing')
}
// DOESN'T WORK!!!
// handleSubmit(formValues){
// //console.log(formValues);
// }
render() {
const { handleSubmit } = this.props
return (
<div>
<div className="login-form">
<form onSubmit={ /*this.*/handleSubmit(this.onSubmit.bind(this))}>
<h2 className="text-center">TQI Log in</h2>
<div className="form-group">
<Field id="username" name="username" label="username" component={this.renderField} />
</div>
<div className="form-group">
<Field id="password" name="password" label="password" component={this.renderPasswordField} />
</div>
<div className="form-group">
<button id="login_button" type="submit" className="btn btn-primary btn-block">Login </button>
</div>
</form>
</div>
</div>
);
}
}
function validate(values) {
const errors = {}
const dummyData = {
username: 'admin',
password: '123'
}
// Validate the inputs from values
if(!values.username) {
errors.username = "Enter a username"
} else if(values.username !== dummyData.username){
errors.username = "Wrong username"
}
if(!values.password) {
errors.password = "Enter a password"
} else if( values.username === dummyData.username && values.password !== dummyData.password){
errors.password = "Wrong password"
}
// if errors is empty, the form is fine to submit
// If errors has *any* properties, redux forms assumes form is invalid
return errors
}
export default reduxForm({
validate,
form: 'LoginForm'
})(Login)
The testing file:
import React from 'react'
import { shallow } from 'enzyme'
import { Login } from './login'
describe('<Login />', () => {
it('render the <Login /> component without crashing', () => {
const wrapper = shallow(<Login />)
expect(wrapper.length).toEqual(1)
})
// it('navigate to /landing page when the form is submit correctly', () => {
// })
})
You are consuming a prop function from your HOC reduxForm on your render method. But on the test file, you are importing the component without the HOC on top of it, which means that prop/function is not available. You have to provide Login with a mock handleSubmit prop function.
Try:
import React from 'react'
import { shallow } from 'enzyme'
import { Login } from './login'
describe('<Login />', () => {
it('render the <Login /> component without crashing', () => {
const wrapper = shallow(<Login handleSubmit={() => {}} />)
expect(wrapper.length).toEqual(1)
})
})
you need pass onSubmit on initialization:
export default reduxForm({
validate,
form: 'LoginForm',
onSubmit: this.onSubmit // here
})(Login)
or in props:
<Component>
<Login onSubmit={this.onSubmit}/>
</Component>
First export the redux-form decorated class
export const DecoratedLogin = reduxForm({
validate,
form: 'LoginForm'
})(Login);
Then use it instead of the plain class.
import { DecoratedLogin } from './login'
and you should be able to access the props from redux-form.