I am using a React library called Kendo React in order to create a simple form. What I want is to be able to submit my form when the data is changed by clicking on the Load new user button, however the current behavior is that when I click the button and populate with data the submit button does not submit form until I manually change the value of the field, why and how can I just submit the form immediately after I update the data and the button is enabled? Here is my code:
import React from 'react';
import ReactDOM from 'react-dom';
import { Form, Field, FormElement } from '#progress/kendo-react-form';
import { Error } from '#progress/kendo-react-labels';
import { Input } from '#progress/kendo-react-inputs';
const emailRegex = new RegExp(/\S+#\S+\.\S+/);
const emailValidator = value => emailRegex.test(value) ? "" : "Please enter a valid email.";
const EmailInput = fieldRenderProps => {
const {
validationMessage,
visited,
...others
} = fieldRenderProps;
return <div>
<Input {...others} />
{visited && validationMessage && <Error>{validationMessage}</Error>}
</div>;
};
const App = () => {
const handleSubmit = dataItem => alert(JSON.stringify(dataItem, null, 2));
const [user, setUser] = React.useState({
firstName: 'John',
lastName: 'Smith',
email: 'John.Smith#email.com'
});
const loadNewUser = () => {
setUser({
firstName: 'NewFirstName',
lastName: 'NewLastName',
email: 'NewEmails#email.com'
});
};
return <React.Fragment>
<button className='k-button' onClick={loadNewUser}> Load new user </button>
<hr className='k-hr' />
<Form onSubmit={handleSubmit} initialValues={user} key={JSON.stringify(user)} render={formRenderProps => <FormElement style={{
maxWidth: 650
}}>
<fieldset className={'k-form-fieldset'}>
<legend className={'k-form-legend'}>Please fill in the fields:</legend>
<div className="mb-3">
<Field name={'firstName'} component={Input} label={'First name'} />
</div>
<div className="mb-3">
<Field name={'lastName'} component={Input} label={'Last name'} />
</div>
<div className="mb-3">
<Field name={"email"} type={"email"} component={EmailInput} label={"Email"} validator={emailValidator} />
</div>
</fieldset>
<div className="k-form-buttons">
<button type={'submit'} className="k-button" disabled={!formRenderProps.allowSubmit}>
Submit
</button>
</div>
</FormElement>} />
</React.Fragment>;
};
ReactDOM.render(<App />, document.querySelector('my-app'));
this is normal because when you use setUser a render event is triggered and not a change event on your inputs, so to submit your form on every loadNewUser click, you can add the submit logic to loadNewUser method after setUser. The last option is better as you avoid triggering the submit logic before setUser call inside loadNewUser.
Or use UseEffect to perform the submit on each change to the user object.
useEffect(() => {
console.log(user);
}, [user]);
Here is a simple example ( without Kendo ) :
import { useEffect, useState } from "react";
export default function App() {
const [user, setUser] = useState(null);
const loadNewUser = () => {
setUser({ name: "alan" });
};
const onchange = (event) => {
setUser({
...user,
name: event.target.value
});
};
const submit = () => {
console.log("submit", user);
};
useEffect(() => {
user && submit();
}, [user]);
return (
<div className="App">
<button onClick={loadNewUser}>Load new user</button>
<input
type="text"
onChange={onchange}
value={user != null ? user.name : ""}
/>
<button onClick={submit}>submit</button>
</div>
);
}
Related
I'm trying to send user input data to my tRPC API. When I try to send my query, I get an error that I can only use React Hooks inside a function component. I believe I can't call tRPC's useQuery from a callback because it's a react hook, but how can I submit the mutation when the form is completed?
my zip code component
'use client';
import { type NextPage } from "next"
import { NextPageButtonLink } from "../UI/NextPageButtonLink"
import { api } from "../utils/api";
import { useState } from "react";
const ZipCode: NextPage = () => {
const [zip code, setZipCode] = useState("");
// This posts on every rerender and input. Ideally, it should only post when the user clicks submit
// const postZipCodeResult = API.zipcode.postZipCode.use query({userId: "1", zipcode: zipcode});
const handleSubmit = () => {
// This throws the invalid hook location error
const postZipCodeResult = API.zipcode.postZipCode.use query({userId: "1", zipcode: zipcode});
console.log("Posting result", postZipCodeResult)
}
return (
<div className="bg-[#3276AE] flex flex-col items-center h-screen">
<form onSubmit={handleSubmit}>
<label htmlFor="zipcode">Enter your zipcode:
<input type="text" id="zipcode" name="zipcode" required value={zipcode} onChange={e => setZipCode(e.target.value)} />
</label>
<button type="submit">Submit</button>
</form>
<NextPageButtonLink pageName="survey" msg="Click here to start the demographics survey." />
</div>
)
}
export default ZipCode;
My zip code page:
import dynamic from "next/dynamic";
const ZipCode = dynamic(() => import('../components/zipcode'), {
SSR: false
})
export default function ZipCodePage() {
return (<ZipCode/>)
}
my zip code router
import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "../trpc";
export const zipcodeRouter = createTRPCRouter({
postZipCode: publicProcedure
.input(z.object({ userId: z.string(), zipcode: z.string() }))
.query(({ input }) => {
return {
zipcode: `User: ${input.userId} zipcode: ${input.zipcode}`,
};
}),
});
You can't call hooks conditionally, but you can disable the query and then use its refetch to fire it when the user clicks the button.
const ZipCode: NextPage = () => {
const [zipcode, setZipCode] = useState("");
const { data, refetch } = api.zipcode.postZipCode.useQuery({userId: "1", zipcode: zipcode}, {
enabled: false
});
const handleSubmit = () => {
refetch();
}
return (
<div className="bg-[#3276AE] flex flex-col items-center h-screen">
<form onSubmit={handleSubmit}>
<label htmlFor="zipcode">Enter your zipcode:
<input type="text" id="zipcode" name="zipcode" required value={zipcode} onChange={e => setZipCode(e.target.value)} />
</label>
<button type="submit">Submit</button>
</form>
{
data && (
<pre>{data.zipcode}</pre>
)
}
</div>
)
}
export default ZipCode;
I just want to preface this that I am learning JavaScript and React so this is all very new to me.
I am building a "simple" movie rating app and need to be able to push a review to a div "on submit" and cannot figure out how to do so. I have tried using update state in react and/or creating functions to try to accomplish this and cannot figure out how to do this for the life of me. I did somewhat succeed using the latter method, but was getting errors about using unique key props. The other problem was I am to use a star-rating component and when I submitted the review, it wasn't pushing that to the div. This is where I'm at currently:
import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";
export default function ReviewForm() {
const [reviews, setReviews] = useState("");
const onChange = (e: any) => {
setReviews(e.target.value);
};
const onSubmit = (e: any) => {
console.log("Form Submitted");
};
return (
<div className="form-container">
<Stars />
<Form onSubmit={onSubmit}>
<Input
className="form-control" type="text"
placeholder="Enter you review"
value={reviews}
onChange={onChange}
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
</Form>
</div>
);
}
// This is what I have in my Stars component:
import React, { useState } from "react";
import { FaStar} from 'react-icons/fa'
const Stars = () => {
const [rating, setRating] = useState(0);
const [hover, setHover] = useState(null);
return(
<div>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return <label>
<input
type="radio"
name="rating"
value={ratingValue}
onClick={() => setRating(ratingValue)}
/>
<FaStar
className="star"
color={ratingValue <= (hover || rating) ? "gold" : "lightgray"}
size={20}
onMouseEnter={() => setHover(ratingValue)}
onMouseLeave={() => setHover(null)}
/>
</label>;
})}
<p>I rate this movie {rating + " stars"}</p>
</div>
);
};
export default Stars```
Here is the working version of your code. You should use key in your map and e.preventDefault() in your form submit function. As final touch you should set another state inside your form submit and show this value in a div or some html element. Also I see that you want to get child state into parent so you can call callback for this https://codesandbox.io/embed/brave-euler-ybp9cx?fontsize=14&hidenavigation=1&theme=dark
ReviewForm.js
export default function ReviewForm() {
const [reviews, setReviews] = useState("");
const [value, setValue] = useState("");
const [star, setStar] = useState();
const onChange = (e: any) => {
setReviews(e.target.value);
};
const onSubmit = (e: any) => {
e.preventDefault();
setValue(reviews + " with " + star + " star ");
};
return (
<div className="form-container">
<Stars setStar={setStar} />
<Form onSubmit={onSubmit}>
<Input
className="form-control"
type="text"
placeholder="Enter you review"
value={reviews}
onChange={onChange}
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
<div>{value}</div>
</Form>
</div>
);
}
Stars.js
const Stars = ({ setStar }) => {
const [rating, setRating] = useState(0);
const [hover, setHover] = useState(null);
const handleClick = (ratingValue) => {
setRating(ratingValue);
setStar(ratingValue);
};
return (
<div>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return (
<label key={i}>
<input
type="radio"
name="rating"
value={ratingValue}
onClick={() => handleClick(ratingValue)}
/>
<FaStar
className="star"
color={ratingValue <= (hover || rating) ? "gold" : "lightgray"}
size={20}
onMouseEnter={() => setHover(ratingValue)}
onMouseLeave={() => setHover(null)}
/>
</label>
);
})}
<p>I rate this movie {rating + " stars"}</p>
</div>
);
};
export default Stars;
You probably are seeing a page refresh when you press the submit button. This is the default behavior of HTML forms.
When using React or any front-end framework, you'd want to handle the form submission yourself rather than letting the browser submit your forms.
In your onSubmit function, add the following line
e.preventDefult()
const onSubmit = (e: any) => {
e.preventDefault()
console.log("Form Submitted");
};
Your code will work perfectly.
import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";
export default function ReviewForm() {
const [Reviews, setReviews] = useState("");
const [ReviewsRating, setReviewsRating] = useState(5);
const [Reviews_, setReviews_] = useState([]);
const onChange = (e: any) => {
setReviews(e.target.value);
};
const onSubmit = (e: any) => {
e.preventDefault()
console.log("Form Submitted");
//After upload to the server
setReviews_([Reviews, ...Reviews_]
};
return (
<div className="form-container">
<Stars getRating={getRating}/>
<Form onSubmit={onSubmit}>
<Input
className="form-control" type="text"
placeholder="Enter you review"
value={reviews}
onChange={onChange}
/>
<br></br>
<Button type="submit" className="btn btn-primary">
Submit
</Button>
</Form>
<div class="reviews">
{Reviews_.map(item => <div> {item}</div> )}
</>
</div>
);
}```
Then to get the stars rating value use props like...
And make sure you call that property (function) inside your Starts component
const getRating =(value)=>{
setReviewsRating(value)
}
I have a React Form app with name and description fields.
The form data is held in a local state object using Hooks:
const [data,setData] = useState({name: '', description: ''}).
The <Form /> element creates inputs and passes their value using <Field initialValue ={data.name} />
Within the <Field /> element, this initialValue is passed to the state, which controls the input value (updated onChange):
const [value,setValue] = useState(initialValue).
But if I reset the data object (see handleResetClick function), the inputs don't clear (even though the data object clears). What am I doing wrong? I thought that changing the data would cause a re-render and re-pass initialValue, resetting the input.
Codepen example here - when I type in the inputs, the data object updates, but when I click Clear, the inputs don't empty.
function Form() {
const [data, setData] = React.useState({name: '', description: ''});
React.useEffect(() => {
console.log(data);
},[data]);
const onSubmit = (e) => {
// not relevant to example
e.preventDefault();
return;
}
const handleResetClick = () => {
console.log('reset click');
setData({name: '', description: ''})
}
const onChange = (name, value) => {
const tmpData = data;
tmpData[name] = value;
setData({
...tmpData
});
}
return (
<form onSubmit={onSubmit}>
<Field onChange={onChange} initialValue={data.name} name="name" label="Name" />
<Field onChange={onChange} initialValue={data.description} name="description" label="Description" />
<button type="submit" className="button is-link">Submit</button>
<button onClick={handleResetClick} className="button is-link is-light">Clear</button>
</form>
)
}
function Field(props) {
const {name, label, initialValue, onChange} = props;
const [value, setValue] = React.useState(initialValue);
return (
<div>
<div className="field">
<label className="label">{label}</label>
<div className="control">
<input
name={name}
className="input"
type="text"
value={value}
onChange={e => {
setValue(e.target.value)
onChange(name, e.target.value)
}}
/>
</div>
</div>
</div>
)
}
class App extends React.Component {
render() {
return (
<div className="container">
<Form />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
On handleResetClick you change the data state of Form, but it doesn't affect its children.
Try adding a listener for initialValue change with useEffect:
function Field(props) {
const { name, label, initialValue, onChange } = props;
const [value, setValue] = React.useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return ...
}
You may be better off having Field as a controlled component (ie it's state is managed by the parent component rather than maintaining its own state). In this example I've swapped in value instead of initialValue and simply passed that down as props to the field. onChange then calls the parent method and updates the state there (which is automatically passed back down to the field when it renders):
const { useState, useEffect } = React;
function Form() {
const [data, setData] = React.useState({
name: '',
description: ''
});
useEffect(() => {
console.log(data);
}, [data]);
const onSubmit = (e) => {
e.preventDefault();
return;
}
const handleResetClick = () => {
setData({name: '', description: ''})
}
const onChange = (e) => {
const { target: { name, value } } = e;
setData(data => ({ ...data, [name]: value }));
}
return (
<form onSubmit={onSubmit}>
<Field onChange={onChange} value={data.name} name="name" label="Name" />
<Field onChange={onChange} value={data.description} name="description" label="Description" />
<button type="submit" className="button is-link">Submit</button>
<button onClick={handleResetClick} className="button is-link is-light">Clear</button>
</form>
)
}
function Field(props) {
const {name, label, value, onChange} = props;
return (
<div>
<div className="field">
<label className="label">{label}</label>
<div className="control">
<input
name={name}
className="input"
type="text"
value={value}
onChange={onChange}
/>
</div>
</div>
</div>
)
}
function App() {
return (
<div className="container">
<Form />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I've been trying to learn React hooks in order to start building a personal project but ran into a few road blocks. Currently, when I do an axios request, the page resets and no data is shown.
In order to make sure it was working the correct way, I made a class version and was able to retrieve the data plus upload it to state with setState.
import React, { useState } from "react";
import axios from "axios";
const Form = () => {
const [signup, setForm] = useState({ username: "", email: "", password: "" });
const [user, setUser] = useState({ user: "" });
const submit = () => {
axios.get("/api/users").then(user => {
setUser({ user });
});
};
return (
<div>
{user.username}
<h1>This is the Landing Page!</h1>
<form onSubmit={submit}>
<input
type="text"
placeholder="Enter Username"
value={signup.username}
onChange={e => setForm({ ...signup, username: e.target.value })}
/>
<input
type="text"
placeholder="Enter Email"
value={signup.email}
onChange={e => setForm({ ...signup, email: e.target.value })}
/>
<input
type="password"
placeholder="Enter Your Password"
value={signup.password}
onChange={e => setForm({ ...signup, password: e.target.value })}
/>
<input type="submit" value="Submit" />
</form>
</div>
);
};
The class option works
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
user: ""
};
}
loadData = () => {
console.log(this.state);
axios.get("/api/users").then(user => {
console.log(user.data);
debugger;
this.setState({ user });
});
};
render() {
return (
<div>
<h1>This is the header</h1>
<button onClick={this.loadData}>This is a button</button>
</div>
);
}
}
What I'm expecting is for the data to persist. It does appear in console.log but even that disappears within a few seconds as if the entire page keeps reloading. When I do input typing it works but not on a axios call.
Change your onSubmit function to the following to avoid a page reload.
const submit = (e) => {
e.preventDefault();
axios.get("/api/users").then(user => {
setUser({ user });
});
};
You want to disable the browser from submitting the form by adding an event.preventDefault(), and let your submit() function send that request with Axios (or fetch) instead:
const submit = event => {
event.preventDefault();
...
}
So i'm using this example to try and build a simple form that validates synchronously.
This is my form:
const DatasetForm: React.StatelessComponent = (props: any) => {
const { handleSubmit, pristine, reset, submitting } = props;
console.log(props);
return (<form onSubmit= { handleSubmit }>
<div>
<div>
<Field
name="firstName"
component= {renderField}
type="text"
label="First Name"
/>
</div>
<div>
<button type="submit" disabled={submitting}>Submit</button>
</div>
</div>
</form>)
}
I'm using exactly the same renderField function and this is my validate function:
const validate = (values: IValues) => {
let errors: IValues = {
firstName: ''
};
if (!values.firstName) {
errors.firstName = 'Required';
}
else if (values.firstName !== 'aaaaa') {
errors.firstName = 'Must be aaaaa';
}
return errors;
}
The handleSubmit function is a simple console.log and is passed as a prop.
Now in the example the handleSubmit function doesn't seem to be called if a field is not valid. But in my code, it is definitely called every time i click the submit button. I've stared at both my and the example code for a long time without finding what may cause this difference. Any help is appreciated.
EDIT: Adding the export function:
export default reduxForm({
form: 'simpleForm',
validate
})(DatasetForm);
You need to pass in your custom validate function to your wrapped custom form DatasetForm. Like this:
const validate2 = (values) => {
let errors = {};
if (!values.firstName) {
errors.firstName = 'Required';
}
else if (values.firstName !== 'aaaaa') {
errors.firstName = 'Must be aaaaa';
}
console.log(errors);
return errors;
}
const DatasetForm = (props) => {
const { handleSubmit, pristine, reset, submitting } = props;
return (<form onSubmit={handleSubmit}>
<div>
<div>
<Field
name="firstName"
component={renderField}
type="text"
label="First Name"
/>
</div>
<div>
<button type="submit" disabled={submitting}>Submit</button>
</div>
</div>
</form>)
}
export default reduxForm({
form: 'syncValidation',
validate: validate2,
warn
})(DatasetForm)
Check out this working sample.