fetch api happens before I submit the form - javascript

I use react js as frontend framework and laravel as backend framework. I also use proxy. I have a function that hanlde submit of my form. Everytime I load my form page I got two responses on my console although I haven't hit the submit button yet. This is my component CreateDaerah. I use function component
import React, { useEffect, useState} from 'react';
const CreateDaerah = () => {
const [nama, setNama] = useState('');
const handleSubmit = (url = '', data = {}) => {
fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
})
.then(response => console.log(response))
.catch(err => console.error(err));;
}
return(
<div className="card-create">
<form onSubmit={handleSubmit(`/api/provinsi`, nama)}>
<h1>Form Provinsi</h1>
<label htmlFor="nama">Nama</label>
<input type="text"
className="nama-provinsi"
name="nama"
placeholder="masukkan nama"
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
Also how can I get the nama value from the input? Should I use onChange attribute?

This is because you are calling handleSubmit immediately at each render rather than providing a function to be called at submit. Instead you should update it to something like:
import React, { useEffect, useState} from 'react';
const CreateDaerah = () => {
const [nama, setNama] = useState('');
const handleSubmit = (url = '', data = {}) => {
fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
})
.then(response => console.log(response))
.catch(err => console.error(err));;
}
return(
<div className="card-create">
<form onSubmit={() => handleSubmit(`/api/provinsi`, nama)}>
<h1>Form Provinsi</h1>
<label htmlFor="nama">Nama</label>
<input type="text"
className="nama-provinsi"
name="nama"
placeholder="masukkan nama"
/>
<button type="submit">Submit</button>
</form>
</div>
);
}

