React hook, Invalid hook call error occurs - javascript

I am building a project using react hooks but getting this error below.
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
And this is the code below
const authRequest = (e: any) => {
e.preventDefault();
alert('Error!')
const [authRequestState, authRequestTrue] = React.useState(false)
authRequestTrue(true)
}
const renderFormikForm = () => {
return (
<Formik initialValues={{country: '', number: ''}} onSubmit={(values) => {submitForm(values)}}>
{({ values, errors, touched, handleChange, handleBlur}) => (
<form>
<div className='input-box'>
<p className='input'>
<input type='email' name='email' placeholder='emial' value='libeto#commontown.co'/>
</p>
<p className='input'>
<input type='number' name='number' placeholder='number' value={values.number} onChange={handleChange} style={{width: '50%'}} />
<button onClick={(e) => authRequest(e)}><em><a>Click!!!</a></em></button>
</p>
</div>
</form>
)}
</Formik>
)
}
So basically, functional component renders renderFormikForm component and when I click the button (say Click!!!) onClick triggers authRequest function but instead state is changed, it gives me the error that I mentioned above.

Hooks can only be created inside function components. You need to use useState inside the function component.
Update your code to following:
const renderFormikForm = () => {
const [authRequestState, authRequestTrue] = React.useState(false)
const authRequest = (e: any) => {
e.preventDefault();
alert('Error!')
authRequestTrue(true)
}
return (
<Formik initialValues={{country: '', number: ''}} onSubmit={(values) => {submitForm(values)}}>
{({ values, errors, touched, handleChange, handleBlur}) => (
<form>
<div className='input-box'>
<p className='input'>
<input type='email' name='email' placeholder='emial' value='libeto#commontown.co'/>
</p>
<p className='input'>
<input type='number' name='number' placeholder='number' value={values.number} onChange={handleChange} style={{width: '50%'}} />
<button onClick={(e) => authRequest(e)}><em><a>Click!!!</a></em></button>
</p>
</div>
</form>
)}
</Formik>
)
}
or you can also re-write it as follows:
const authRequest = (e: any, authRequestTrue) => {
e.preventDefault();
alert('Error!')
authRequestTrue(true)
}
const renderFormikForm = () => {
const [authRequestState, authRequestTrue] = React.useState(false)
return (
<Formik initialValues={{country: '', number: ''}} onSubmit={(values) => {submitForm(values)}}>
{({ values, errors, touched, handleChange, handleBlur}) => (
<form>
<div className='input-box'>
<p className='input'>
<input type='email' name='email' placeholder='emial' value='libeto#commontown.co'/>
</p>
<p className='input'>
<input type='number' name='number' placeholder='number' value={values.number} onChange={handleChange} style={{width: '50%'}} />
<button onClick={(e) => authRequest(e, authRequestTrue)}><em><a>Click!!!</a></em></button>
</p>
</div>
</form>
)}
</Formik>
)
}
The latter one is more closer to the code mentioned in question.
Hope it helps. Revert for any doubts.

Related

Ordering data inside .map [duplicate]

