I must post {input} data to http://localhost:4000/prediction with Axios. But {input} turns undefined.
I am using const instead of class Main extends component. onChange, it sets form data.
const Main = ({ value, suggestions, auth: { user } }) => {
const [formData, setFormData] = useState("");
const [messages, setMessages] = useState([]);
const { input } = formData;
const onChange = e => setFormData(e.target.value);
const onSubmit = event => {
event.preventDefault();
setMessages(prevMsgs => [...prevMsgs, formData]);
console.log({ input });
Axios post.
axios
.post(
`http://localhost:4000/prediction`,
{ input },
{ crossdomain: true }
)
.then(res => {
console.log(res.data);
//setMessages(prevMsgs => [...prevMsgs, formData]);
})
.catch(error => {
console.log(error.message);
});
};
Return (form) with onSubmit, onChange.
return (
<div className="true">
<br />
<form noValidate onSubmit={e => onSubmit(e)}>
<div className="input-group mb-3">
<input
name="input"
type="text"
className="form-control"
placeholder="Type text"
onChange={e => onChange(e)}
/>
)}
<div className="input-group-append">
<button className="btn btn-outline-secondary">Send</button>
</div>
</div>
</form>
</div>
);
};
As I have mentioned in the comment section formData is a string as I see which does not have a property called input what you try to destructure and that's why it is undefined always.
If you really need that format for axios then you can try change the structure of formData with useState as the following first:
const [formData, setFormData] = useState({input: null});
Then maybe you can try updating as:
const onChange = e => setFormData({input: e.target.value});
I hope that helps!
Related
I'm currently converting the logic in my mern (with typescript) project to use React/Tanstack query to learn this tool better.
I want to use useMutation to handle the post request logic from the details inputted in the form, in this login component but can't figure out how to do this. Any tips would be appreciated thanks. Below is the code from my login component
const Login = () => {
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorMsg, setErrorMsg] = useState("");
const [state, setState] = useContext(UserContext);
const handleSubmit = async (e: { preventDefault: () => void }) => {
e.preventDefault();
let response;
const { data: loginData } = await axios.post("http://localhost:5001/auth/login", {
email,
password,
});
response = loginData;
if (response.errors.length) {
return setErrorMsg(response.errors[0].msg);
}
setState({
data: {
id: response.data.user.id,
email: response.data.user.email,
stripeCustomerId: response.data.user.stripeCustomerId,
},
loading: false,
error: null,
});
localStorage.setItem("token", response.data.token);
axios.defaults.headers.common["authorization"] = `Bearer ${response.data.token}`;
navigate("/dashboard");
};
return (
<div className="login-card">
<div>
<h3>Login</h3>
</div>
<form onSubmit={handleSubmit}>
<div className="login-card-mb">
<label>Email</label>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<div className="login-card-mb">
<label>Password</label>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
{errorMsg && <p>{errorMsg}</p>}
<button type="submit">Submit</button>
</form>
</div>
);
};
After setting up your project to use React Query ( Check the docs if you have not). You want to extract your api call to a separate function that takes an object. This object will hold the values you would like to post.
const Login = (dataToPost) => {
let res = await axios.post('url', dataToPost)
return res.data
}
Now that you have that, you can import useMutation from React Query. Once imported you can now use the hook. UseQuery, useMutation both contain a data variable so no need to create state for the data returned from your endpoint. In this example, I'm deconstructing the data and loading state. But most importantly the mutate function. Which allows you to fire off your api call. We add our api call to the hook. I'm renaming the mutate function to doLogin. It's a habit
const {data,isLoading,mutate:doLogin} = useMutation(Login)
Finally we can just call mutate(objectWithValues) wherever you want in your code. The data will initially be null and isLoading will be true once called. To tie it all together. Your handleSubmit could look as follows
const handleSubmit = () => {
e.preventDefault();
doLogin({email,password})
}
You also have the option of running functions on a success or error of the mutation
const {data,isLoading,mutate: doLogin} =
useMutation(Login, {
onError: (err) => console.log("The error",err),
onSuccess:(someStuff)=>console.log("The data being returned",someStuff)
})
I have a form with some input with a default value and its hidden. I want to submit its value, together with other inputs to the database using an API, I have tried the following method, but it is not working. Kindly help.
const PostWidget = ({ post }) => {
const [loading, setLoading] = useState(false);
const [show, setShow] = useState(false);
const [commentInput, setComment] = useState({
post_id: '',
commentcontent: '',
});
const [errorlist, setError] = useState([]);
const handleInput = (e) => {
e.persist();
setComment({ ...commentInput, [e.target.name]: e.target.value });
}
const submitComment = (e) => {
e.preventDefault();
setLoading(true);
const data = {
post_id: commentInput.post_id,
commentcontent: commentInput.commentcontent,
};
axios.post(`/api/store-comment`, data).then((res) => {
if (res.data.status === 200) {
toast.success(res.data.message, "success");
setLoading(false);
}
else if (res.data.status === 422) {
toast.error("Your Comment is too Long", "", "error");
setError(res.data.errors);
setLoading(false);
}
});
}
return (
<div className="form-group boxed">
<div className="input-wrapper">
<form onSubmit={submitComment}>
<input defaultValue={post.postid} hidden />
<input type="text" name="commentcontent" className="form-control" onChange={handleInput} value={commentInput.commentcontent} placeholder="Write your Comment" />
<button type="submit" className="send-input">
{loading ? <><span className="spinner-border spinner-border-sm spinner-comment" role="status" aria-hidden="true"></span></> : <><i className="fi fi-rr-arrow-circle-right"></i></>}
</button>
</form>
</div>
);
}
export default PostWidget;
I think your issue might be due to the fact that you're setting the initial state of post_id to an empty string and as far as I can tell, it never gets updated.
I don't think you even need to keep post_id in the commentInput state. Just remove it and change your data object to:
const data = {
post_id: post.post_id,
commentcontent: commentInput.commentcontent,
};
Inside of your submitComment you can use:
const postId = e.target["postId"].value
If you add following to your hidden input:
name="postId"
But in your instance you can just use post.postid that you are getting from props instead of getting it from hidden input.
it updates only the lastly typed input box value in the state and other are undefined
i get this in console
Object { Name: undefined, Age: "123", City: undefined }
second time
Object { Name: undefined, Age: undefined, City: "city" }
Form.jsx
import React, {useState} from 'react';
const Form = (props) => {
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
[e.target.name]: e.target.value,
})
}
const infoSubmit = e =>{
e.preventDefault();
let data={
Name:formData.Name,
Age:formData.Age,
City:formData.City
}
props.myData(data);
}
return (
<div className="">
<form onSubmit={infoSubmit} autoComplete="off">
<div className="form-group mb-6">
<label className="">Name:</label>
<input type="text" onChange={infoChange} name="Name" value={formData.Name} className=""placeholder="Enter Name" />
</div>
<div className="form-group mb-6">
<label className="">City:</label>
<input type="text" onChange={infoChange} name="City" value={formData.City} className=""
placeholder="Enter Age" />
</div>
<button type="submit" className="">Submit</button>
</form>
</div>
);
};
export default Form;
App.jsx
this is App.jsx file, here i get the data prop and display it in console.log
import React from 'react';
import Form from './components/Form';
import Table from './components/Table';
const App = () => {
const create = (data) => {
console.log(data);
}
return (
<div className='flex w-full'>
<div className=''>
<Form myData={create} />
</div>
<div className=''>
<Table />
</div>
</div>
);
};
export default App;
You're stomping the previous state with the most recent change. If you want to preserve the existing state you have to include it in the update.
setFormData({
...formData,
[e.target.name]: e.target.value,
})
with react-hooks you need to set the entire object again.
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
// spread the current values here
...formData,
// update the current changed input
[name]: value,
})
or, even better IMHO. You have one state for each prop
const [name, setName] = useState('');
const [age, setAge] = useState('');
const [city, setCity] = useState('');
// ...
<input onChange={({target: {value}}) => setName(value)} />
<input onChange={({target: {value}}) => setAge(value)} />
<input onChange={({target: {value}}) => setCity(value)} />
Change this
const infoChange = e => {
const { name,value} = e.target;
setFormData({...formData
[e.target.name]: e.target.value,
})
}
How do I add a click handler to refetch data from my API based on my input ON CLICK?
In my console I'm getting back data if I input "Jon Snow" for instance because the onChange set to e.target.value but not sure how to fetch this on button click.
Code Sandbox: https://codesandbox.io/s/pedantic-lichterman-4ev6f?file=/src/game.jsx
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function Game() {
const [error, setError] = useState(null);
const [name, setName] = useState("");
const handleSubmit = e => {
e.preventDefault();
console.log( name );
}
const handleClick = e => {
// ??
}
useEffect(() => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then((data) => {
console.log(data[0].name); // the data I want back
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}, [name]);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
);
}
When the Submit button is clicked it will trigger onSubmit event, no need for you to handle the onClick event separately.
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function Game() {
const [error, setError] = useState(null);
const [name, setName] = useState("");
const handleSubmit = e => {
e.preventDefault();
console.log( name );
fetchData(name);
}
const fetchData = (name) => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then((data) => {
console.log(data[0].name); // the data I want back
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}
useEffect(() => {
fetchData(name);
}, []);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
);
}
Add another stateful variable. You need not only a value and setter for the input value but also a value and setter for the API results you want to be able to use elsewhere. Maybe something like
const [searchText, setSearchText] = useState('');
const [result, setResult] = useState('');
// inside fetch callback:
setResult(data[0]?.name ?? ''); // use optional chaining to not throw an error
// if there is no result
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Name"
/>
And then you can use the result where you need.
Live demo:
const App = () => {
const [error, setError] = React.useState(null);
const [searchText, setSearchText] = React.useState('');
const [result, setResult] = React.useState('');
const handleSubmit = e => {
e.preventDefault();
console.log( name );
}
React.useEffect(() => {
fetch(`https://anapioficeandfire.com/api/characters?name=${searchText}`)
.then((res) => res.json())
.then((data) => {
setResult(data[0] ? data[0].name : '');
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}, [searchText]);
console.log(result);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={e => e.preventDefault()}/>
</form>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
You can use direct state variable [name] in handleClick function.
The other answers are all correct that you should trigger the fetch in your handleSubmit. I just wanted to chime in with some sample code for rendering results since you asked for help with that.
The API returns an array of characters. We want to map through that result and show each character. We also want to tell the user if there were no results (especially since this API seems to only work with an exact name and will return a result for "Arya Stark" but not for "Stark"). We don't want to show that "No Characters Found" message before they have submitted.
I am using a setState hook to store the array of character matches from the API. I am initializing the state to undefined instead of [] so that we only show the no results message if it gets set to [].
My code allows the user to submit multiple times. We keep displaying the previous results until they submit a new search. Once we have an array in our characters state, we display those results.
// an example component to render a result
const RenderCharacter = ({ name, aliases }) => {
return (
<div>
<h2>{name}</h2>
{aliases.length && (
<div>
<h3>Aliases</h3>
<ul>
{aliases.map((a) => (
<li key={a}>{a}</li>
))}
</ul>
</div>
)}
</div>
);
};
export default function Game() {
// current form input
const [name, setName] = useState("");
// save characters returned from the API
// start with undefined instead of empty array
// so we know when to show "no characters found" message
const [characters, setCharacters] = useState();
// store API errors
const [error, setError] = useState(null);
const fetchData = () => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then(setCharacters) // store data to state
.then(() => setError(null)) // clear previous errors
.catch((error) => {
console.log("Error", error);
setError(error);
setCharacters(undefined); // clear previous character matches
});
};
const handleSubmit = (e) => {
e.preventDefault();
fetchData();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" />
</form>
{characters !== undefined &&
(characters.length === 0 ? (
<div>No Characters Found</div>
) : (
<div>
{characters.map((character) => (
<RenderCharacter key={character.name} {...character} />
))}
</div>
))}
{error !== null && <div>Error: {error.message}</div>}
</div>
);
}
Code Sandbox Demo (with typescript annotations)
I have a react component that looks like the one given below.
The form inputs are handled using the onInputChange function and form submit is handled by onFormSubmit
function RegisterForm() {
// formData stores all the register form inputs.
const [formData, setFormData] = useState(registerDefault);
const [errors, posting, postData] = useDataPoster();
function onInputChange(event: ChangeEvent<HTMLInputElement>) {
let update = { [event.target.name]: event.target.value };
setFormData(oldForm => Object.assign(oldForm, update));
}
function onFormSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const onSuccess: AxiosResponseHandler = response => {
setFormData(Object.assign(formData, response.data));
};
postData("/api/register", formData, onSuccess);
}
return (
<form onSubmit={onFormSubmit}>
<FormTextInput
name="full_name"
label="Name"
errors={errors.full_name}
onChange={onInputChange}
/>
<FormTextInput
name="email"
label="Email address"
type="email"
errors={errors.email}
onChange={onInputChange}
/>
<button type="submit" className="theme-btn submit" disabled={posting}>
{posting && <span className="fas fa-spin fa-circle-notch"></span>}
Create
</button>
</form>
);
}
My app has more than 50 similar forms and I wonder if I have to copy paste these two functions on all the other forms. onInputChange won't be changing a bit and the url is the only variable in onFormSubmit.
I am thinking of a class based approach with setFormData and postData as properties and the functions in question as class methods. But in that case, I have to bind the handlers with the class instance, so that handlers have a valid this instance.
Is there any other way to do this? How would you avoid the repetition of these two code blocks in all the form components?
Thanks
you could create a custom hook, something like this:
const [formState, setFormState] = useFormStateHandler({name: ''})
<input value={formState.name} onChange={event => setFormState(event, 'name')} />
where the definition looks like this:
export default function useFormStateHandler(initialState) {
const [state, setState] = useState(initialState)
const updater = (event, name) => {
setState({...state, [name]: event.target.value})
}
return [state, updater]
}
Create an HOC to inject input handlers to the form components with added params for url.
function RegisterForm(props) {
// specific function
const specific = () => {
const formData = props.formData; // use passed state values
// use form data
}
}
function withInputHandlers(Component, params) {
return function(props) {
// states
function onInputChange(...) {...}
function onFormSubmit(...) {
// use params.url when submitting
postData(params.url, formData, onSuccess);
}
// inject input handlers to component and state values
return (
<Component {...props} formData={formData} onChange={onInputChange} onSubmit={onFormSubmit} />
);
}
}
// Usage
const EnhancedRegisterForm = withInputHandlers(
RegisterForm,
{ url: 'register_url' } // params
);
const EnhancedSurveyForm = withInputHandlers(
Survey,
{ url: 'survey_url' } // params
)
This change may help you
function RegisterForm() {
// formData stores all the register form inputs.
const [formData, setFormData] = useState(registerDefault);
const [errors, posting, postData] = useDataPoster();
const onInputChange = name => event => {
let update = { [name]: event.target.value };
setFormData(oldForm => Object.assign(oldForm, update));
}
const onFormSubmit = url => event =>{
event.preventDefault();
const onSuccess: AxiosResponseHandler = response => {
setFormData(Object.assign(formData, response.data));
};
postData(url, formData, onSuccess);
}
return (
<form onSubmit={onFormSubmit("/api/register")}>
<FormTextInput
name="full_name"
label="Name"
errors={errors.full_name}
onChange={onInputChange("full_name")}
/>
<FormTextInput
name="email"
label="Email address"
type="email"
errors={errors.email}
onChange={onInputChange("email")}
/>
<button type="submit" className="theme-btn submit" disabled={posting}>
{posting && <span className="fas fa-spin fa-circle-notch"></span>}
Create
</button>
</form>
);
}