I have a form in a react project that is behaving a bit strange. I have e.preventDefault() attatched to my submit button but for some reason the page is still refreshing each time the button is clicked. Could someone please help me figure out why this is happening? Here is the Component in question:
import React, { useState } from 'react';
import './PostForm.css';
import axios from 'axios'
const PostForm = () => {
const [posts, setPost] = useState({
body: '',
author: 'Michael',
});
const onChange = (e) =>{
setPost({...posts, [e.target.name] : e.target.value})
}
const sendPost = (e) => {
e.preventDefault()
try {
const res = axios.post('http://localhost:4000/post', posts);
console.log(res)
} catch (error) {
console.log(error)
}
}
return (
<form className="formContainer">
<div className="form">
<textarea className='formBody' type="text" placeholder="What's new?" name='body' onChange={onChange} />
<button onSubmit={sendPost}>Share</button>
</div>
</form>
);
};
export default PostForm;
onSubmit should be in the form tag. And change the button to input and it's type as submit.
<form className="formContainer" onSubmit={sendPost}>
<div className="form">
<textarea className='formBody' type="text" placeholder="What's new?" name='body' onChange={onChange} />
<input type="submit" value="Share" />
</div>
</form>
Related
In my react js application i have the next input:
<input required type="text" id="fname" name="fname" value=""/>
In the case above if the form will be submitted the without input value the will appear a message: Please fill in this field, because it is required. Instead of this text i want to add a custom html element with a different content and style.
I want to check after the form is submitted that this input is required and there is no value. So if there is that scenario then to show a custom element bellow like: <div>No data here</div>
I need to achieve this without using the form tag just to create a custom input component in React js and to do something like:
export default function Input() {
const ref = React.useRef();
return (
<div className="App">
<input ref={ref} required type="text" id="fname" name="fname" value=""/>
{ref.current.isError ? <div>Message</div> : null}
</div>
);
}
Is this possible to check?
You can use onInvalid method on input e.g:
function Input() {
const [invalid, setInvalid] = useState<boolean>(false);
const onSubmit = (data: any) => {
setInvalid(false);
console.log(data)
}
return (
<div style={{ margin: '60px'}}>
<form onSubmit={onSubmit}>
<input
id="fname"
name="fname"
required
type="text"
onInvalid={(e) => {
e.preventDefault();
// if you have ref you can obtain reason why this input is invalid
// console.log(ref.current?.validity.valueMissing);
// or just use e.target.validity
setInvalid(true);
}}
/>
<button type="submit">submit</button>
</form>
{invalid && "invalid"}
</div>
);
}
useState is used here to cause re-render component
Edit: if you don't want to have form inside Input component then just move state to parent component e.g:
function Input(props: { invalid: boolean; setInvalid: () => void }) {
const { invalid, setInvalid } = props;
return (
<div style={{ margin: '60px'}}>
<input
id="fname"
name="fname"
required
type="text"
onInvalid={(e) => {
e.preventDefault();
setInvalid();
}}
/>
{invalid && "invalid"}
</div>
);
}
function App() {
const [invalid, setInvalid] = useState<Record<string, boolean>>({});
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
setInvalid({});
console.log(event);
}
return (
<div className="App">
<form onSubmit={handleSubmit}>
<Input invalid={invalid.fname} setInvalid={() => setInvalid(state => ({...state, fname: true}))}/>
<button type="submit">submit</button>
</form>
</div>
);
}
Edit2: If you want make it valid again you can use onChange:
function Input() {
const [invalid, setInvalid] = React.useState(false);
const updateInvalid = (e) => setInvalid(!e.nativeEvent.target.validity.valid);
return (
<div>
<input
id="fname"
name="fname"
required
type="text"
onChange={updateInvalid}
onInvalid={(e) => {
e.preventDefault();
updateInvalid(e);
}}
/>
<div>{invalid && "Invalid"}</div>
</div>
);
}
Sandbox
All you need is to check if the input is empty or not and based on result we are making a decision for printing message.
All we need is useState hook, onSubmit form validation.
index.js
import React from "react";
import ReactDOM from "react-dom";
import MyInput from "./MyInput.js";
class App extends React.Component {
render() {
return (
<div>
// validate attribute to defind which validation we need
<MyInput validate="email" />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
MyInput.js
import React, { useState } from "react";
const MyInput = ({ validate }) => {
const [userInput, setUserInput] = useState("");
const [isError, setIsError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const handleInput = (e) => {
setUserInput(e.target.value);
setIsError(false);
};
function validateEmail(email) {
var re = /\S+#\S+\.\S+/;
return re.test(email);
}
//you can defind more states such as for password or as per your requirement.
const validateField = (fieldType) => {
switch (fieldType) {
case "email":
if (validateEmail(userInput)) {
setIsError(false);
} else {
setErrorMessage("Need proper email..");
setIsError(true);
}
break;
default:
setIsError(false);
}
};
const handleSubmit = (e) => {
e.preventDefault();
validateField(validate);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" value={userInput} onChange={handleInput} />
{isError ? <p>{errorMessage}</p> : ""}
<input type="submit" />
</form>
);
};
export default MyInput;
Here is working example.
<iframe src="https://codesandbox.io/embed/react-playground-forked-ozgle5?fontsize=14&hidenavigation=1&theme=dark&view=preview" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="React PlayGround (forked)" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
const afterSubmission = (e, message) => {
e.preventDefault()
console.log(message);
if (message === "") return
displayMessage(message)
messageInput.value=""
}
<div id="message-container"></div>
<form onSubmit = {afterSubmission}>
<label for="message-input">Message</label>
<input type="text" id="message-input" />
<button type="submit" id="send-button">Send</button>
</form>
I have these two pieces of code, my question is how can I pass the value from the input tag in the form to the afterSubmission method?
Assuming this is the same component, an easy solution would be to use state.
Your code snippets aren't very useful, just as an FYI for future questions, but here's an example of me tracking form state.
import { useState } from "react";
export default function App() {
const [message, setMessage] = useState("test");
const handleChange = (e) => {
setMessage((old) => e.target.value);
};
const afterSubmission = (e) => {
e.preventDefault();
console.log(message);
};
return (
<div className="App">
<form onSubmit={afterSubmission}>
<input
type="text"
id="message-input"
value={message}
onChange={handleChange}
/>
<button
type="submit"
id="send-button"
>
Send
</button>
</form>
</div>
);
}
As you can see, this tracks your message in the component's state which makes it accessible in the afterSubmission function.
I have several pages on my react app.
Edit, Delete, Create, Home
I am using React Router for navigation. I found this issue:
When I refresh a page that has :id in its params like "/games/edit/1" or "/games/details/2" my style.css is not loading. Instead in its it loads "You need to enable JavaScript to run this app." when I inspect the Networking tab in my browser.
NOTE : My style.css is included in the index.html file
Here is my Edit Component :
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { editGame, getGame } from 'services/api/games';
import { getAccessToken } from 'utils/userToken';
export const Edit = () => {
const navigate = useNavigate();
const { id } = useParams();
const [formData, setFormData] = useState({
category: '',
title: '',
maxLevel: '',
imageUrl: '',
summary: '',
});
useEffect(() => {
getGame(id).then(({ category, title, maxLevel, imageUrl, summary }) => {
let newForm = {
category,
title,
maxLevel,
imageUrl,
summary,
};
setFormData((formData) => newForm);
});
}, [id]);
function onChange(ev) {
const name = ev.target.name;
const value = ev.target.value;
const newForm = {
...formData,
[name]: value,
};
setFormData((formData) => newForm);
}
async function onSubmite(ev) {
ev.preventDefault();
const token = await getAccessToken();
const response = await editGame(id, token, formData);
if (response.code === 403) {
return window.alert('You are not authorized');
}
navigate(`/games/${id}`);
}
return (
// <!-- Edit Page ( Only for the creator )-->
<section id="edit-page" className="auth">
<form id="edit" onSubmit={onSubmite}>
<div className="container">
<h1>Edit Game</h1>
<label htmlFor="leg-title">Legendary title:</label>
<input
type="text"
id="title"
name="title"
value={formData.title}
onChange={onChange}
/>
<label htmlFor="category">Category:</label>
<input
type="text"
id="category"
name="category"
value={formData.category}
onChange={onChange}
/>
<label htmlFor="levels">MaxLevel:</label>
<input
type="number"
id="maxLevel"
name="maxLevel"
min="1"
value={formData.maxLevel}
onChange={onChange}
/>
<label htmlFor="game-img">Image:</label>
<input
type="text"
id="imageUrl"
name="imageUrl"
value={formData.imageUrl}
onChange={onChange}
/>
<label htmlFor="summary">Summary:</label>
<textarea
name="summary"
id="summary"
onChange={onChange}
value={formData.summary}
></textarea>
<input
className="btn submit"
type="submit"
value="Edit Game"
/>
</div>
</form>
</section>
);
};
Try importing your CSS into the index.js or App.js file (not sure your folder structure) instead of the in your index.html file.
import '../some/route/style.css';
So I have a parent component which contains a form and a child component(this component also has a form , here its a dynamic form meaning when the add button is hit it creates another form below so that user can add a list of parameters of various types . Basically I am trying to achieve state lifting from child to component . I would be really grateful if someone could help me out , I have been stuck on this since a week and I tried a lot . I would appreciate if someone could refactor my code so that it works . I have also attached a gif which shows the form
GIF : https://drive.google.com/file/d/15YXGhvo0OMCh4ch44q4S88wcqOB7i2ew/view?usp=sharing
Parent Component - ParamsForm.js
import React, { useState, useEffect } from 'react'
import { Form } from 'react-bootstrap'
import styles from '../style.module.css'
import Operations from './Operations'
import OperationsFormTest from './OperationsFormTest'
const ParamsForm = () => {
const[isToggled,setIsToggled] = useState(false)
const[formData,setFormData] = useState(
{url:'',endpoint:'',name:'',description:'',type:'',required:''}
)
const handleSubmit = (e) =>{
e.preventDefault()
console.log('Form Data is :' , formData)
}
const handleChangeInput = (index,e) =>{
const values = [...formData]
values[index][e.target.name] = e.target.value
setFormData(values)
}
const handleAddFields = () =>{
setFormData([...formData,{url:'',endpoint:'',name:'',description:'',type:'',required:''}])
}
const handleRemoveFields = (index) =>{
const values = [...formData]
values.splice(index,1)
setFormData(values)
}
useEffect(()=>{
console.log(isToggled)
},[isToggled])
return (
<div className={styles.paramFormsContainer}>
{setFormData}
<form onSubmit={handleSubmit}>
<input type='text' name='url' placeholder='Url..' value={formData.url} onChange={handleChangeInput}></input>
<input type='text' name='endpoint' placeholder='Endpoint...' value={formData.endpoint} style={{flex : 1 }} onChange={handleChangeInput}></input>
<button type='button' onClick={()=>setIsToggled(!isToggled)} className={styles.pathParamFormsBtn}>Path Params</button>
{isToggled && <OperationsFormTest name={formData.name} description={formData.description} type={formData.type} required={formData.required} handleAddFields={handleAddFields} nameFunc={handleChangeInput} descriptionFunc={handleChangeInput} typeFunc={handleChangeInput} requiredFunc={handleChangeInput} handleRemoveFields={handleRemoveFields}></OperationsFormTest>}
<br></br><br></br>
<button type='submit' onClick={handleSubmit}>Submit</button>
</form>
</div>
)
}
export default ParamsForm
Child Component - OperationsFormTest.js
import React, { useEffect, useState } from 'react'
import styles from '../style.module.css'
import {FaInfo,FaFileInvoiceDollar} from 'react-icons/fa'
import ReactTooltip from "react-tooltip";
const OperationsFormTest = ({name,type,description,required,nameFunc,descriptionFunc,typeFunc,requiredFunc,handleAddFields,handleRemoveFields}) =>{
const ChildToParentName = (e) =>{
nameFunc(e)
}
const ChildToParentType = (e) =>{
typeFunc(e)
}
const ChildToParentDescription = (e) =>{
descriptionFunc(e)
}
const ChildToParentRequired = (e) =>{
requiredFunc(e)
}
return(
<>
<div>
<div className={styles.pathParamsFormParentContainer}>
<div className={styles.pathParamsFormChildContainer}>
<label>Name : </label>
<input name='name' type='text' placeholder='Name..' value={name} onChange={ChildToParentName}></input><br></br><br></br>
<label>Description : </label>
<input name='description' type='text' placeholder='Description..' value={description} onChange={ChildToParentDescription}></input><br></br><br></br>
<select name = 'type' value = {type} onChange={ChildToParentType}>
<option>Any</option>
<option>String</option>
<option>Boolean</option>
</select>
<label>Required : </label>
<input name='required' type='text' placeholder='Yes or No..' value={required} onChange={ChildToParentRequired}></input><br></br><br></br>
<button type='button' onClick={()=>handleAddFields()}>Add</button>
<button type='button' onClick={()=>handleRemoveFields()}>Remove</button><br></br><br></br>
</div>
</div>
</div>
</>
)
}
export default OperationsFormTest
I hope you will get some idea from this simple codes.
function Home() {
// Async await is up to you
const onSubmit = async (data)=> {
console.log(data);
}
return (
<Form onSubmit={onSubmit} />
)
}
function Form() {
const [text,setText] = useState("");
// async await is up to you
const handleSubmit = async (e)=> {
e.preventDefault();
await onSubmit(text);
setText("");
}
return (
<form onSubmit={handleSubmit}>
<input type={"text"} value={text} onChange={e=>setText(e.target.value)} />
<input type={"submit"} />
</form>
)
}
I am trying to post new information about a cow to my cow API, however, everytime i hit the submit button on my frontend, it seems to be sending an empty object rather than the name of the cow, description of the cow, and image of the cow (via url). What is causing it to send an empty object versus my desired data?
Here is the frontend code:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import './App.css';
const baseUrl = "http://localhost:3001/api/cows"
function Display({setNameOfCow, setImageOfCow, setDescriptionOfCow, nameOfCow, imageOfCow, descriptionOfCow}) {
axios.get(baseUrl)
.then(res => res.data)
.then(res => {
setNameOfCow(res.name)
setImageOfCow(res.image)
setDescriptionOfCow(res.description)
})
return (
<div>
<p>{nameOfCow}</p>
<img src={imageOfCow}/><p>{descriptionOfCow}</p>
</div>
)
}
function Input({setNameOfCow, setImageOfCow, setDescriptionOfCow, nameOfCow, imageOfCow, descriptionOfCow}) {
function handleSubmit(e) {
e.preventDefault()
let newObject = {
name: nameOfCow,
description: descriptionOfCow,
image: imageOfCow
}
axios.post(baseUrl, newObject)
}
return (
<div>
<form>
<label htmlFor="name">name: </label>
<input type="text" id="name" onChange={(e) => {
const eTarget = e.target.value
setNameOfCow(eTarget)}}/><br></br>
<label htmlFor="description">description: </label>
<input type="text" id="description" onChange={(e) => {
const eTargetDesc = e.target.value
setDescriptionOfCow(eTargetDesc)}}/><br></br>
<label htmlFor="image">image url: </label>
<input type='text' id="image" onChange={(e) => {
const eTargetImage = e.target.value
setImageOfCow(eTargetImage)}}/><br></br>
<button type="submit" onSubmit={handleSubmit}>Add a cow!</button>
</form>
</div>
)
}
function App() {
const [nameOfCow, setNameOfCow] = useState('')
const [descriptionOfCow, setDescriptionOfCow] = useState('')
const [imageOfCow, setImageOfCow] = useState('')
return (
<div className="App">
<Input imageOfCow={imageOfCow} setNameOfCow={setNameOfCow} setDescriptionOfCow={setDescriptionOfCow} setImageOfCow={setImageOfCow} />
<Display setNameOfCow={setNameOfCow} setImageOfCow={setImageOfCow} setDescriptionOfCow={setDescriptionOfCow} nameOfCow={nameOfCow} imageOfCow={imageOfCow} descriptionOfCow={descriptionOfCow} />
</div>
);
}
export default App
and here is the image showing the empty objects being posted:
Looking into your Input component props:
function Input({setNameOfCow, setImageOfCow, setDescriptionOfCow, nameOfCow, imageOfCow, descriptionOfCow}) {...
We can see that you missing to pass this props when using this component:
<Input imageOfCow={imageOfCow} setNameOfCow={setNameOfCow} setDescriptionOfCow={setDescriptionOfCow} setImageOfCow={setImageOfCow} />
The correct way to use is something like:
<Input
imageOfCow={imageOfCow}
nameOfCow={nameOfCow}
descriptionOfCow={descriptionOfCow}
setNameOfCow={setNameOfCow}
setDescriptionOfCow={setDescriptionOfCow}
setImageOfCow={setImageOfCow}
/>
Also the correct way to prevent the form default behavior is setting the onSubmit and the handleSubmit at the form attribute (you can remove from the button):
<form onSubmit={handleSubmit}>
Otherwise a very nice change is to put your axios request inside a useEffect hook to prevent your app from making request every time it re-render.
Using something like this the app will make the request only at the first component render.
const getCow = async (baseUrl) => {
const cow = await axios.get(baseUrl);
setNameOfCow(cow.name);
setImageOfCow(cow.image);
setDescriptionOfCow(cow.description);
};
useEffect(() => {
getCow(baseUrl);
}, []);