This question already has answers here:
Sort array of objects by string property value
(57 answers)
Closed 1 year ago.
Learning React thanks to your advices, I ended with this simple example, where everything runs nice & smooth. Question is: how can I implement a sort() method to order those data? ( I mean to show table Users ordering by LastName)....
PhoneBookForm:
import {useState} from 'react';
import InformationTable from './InformationTable';
export default function PhoneBookForm() {
const[user,setUser] = useState([]);
const [input,setInput]=useState({
firstName:'',
lastName:'',
phone:'',
});
function handleChange(e){
setInput({
...input,
[e.target.name]:e.target.value
})
}
function handleSubmit(e){
console.log(user)
e.preventDefault();
setUser(
[...user,{...input}])
setInput({
...input,
firstName:'',
lastName:'',
phone:''
})}
return (
<>
<form onSubmit={e => { e.preventDefault() }} style={style.form.container}>
<label>First name:</label>
<br />
<input
style={style.form.inputs}
className='userFirstname'
name='firstName'
type='text'
placeholder='Enter your name here...'
value={input.firstName}
onChange={handleChange}
/>
<br/>
<label>Last name:</label>
<br />
<input
style={style.form.inputs}
className='userLastname'
name='lastName'
type='text'
placeholder='Enter your Last Name here...'
value={input.lastName}
onChange={handleChange}
/>
<br />
<label>Phone:</label>
<br />
<input
style={style.form.inputs}
className='userPhone'
name='phone'
type='text'
placeholder='Enter your phone number...'
value={input.phone}
onChange={handleChange}
/>
<br/>
<input
style={style.form.submitBtn}
className='submitButton'
type='submit'
value='Add User'
onClick={handleSubmit}
/>
</form>
<InformationTable user={user}/>
</>
)
}
InformationTable :
export default function InformationTable({user}) {
// console.log(user);
return (
<div>
{user?.map((u)=>{
return(
<div key={u.phone}>
<table style={style.table} className='informationTable'>
<thead>
<tr>
<th style={style.tableCell}>{u.firstName}</th>
<th style={style.tableCell}>{u.lastName}</th>
<th style={style.tableCell}>{u.phone}</th>
</tr>
</thead>
</table>
</div>
)
})}
</div>
);
}
Currently, User data is showing, but with no order at all
Using state inside setState is React antipattern. SetState function has an callback with previous state, e.g.:
setState(previouState => previousState + 1)
Knowing this, you have two possibilities to sort your users. Directly in setUser function or directly when rendering.
Your handleSubmit function then could look like this
function handleSubmit(e){
console.log(user)
e.preventDefault();
setUser(prevUserState => {
newUsersArray = [...prevUserState, input]
newUsersArray.sort((a,b) =>
{ if (a.lastName > b.lastName) {
return 1
} else {
return -1
}
})
)
setInput({
firstName:'',
lastName:'',
phone:''
})}
If you want to sort the users till on rendering, then simply:
<div>
{user.sort((a,b) => {
if (a.lastName > b.lastName) return 1
else return -1
}).map((u)=>{
return(...

Warning: validateDOMNesting(…): <form> cannot appear as a descendant of <form> by using semantic-ui-react modal

When I use Form in modal of semantic-ui-react, it shows that error.
Warning: validateDOMNesting(…): cannot appear as a descendant
of
I know it is show if there are form in form.
Below is my code, there are no one. if i don't use modal, there are no error.
import { useState } from "react";
import { Helmet } from "react-helmet";
import { Button, Modal, Form } from "semantic-ui-react";
import { Body, Wrapper, Content, Article } from "../../Styles/Wrapper";
// eslint-disable-next-line import/no-anonymous-default-export
export default (company_id, company_secret, onSubmit) => {
const [open, setOpen] = useState(false);
return (
<Body>
<Wrapper>
<Helmet>
<title>juju</title>
</Helmet>
<Content>
<Article>
<Modal as={Form}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open}
trigger={
<Button
style={{ marginBottom: 10, backgroundColor: "#FEE500" }}
size="large"
fluid
>
<span style={{ fontSize: 15 }}>begin</span>
</Button>
}
>
<Modal.Header>add</Modal.Header>
<Modal.Content>
<Form onSubmit={onSubmit}>
<Form.Group>
<Form.Input
placeholder="put id"
name="id"
{...company_id}
/>
<Form.Input
placeholder="put secret"
name="secret"
{...company_secret}
/>
<Form.Button content="Submit" />
</Form.Group>
</Form>
</Modal.Content>
</Modal>
</Article>
</Content>
</Wrapper>
</Body>
);
};
You cannot have a form inside a form. Remove as={Form} when rendering the Modal component. You should also fix the function arguments since the component receives a props object. You should destructure company_id, company_secret, and onSubmit.
export default ({ company_id, company_secret, onSubmit }) => {
// ...
}
And there are a few issues with the <Form.Input> components. You should pass them the value and onChange props. You could create a couple of state variables companyId and companySecret to manage the input states.
const [companyId, setCompanyId] = useState(company_id)
const [companySecret, setCompanySecret] = useState(company_secret)
<>
<Form.Input
name="id"
value={companyId}
onChange={(e) => setCompanyId(e.target.value)}
/>
<Form.Input
name="secret"
value={companySecret}
onChange={(e) => setCompanySecret(e.target.value)}
/>
</>
P.S. I would suggest using camelCase variables everywhere (unless you absolutely have to use snake_case) for consistency.

How to 'useState' correctly in Formik 'onSubmit' function?

I'm using Formik in my Next.js app, and i ran into a problem that I'm not sure how to fix. My submit Button is a component that accepts a showSpinner prop. If it is true -> button is disabled and a loading spinner in a button is displayed. showSpinner value depends on loading that is coming from useState hook. Here is a relevant code:
export default function register() {
const [loading, setLoading] = useState(false)
return (
<>
<Layout>
<div className={styles.registerContainer}>
<div className={styles.registerFormContainer}>
<h1 className={styles.registerHeader}>Sign Up</h1>
<Formik
initialValues={{
email: '',
password: '',
passwordConfirm: '',
acceptedTerms: false
}}
onSubmit={
(values, { setSubmitting }) => {
console.log(loading)
// here loading == false as expected
setLoading(true)
console.log(loading)
// here loading == false even though i set it to true
initFirebase()
firebase.auth().createUserWithEmailAndPassword(values.email, values.password)
.then((res) => {
console.log('done!')
})
.catch(function (error) {
// Handle Errors here.
console.log(error)
})
.finally(() => {
console.log(loading)
//here loading == false too, even though I expected it to be true
setSubmitting(false)
setLoading(false)
})
}
}
>
<Form>
<FormikText label="Email:"
name="email"
type="email"
id="email" />
<FormikPassword label="Password:"
name="password"
id="password"
/>
<FormikPassword label="Confirm Password:"
name="passwordConfirm"
id="passwordCOnfirm"
/>
<FormikCheckbox
name="acceptedTerms"
id="acceptedTerms"
>
<span className={styles.checkboxLabel}>
I agree to the <Link href="/terms" ><a className={styles.registerLink}>Terms of Service</a></Link> and <Link href="/privacy"><a className={styles.registerLink}>Privacy/Cookie Policy</a></Link>
</span>
</FormikCheckbox>
<div className={styles.buttonContainer}>
<Button type="submit" color="blue" showSpinner={loading}>Sign Up</Button>
</div>
</Form>
</Formik>
</div>
</div>
</Layout>
</>
)
}
Even though my Button is somehow working as expected (spinner is displayed as intended), after console.loging value of loading through onSubmit function call I noticed that it is false were I expected it to be true. Is it due to the way React batches useState calls?
My questions are:
How to handle this scenario in a right way?
If loading == false in those console.logs, why is my Button working as intended ?
is it due to the way React batches useState calls?
I think so, that's precisely why Formik provides a isSubmitting flag, try using it instead of tracking your own loading state, I know it's working for your current specs but you could have some issues when this component gets more complex
Your code would look like this
export default function register() {
return (
<>
<Layout>
<div className={styles.registerContainer}>
<div className={styles.registerFormContainer}>
<h1 className={styles.registerHeader}>Sign Up</h1>
<Formik
initialValues={{
email: "",
password: "",
passwordConfirm: "",
acceptedTerms: false,
}}
onSubmit={async (values) => {
try {
initFirebase();
await firebase
.auth()
.createUserWithEmailAndPassword(
values.email,
values.password
);
} catch (e) {
// Handle Errors here.
console.log(error);
}
}}
>
{({ isSubmitting }) => (
<Form>
<FormikText
label="Email:"
name="email"
type="email"
id="email"
/>
<FormikPassword
label="Password:"
name="password"
id="password"
/>
<FormikPassword
label="Confirm Password:"
name="passwordConfirm"
id="passwordCOnfirm"
/>
<FormikCheckbox name="acceptedTerms" id="acceptedTerms">
<span className={styles.checkboxLabel}>
I agree to the{" "}
<Link href="/terms">
<a className={styles.registerLink}>Terms of Service</a>
</Link>{" "}
and{" "}
<Link href="/privacy">
<a className={styles.registerLink}>
Privacy/Cookie Policy
</a>
</Link>
</span>
</FormikCheckbox>
<div className={styles.buttonContainer}>
<Button
type="submit"
color="blue"
showSpinner={isSubmitting}
>
Sign Up
</Button>
</div>
</Form>
)}
</Formik>
</div>
</div>
</Layout>
</>
);
}
taken from here https://formik.org/docs/examples/async-submission

Redux Form Field Arrays-initializing a field array with a specific length

I would like for my form component to start off with a field array with 3 empty fields. Can anyone explain how to initialize it this way?
I'm going off the example provided in the documentation here: https://redux-form.com/7.0.4/examples/fieldarrays/
Here, we can see that originally, we have no fields, and only when we click on the relevant button do we add them, by calling onClick={() => fields.push({})}.
I'd like to start off with three fields and allow the user to add more. Calling fields.push in ComponentDidMount doesn't work. How do I initialize the fields object with a specific length?
FieldArraysForm.js
import React from 'react'
import { Field, FieldArray, reduxForm } from 'redux-form'
import validate from './validate'
const renderField = ({ input, label, type, meta: { touched, error } }) =>
<div>
<label>
{label}
</label>
<div>
<input {...input} type={type} placeholder={label} />
{touched &&
error &&
<span>
{error}
</span>}
</div>
</div>
const renderHobbies = ({ fields, meta: { error } }) =>
<ul>
<li>
<button type="button" onClick={() => fields.push()}>
Add Hobby
</button>
</li>
{fields.map((hobby, index) =>
<li key={index}>
<button
type="button"
title="Remove Hobby"
onClick={() => fields.remove(index)}
/>
<Field
name={hobby}
type="text"
component={renderField}
label={`Hobby #${index + 1}`}
/>
</li>
)}
{error &&
<li className="error">
{error}
</li>}
</ul>
const renderMembers = ({ fields, meta: { error, submitFailed } }) =>
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>
Add Member
</button>
{submitFailed &&
error &&
<span>
{error}
</span>}
</li>
{fields.map((member, index) =>
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}
/>
<h4>
Member #{index + 1}
</h4>
<Field
name={`${member}.firstName`}
type="text"
component={renderField}
label="First Name"
/>
<Field
name={`${member}.lastName`}
type="text"
component={renderField}
label="Last Name"
/>
<FieldArray name={`${member}.hobbies`} component={renderHobbies} />
</li>
)}
</ul>
const FieldArraysForm = props => {
const { handleSubmit, pristine, reset, submitting } = props
return (
<form onSubmit={handleSubmit}>
<Field
name="clubName"
type="text"
component={renderField}
label="Club Name"
/>
<FieldArray name="members" component={renderMembers} />
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
)
}
export default reduxForm({
form: 'fieldArrays', // a unique identifier for this form
validate
})(FieldArraysForm)
Thanks to the Redux Form team:
https://github.com/erikras/redux-form/issues/3640
Basically, the way it's done is to pass in an array of initial values to the form when you connect and export it, something like this:
export default reduxForm({
form: "foo",
initialValues: {
rockSingers: ['Axl Rose', 'Brian Johnson']
},
onSubmit: values => {
window.alert( "Submited: \n" + JSON.stringify( values, null, 2 ) );
}
})( MyForm );

Initialize my form with redux-form

I am facing trouble with initializing my form with redux-form and the 'initialValues' props. I read a lot posts (for instance here) and I don't manage to get it work...
I see that my initialValues is properly set but my fields are not updated with it... Maybe the issue is in my renderBasicField function but I don't know how to fix this.
Otherwise it could also be something like the prop is not populated yet when the component is rendered... But I don't know what to do to make it work as I must rely on mapStateToProps to feed it.
I saw a lot of post about this kind of issues and unfortunately for me I already set up the enableReinitialize property to true :)
Here is my code :
// My component
class ProfileForm extends React.Component {
render () {
console.log(this.props);
const { handleSubmit } = this.props;
const messageClassname = this.props.errorMessage !== undefined ? stylesShared.errorMessage : this.props.confirmationMessage !== undefined ? stylesShared.confirmationMessage : '';
return (
<div>
<div>
<div>
<form onSubmit={handleSubmit(this.props.onSubmitProfileUpdate)}>
<div>
<h4>Votre profil</h4>
</div>
<div className={messageClassname}>
{this.props.errorMessage &&
<span>{this.props.errorMessage}</span>
}
{this.props.confirmationMessage &&
<span>{this.props.confirmationMessage}</span>
}
</div>
<div>
<Field name='firstname' type='text' label='Prénom' component={renderBasicField} />
</div>
<div>
<Field name='lastname' type='text' label='Nom' component={renderBasicField} />
</div>
<div>
<Field name='email' type='email' label='Email' addon='#' component={renderBasicField} />
</div>
<div>
<Field name='telephone' type='text' label='Téléphone' component={renderBasicField} />
</div>
<div>
<Field name='ranking' className='input-row form-group form-control' options={this.getTennisRankingsOptions()} type='select' component={renderSelectField} />
</div>
<div>
<Field name='city' type='text' label='Ville' component={renderBasicField} />
</div>
<div>
<button className='btn btn-info btn-lg center-block' type='submit'>Mettre à jour</button>
</div>
</form>
</div>
</div>
</div>
);
}
}
const reduxFormDecorator = reduxForm({
form: 'profile',
enableReinitialize: true,
validate: validateProfileForm
});
const mapStateToProps = (state) => {
return {
initialValues: state.userConnection.loadProfile.user
};
};
const reduxConnector = connect(
mapStateToProps,
null
);
export default reduxConnector(reduxFormDecorator(ProfileForm));
And the code to render my field :
// My renderFunction
export const renderBasicField = ({input, meta: {touched, error}, label, type='text', id, addon, styleClasses, handleChange, controlledAsyncValue}) => {
const inputStyles = getInputStyles(input.value, touched, error);
if (controlledAsyncValue !== input.value) {
input.value = controlledAsyncValue;
input.onChange(input.value);
}
return (<div className={inputStyles.container}>
{displayInputLabel(inputStyles.input.idInput, label)}
<div className={addon && 'input-group'}>
{addon && <span className='input-group-addon'>{addon}</span>}
<input
{...input}
className={classNames(styles.basicInputField, styleClasses)}
id={id}
value={input.disabled ? '' : input.value}
onChange={getOnChangeAction(input.onChange, handleChange)}
placeholder={label}
type={type}
aria-describedby={inputStyles.input.ariaDescribedBy}
/>
</div>
{touched && error &&
displayErrorMessage(error)}
</div>);
};
I am wondering if I am ignoring the initialValue with my custom renderBasicField function but in that case, I would I retrieve this value to set my input ?
Thanks a lot for your help ! :)
Try to switch connect and form decorator. It should helps.
export default reduxFormDecorator(reduxConnector(ProfileForm));

Categories

Resources