You're calling the handleSubmit function immediately on the render, so you either need to pass a reference to the function instead with a callback, or just pass the function reference without those arguments.
I probably wouldn't pass those arguments. I wouldn't even use a form element as elements don't need to be wrapped in it. I'd would declare the URL in the component and place the handleSubmit on the button. That way you don't have to deal with the form updating the page.
Place an onChange handler on your input which calls a function that updates the state, and have the value of that input be the result of the state.
Here's a shorter working example.
const { useState } = React;
function Example() {
const [ numa, setNuma ] = useState('');
const url = `/api/provinsi`;
function handleInput(e) {
setNuma(e.target.value);
}
function handleClick() {
console.log('Button clicked');
console.log(`Final state: ${numa}`);
// fetch(url) using numa state
}
return (
<div>
<input value={numa} onChange={handleInput} />
<button onClick={handleClick}>Click me</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Related

Input values in state as route params

I want to send input values as route params to server. Should I write a function to encode values? I'm trying to do this without any libraries..
By coincidence, I mistyped localhost 8000,then the browser appended localhost 3000 url to 8000 and only then did the set Search Params work and I did get the values appended to as route params but the url of server wasn't right one, obviously.
Here is my code:
import axios from 'axios';
import React, { useState } from 'react';
import { useSearchParams } from 'react-router-dom';
const AddProductForm = ({ id }) => {
let [searchParams, setSearchParams] = useSearchParams();
const [input, setInput] = useState({
title: '',
price: '',
rating: '',
description: '',
});
const handleSubmit = (e) => {
e.preventDefault();
setSearchParams(input)
axios
.put(`http://localhost:8080/api/v1/products/${id}?` + searchParams)
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
const onChange = (e) => {
//function to handle change of each input
}
return (
<div className='container' >
<form className='form' onSubmit={handleSubmit}>
<div className='form_inputs'>
<h1>Edit Product</h1>
<div className='flex-column'>
<label>Add new title</label>
<input
type='text'
value={input.title}
onChange={onChange}
name='title'
placeholder='Title'
/>
</div>
<div className='flex-column'>
<label>Add new price</label>
<input
type='number'
value={input.price}
onChange={onChange}
name='price'
placeholder='Price'
/>
</div>
//All other inputs
<button className='btn-block' type='submit'>
Create
</button>
</form>
</div>
);
};
export default AddProductForm;
On Submitting I only get empty object URLSearchParams{}
The setSearchParams function works like the navigate function in that it effects a navigation action but only updates the current URL's search string. The code isn't actually updating the searchParams variable.
You want to take the input state and create a new URLSearchParams object.
Example:
const handleSubmit = (e) => {
e.preventDefault();
const searchParams = new URLSearchParams(input);
axios
.put(`http://localhost:8080/api/v1/products/${id}?${searchParams.toString()}`)
.then(console.log)
.catch(console.warn);
};

Sending form data from react client side to node.js server side

I have a login form at the client side (react) that I try to submit and pass the credentials to the login function at the server side (node.js)
when I use postman to send raw json object with the user name and password it works fine, but when I sent it through the client side the req.body contains only this: [[Prototype]]:
Object
what am I doing wrong here?
here is the code of the component that contains the form:
import React from 'react';
import '../signIn/signIn.component.css'
import { Link } from "react-router-dom";
import { useState, useEffect } from "react";
export default function SignIn() {
const [UserName, setUsername] = useState(null);
const [PassWord, setPassWord] = useState(null);
const [FormData, setFormData] = useState({});
useEffect(() => {
setFormData({ UserName: UserName, PassWord: PassWord });
}, []);
const submitFormSignIn = () => {
const testURL = "http://localhost:3100/login";
const myInit = {
method: "POST",
mode: 'no-cors',
body: JSON.stringify(FormData),
headers: {
'Content-Type': 'application/json'
},
};
const myRequest = new Request(testURL, myInit);
fetch(myRequest).then(function (response) {
return response;
}).then(function (response) {
console.log(response);
}).catch(function (e) {
console.log(e);
});
}
return (
<React.Fragment>
<form onSubmit={(e) => { submitFormSignIn(); e.preventDefault(); }}>
<div className="signIn-form-container">
<h1 className="welcome-header">Welcome</h1>
<div className="userName-form-container">
<input className="input-user-name" type="text" name="userName" placeholder='User name'
//should start with an alphabet so. All other characters can be alphabets, numbers or an underscore so.
required
pattern="^[A-Za-z][A-Za-z0-9_]{7,29}$"
minLength={"6"}
maxLength={"20"}
onChange={(e) => setUsername(e.target.value)}
></input>
</div>
<div className="password-form-container">
<input className="input-password" type="password" name="passWord" required
//Minimum eight characters, at least one uppercase letter, one lowercase letter and one number:
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$"
autoComplete="on"
minLength={"9"}
maxLength={"20"}
placeholder='Password'
onChange={(e) => setPassWord(e.target.value)}
></input>
</div>
<div className="forgot-remember-container">
<Link className="userName-forgot-link" to="/userNameRecovery">Forgot user name?</Link>
<Link className="password-forgot-link" to="/passwordRecovery">Forgot password?</Link>
</div>
<div className="form-submit-btn-container">
<button className="form-submit-btn">Sign in</button>
</div>
<div className="sign-up-container">
<a>Don't have an account?</a>
<Link className="signUp-link" to="/register">Sign up</Link>
</div>
<hr></hr>
</div>
</form>
</React.Fragment>
);
}
Your useEffect is fired only once - after initial render, because it's dependency array is empty. It means, you don't set for formData state with proper data.
I see two solutions:
Either fill the dependency array with UserName and PassWord states:
useEffect(() => {
setFormData({ UserName: UserName, PassWord: PassWord });
}, [UserName, PassWord]);
Or - and I would recommend this - easily create your body Object directly from UserName and PassWord states to :
body: JSON.stringify({UserName, PassWord}),
Small underline notice: states are variables, so their name should be camelCase, with lowercase at the beginning. Variables with UpperCase are intended to be React Components.
useEffect in this case absolutely unnecessary, so you have both and submit handler and useEffect that actually make you app rerender several extra times through setState, so Id build this something like that
import React from 'react';
import '../signIn/signIn.component.css'
import { Link } from "react-router-dom";
import { useState } from "react";
export default function SignIn() {
const [username, setUsername] = useState(null);
const [password, setPassword] = useState(null);
const submitFormSignIn = () => {
const testURL = "http://localhost:3100/login";
const myInit = {
method: "POST",
mode: 'no-cors',
body: JSON.stringify({ username, password }),
headers: {
'Content-Type': 'application/json'
},
};
const myRequest = new Request(testURL, myInit);
fetch(myRequest).then(function (response) {
return response;
}).then(function (response) {
console.log(response);
}).catch(function (e) {
console.log(e);
});
}
return (
<React.Fragment>
//same jsx
</React.Fragment>
);
}

unable to fetch a get request and print the output using fetch api

I am using react to get the data from an API using fetch API and print it but i was unable to retrieve data from after doing a fetch request. Here is the codelink. The input should be CC(C)(C)Br and the output is success message
import React, { useState } from "react";
import TextField from "#mui/material/TextField";
import Button from "#mui/material/Button";
export default function App() {
const [solutestate, setSolutestate] = useState("");
const [fetchData, setFetchData] = useState("");
console.log(solutestate);
console.log(fetchData);
let params = new URLSearchParams({
solute: solutestate
});
const onSubmit = (e) => {
fetch(
`https://fastapi-n7b7u.app/predict_two?${params}`,
{
method: "GET"
}
)
.then((res) => res.json())
.then((result) => {
setFetchData(result);
});
};
return (
<div className="App">
<>
<form noValidate onSubmit={onSubmit}>
<div>
Input: CC(C)(C)Br
<TextField
label="Solute"
variant="outlined"
onChange={(e) => setSolutestate(e.target.value)}
/>
<Button variant="contained">Submit</Button>
<TextField label="Result" variant="outlined" value={fetchData} />
</div>
</form>
</>
</div>
);
}
Couple of issues here.
Your submit button is not of type submit, so submit method is never called.
You will also want to put a preventDefault() on your submit handler, as the default will reload the page.
so changes are->
<Button type="submit" variant="contained">Submit</Button>
and
const onSubmit = (e) => {
e.preventDefault();
fetch(.....
Updated Sandbox
ps. This is not specifically about React, this is how forms work in
HTML.
const onSubmit = (e) => {
e.preventDefault()
let params = new URLSearchParams({
solute: solutestate
});
fetch(
`https://fastapi-ihub-n7b7u.ondigitalocean.app/predict_two?${params}`,
{
method: "GET",
content-type : "application/json"
}
)
.then((res) => res.json())
.then((result) => {
setFetchData(result);
});
};

How to input an existing value into an input field in React for an edit form

I have an existing Task with a title and a description, and I want to navigate to an edit form. By default I want the existing title and description values to populate the inputs using React. The important piece of this code I'm asking about is value={task.title}. Please ignore how the data is being pulled in (I'm new to React and I'm experimenting). The onChange and onSubmit handles work correctly, but the error obviously indicates I'm doing it wrong and it does cause occasional bugs.
I've tried abstracting those values into some sort of formValues state as well, but no matter how the values are being input, if the value={howeverIDoIt} is being directly manipulated I get the error.
import React, { useEffect, useState } from 'react';
import { HEADERS, TODO_URL } from '../urls';
import { useHistory, useParams } from 'react-router-dom';
const TaskEdit = () => {
const [task, setTask] = useState({});
const { id } = useParams();
const history = useHistory();
useEffect(() => {
fetch(`${TODO_URL}/api/tasks/${id}/`, {headers: HEADERS})
.then(response => response.json())
.then(responseJson => {
setTask(responseJson);
});
}, []);
const handleChange = (e) => {
setTask(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault();
const body = {
'title': e.target.form[0].value,
'description': e.target.form[1].value
}
fetch(
`${TODO_URL}/api/tasks/${id}/edit/`,
{
headers: HEADERS,
method: 'PUT',
body: JSON.stringify(body)
}
).then(res => res).catch(err => err);
history.push(`/task/${id}`)
}
return (
<form>
<div>
<label>Title</label>
<input type="text" onChange={handleChange} value={task.title} />
</div>
<div>
<label>Description</label>
<textarea onChange={handleChange} value={task.description}></textarea>
</div>
<button onClick={handleSubmit}>Submit</button>
</form>
);
}
export default TaskEdit;
I have tried putting in a default value for useState like so: useState({title: 'title', description: 'description'}) but that doesn't prevent the error, nor does adding this edit form into the Task.js component, where task is most definitely defined.
You have:
<input type="text" onChange={handleChange} value={task.title} />
Your handleChange method is:
const handleChange = (e) => {
setTask(e.target.value)
}
When your onChange fires, your task state will be set to a String (the value of <input />)
So when you are referencing task.title after your onChange fires, it will be undefined. The same is true for task.description.
Try this:
const handleTitleChange = (e) => {
setTask({...task, title: e.target.value})
}
const handleDescriptionChange = (e) => {
setTask({...task, description: e.target.value})
}
<input type="text" onChange={handleTitleChange} value={task.title} />
<textarea onChange={handleDescriptionChange} value={task.description} />
Alternatively, you could split up the task state to title and description, respectively.

Submit a form with data using a custom React hook

I'm having trouble figuring this out. I want to create a hook that's called to submit a form using fetch.
This is what I have right now. The component holding the form:
const MyForm = (): ReactElement => {
const [status, data] = useSubmitForm('https://myurl-me/', someData);
return <>
<div className='Feedback-form'>
<div className='body'>
<form>
<input type='text' name='username' placeholder='name' required />
<input type='email' name='email' placeholder='email' required />
<button className='submit-feedback-button' type='button'>Send feedback</button>
</form>
</div>
</div>
</>
}
The custom hook:
import { useState, useEffect } from 'react';
const useSubmitForm = (url: string, data: URLSearchParams): [string, []] => {
const [status, setStatus] = useState<string>('idle');
const [responseData, setData] = useState<[]>([]);
useEffect(() => {
if (!url) return;
const fetchData = async () => {
setStatus('fetching');
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'text/html',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data
});
const data = await response.json();
setData(data);
setStatus('fetched');
};
fetchData();
}, [url]);
return [status, responseData];
};
export default useSubmitForm;
My problem is that I think this hook is being called right away. How do I make this hook and call it in such a way that it's only called when the form is submitted and all the data I need to send in the request body is there to be included?
You are correct, the effect runs once when the component mounts and since url is truthy, it skips the early return and invokes fetchData.
How do I make this hook and call it in such a way that it's only
called when the form is submitted and all the data I need to send in
the request body is there to be included?
You need to also return a function for the component to invoke and pass along the form field values. I think you've a couple basic options.
Convert the form fields to be controlled inputs and store the field state in the component and invoke a "fetch" function returned from the useSubmitForm hook.
Return an onSubmit handler from the useSubmitForm to attach to your form element. The onSubmit handler would need to know what fields to access from the onSubmit event though, so passing an array of field names to the hook (i.e. a "config") makes sense.
Solution 1 - Use controlled inputs and returned fetch function
Unwrap the fetchData function from the useEffect hook and add a form field data parameter to it. Since fetch and response.json() can both throw errors/rejections you should surround this block in a try/catch. Return the custom fetchData function for the form to invoke.
useSubmitForm
const useSubmitForm = (
url: string,
data: URLSearchParams
): [function, string, []] => {
const [status, setStatus] = useState<string>("idle");
const [responseData, setData] = useState<[]>([]);
const fetchData = async (formData) => {
setStatus("fetching");
try {
const response = await fetch(url, {
method: "POST",
headers: {
Accept: "text/html",
"Content-Type": "application/x-www-form-urlencoded"
},
body: JSON.stringify(formData)
});
const data = await response.json();
setData(data);
setStatus("fetched");
} catch (err) {
setData(err);
setStatus("failed");
}
};
return [fetchData, status, responseData];
};
MyForm
const MyForm = (): ReactElement => {
const [fields, setFields] = useState({ // <-- create field state
email: '',
username: '',
});
const [fetchData, status, data] = useSubmitForm(
"https://myurl-me/",
someData
);
useEffect(() => {
// handle successful/failed fetch status and data/error
}, [status, data]);
const changeHandler = (e) => {
const { name, value } = e.target;
setFields((fields) => ({
...fields,
[name]: value
}));
};
const submitHandler = (e) => {
e.preventDefault();
fetchData(fields); // <-- invoke hook fetchData function
};
return (
<div className="Feedback-form">
<div className="body">
<form onSubmit={submitHandler}> // <-- attach submit handler
<input
type="text"
name="username"
placeholder="name"
onChange={changeHandler} // <-- attach change handler
value={fields.username} // <-- pass state
/>
<input
type="email"
name="email"
placeholder="email"
onChange={changeHandler} // <-- attach change handler
value={fields.email} // <-- attach state
/>
<button className="submit-feedback-button" type="submit">
Send feedback
</button>
</form>
</div>
</div>
);
};
Solution 2 - Return an onSubmit handler and pass an array of fields to the useSubmitForm
useSubmitForm
const useSubmitForm = (
url: string,
data: URLSearchParams,
fields: string[],
): [function, string, []] => {
const [status, setStatus] = useState<string>("idle");
const [responseData, setData] = useState<[]>([]);
const fetchData = async (formData) => {
setStatus("fetching");
try {
const response = await fetch(url, {
method: "POST",
headers: {
Accept: "text/html",
"Content-Type": "application/x-www-form-urlencoded"
},
body: JSON.stringify(formData)
});
const data = await response.json();
setData(data);
setStatus("fetched");
} catch (err) {
setData(err);
setStatus("failed");
}
};
const onSubmit = e => {
e.preventDefault();
const formData = fields.reduce((formData, field) => ({
...formData,
[field]: e.target[field].value,
}), {});
fetchData(formData);
}
return [onSubmit, status, responseData];
};
MyForm
const MyForm = (): ReactElement => {
const [onSubmit, status, data] = useSubmitForm(
"https://myurl-me/",
someData,
['email', 'username'] // <-- pass field array
);
useEffect(() => {
// handle successful/failed fetch status and data/error
}, [status, data]);
return (
<div className="Feedback-form">
<div className="body">
<form onSubmit={onSubmit}> // <-- attach submit handler
<input
type="text"
name="username"
placeholder="name"
/>
<input
type="email"
name="email"
placeholder="email"
/>
<button className="submit-feedback-button" type="submit">
Send feedback
</button>
</form>
</div>
</div>
);
};
In my opinion the second solution is the cleaner solution and requires less on consuming components to use.

Categories

Resources