I have a form, where I use the input from the input fields to send to a back-end.
For example, I have a variable looking something like this:
const [data, setData] = useState([])
const [inputField, setInputField] = useState()
Then I have a form that looks something like this:
<form onSubmit={fetchData}>
<input type="number" value={value} onChange={(e) => setInputField(e.target.value)} />
<button type="submit">Fetch data</button>
</form>
The fetchData is given by:
function fetchData(e?: any) {
e?.preventDefault();
POST("/api", {
inputField: inputField,
}).then(async (response) => {
const json = await response.json();
setData({
retrievedData: json.retrievedData,
});
});
}
I have other forms as well, where this onChange updating is good, but for some input fields I don't need it do update/re-render before the actual submit button that triggers the form are clicked.
So how do I update the state of the inputField when the button is clicked, instead of now where it updates every time I write a new character in the input field ?
Try this
import {useRef } from "react";
export const Temp = () => {
const inputField = useRef(null);
const onBtnClick = () => {
alert(inputField?.current?.value);
};
return (
<div>
<input type="text" ref={inputField} />
<button type="submit" onClick={onBtnClick}>
Fetch data
</button>
</div>
);
};
You can use useRef hook for that.
const inputNumber = useRef();
<input
ref={inputNumber}
id="number"
placeholder="33xx"
type="number"
/>
Then on button click you can get the value like that
inputNumber.current?.value,
You don't need a state for that and you don't even need a ref. You can get the form values directly from the submit event (event.target.<input name>.value). You will need to add the name property to the <input /> to make it accessible from the event target. Please, find the example below:
function Form() {
const [data, setData] = React.useState();
const onSubmit = (e) => {
e.preventDefault();
const inputField = e.target.inputField.value;
POST("/api", {
inputField: inputField,
}).then(async (response) => {
const json = await response.json();
setData({
retrievedData: json.retrievedData,
});
});
};
return (
<form onSubmit={onSubmit}>
<input type="text" name="inputField" />
<button type="submit">Submit</button>
</form>
);
}
ReactDOM.render(
<Form />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Related
I am developing an chrome extension where i need to authentication user but a very simple onClick button which calls a function is not working
this is the simple code where i want to show info on console when button is clicked
import React, { useState } from 'react';
const Login = () => {
const [user, setuser] = useState("");
const handleSubmit = (data) => {
data.preventDefault();
console.log("usernae: ");
console.log("Data: ", data.target);
}
const getInputValue = (event) => {
console.log(event.target.value)
// Select input element and get its value
console.log("I am heresdfg")
// let inputVal = document.getElementsByClassName("usernameInputField")[0].value;
// Display the value
// alert(inputVal);
}
return (
<div
id="login-form">
<p>
<div className='form'>
</div>
<input type="text"
id="username"
name="username"
className='usernameInputField'
value={user}
onChange={(event => setuser(event.target.value))}
placeholder="Username" required />
</p>
<p>
<button onClick={getInputValue} type="button" id="login">button</button>
</p>
</div>
);
};
export default Login;
It seems like you want the input value value inside the event handler if I'm not wrong, you can get it from the state - user as
const getInputValue = (event) => {
console.log(user)
}
as the event would be button's you wouldn't get the value of input from it's event and it is not required too as it's already in the react's state ....
Example:
const {useState} = React;
const App = () => {
const [name, setName] = useState("");
const submitHandler = () => {
console.log(name)
}
return (
<div>
Name: <input type="text" value={name} onChange={(e)=>setName(e.target.value)}/>
<button onClick={submitHandler}>Submit</button>
</div>
);
};
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
In the getInputValue function event is pointing to the button.
Change the event.target.value to user if you want to print the text into the console.
Here's the codesandbox.
If you don't want to use the value from useState then you can also check useRef hook which works in a similar way.
I am making 2 react components (PlayerSearch for sumbitting a form containing target player's name, and PlayerAPI for fetching request). I want it to re-render PlayerAPI everytime I hit the submit button OR everytime the submitted data is updated. So my code looks like this:
In PlayerSearch:
export function PlayerSearch() {
const [formData, setFormData] = useState({ APIkey: "", name: "" });
const [submittedData, setsubmittedData] = useState({ submittedAPIkey:"", submittedname:"" });
const onChange = (event) => {
setFormData({ ...formData, [event.target.name]: event.target.value });
};
function handlesubmit(e) {
e.preventDefault();
setsubmittedData({ ...submittedData, submittedAPIkey: formData.APIkey, submittedname: formData.name });
}
return <div className='player'>
<div className='inputfield'>
<form onSubmit={handlesubmit} method='GET' autoComplete="off">
<div>
<label htmlFor="APIkey">Your API key:</label>
<input placeholder='Your API key' onFocus={(e)=>{e.target.placeholder=''}} type="text" id="APIkey" name="APIkey" value={formData.APIkey} onChange={onChange}/>
</div>
<div>
<label htmlFor="name">Player name:</label>
<input placeholder='Player name' onFocus={(e)=>{e.target.placeholder=''}} type="text" id="name" name="name" value={formData.name} onChange={onChange}/>
</div>
<div>
<button type='submit'>Submit</button>
</div>
</form>
</div>
<div id='result'>
//This is where I render the PlayerAPI
{(submittedData.submittedAPIkey !== "" && submittedData.submittedname !== "") && <PlayerAPI APIkey={submittedData.submittedAPIkey} name={submittedData.submittedname} />}
</div>
</div>
}
Edit: I've found out that the form submit is not the problem. The problem is in the PlayerAPI and I fixed it.
The PlayerAPI before:
export function PlayerAPI(props) {
const [data, setdata] = useState({ accountId: ''});
const getPlayerID = async () => {
//some API fetching...
}
useEffect(()=>{
getPlayerID();
},[]);
return <div>
<div className='SearchResult'>
hello {JSON.stringify(data)}
</div>
</div>;
}
The PlayerAPI now:
import { useEffect, useState } from "react";
export function PlayerAPI(props) {
const [data, setdata] = useState({ accountId: ''});
const getPlayerID = async () => {
//some API fetching...
}
useEffect(()=>{
getPlayerID();
},[props.name, props.APIkey]);
return <div>
<div className='SearchResult'>
hello {JSON.stringify(data)}
</div>
</div>;
}
maybe this solution will help? Setting onSubmit in React.js
moving the e.preventDefault(); to the end of the handleSubmit
I've found out that the form submit is not the problem. The problem is in the PlayerAPI and I fixed it.
The PlayerAPI before:
export function PlayerAPI(props) {
const [data, setdata] = useState({ accountId: ''});
const getPlayerID = async () => {
//some API fetching...
}
useEffect(()=>{
getPlayerID();
},[]);
return <div>
<div className='SearchResult'>
hello {JSON.stringify(data)}
</div>
</div>;
}
The PlayerAPI now:
import { useEffect, useState } from "react";
export function PlayerAPI(props) {
const [data, setdata] = useState({ accountId: ''});
const getPlayerID = async () => {
//some API fetching...
}
useEffect(()=>{
getPlayerID();
},[props.name, props.APIkey]);
return <div>
<div className='SearchResult'>
hello {JSON.stringify(data)}
</div>
</div>;
}
So apparently somehow I thought that hitting the submit button again would activate the initial render of useEffect in PlayerAPI so I left the dependency array empty.
I have a page /studentprofile where user can look at their profile details, when the user clicks the button 'edit profile' they are brought to /editprofile and is able to update their profile with a form. Once they click on the button 'update', editProfile() function is called and the api updates the details respectively then navigates the user back to /studentprofile. However, once the user goes back to /studentprofile their old data is only shown and you have to manually reload the page to see the updated data, is there any way to fix this so the user don't have to reload the page themselves?
Here is the code of /editprofile. I am using react router dom v6.3.0
function StudentEdit() {
const navigate = useNavigate();
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [description, setDescription] = useState("")
const data = {
student_name: name,
email: email,
password: password,
description: description
}
function editProfile() {
const studentid = localStorage.getItem('studentid')
api.put(`/students/${studentid}`, data)
.then(
navigate('/studentProfile')
)
}
return (
<div className={editstyle.box}>
<form className={editstyle.form}>
<h2>Edit Profile</h2>
<input type='text' className={editstyle.input} placeholder='Name' onChange = {(e) => setName(e.target.value)} id ="postName" value={name}></input>
<input type='text' className={editstyle.input} placeholder='Email' onChange = {(e) => setEmail(e.target.value)} id ="postEmail" value={email}></input>
<input type='password' className={editstyle.input} placeholder='Password' onChange = {(e) => setPassword(e.target.value)} id ="postPassword" value={password}></input>
<input type='text' className={editstyle.input} placeholder='Description' onChange = {(e) => setDescription(e.target.value)} id ="postDescription" value={description}></input>
<div className={editstyle.btncol}>
<button type="update" className={editstyle.btn} onClick={() => editProfile()}>Update</button>
<button type="reset" className={editstyle.btn}>Discard Changes</button>
</div>
</form>
</div>
)
}
export default StudentEdit;
here is the component of /studentprofile
function StudentProfile() {
const [profile, setProfile] = useState([])
const navigate = useNavigate();
useEffect(() => {
const fetchProfile = async () => {
try {
const studentid = localStorage.getItem('studentid')
const response = await api.get(`/students/${studentid}`);
setProfile(response.data);
} catch (err) {
if (err.response) {
console.log(err.response.data);
} else {
console.log(`error: ${err.message}`)
}
}
}
fetchProfile()
}, [])
const delProfile = async () => {
try {
const studentid = localStorage.getItem('studentid')
await api.delete(`/students/${studentid}`);
navigate('/home');
} catch (err) {
console.log(`Error: ${err.message}`);
}
}
return(
<div>
{profile.map(profiles => (
<Profile key={profiles.student_id} profiles={profiles} delProfile={delProfile}/>
))}
</div>
)
}
export default StudentProfile;
app.js routing
function App() {
return (
<div>
<NavBar/>
<BrowserRouter>
<Routes>
<Route path="/studentprofile" element={<StudentProfile/>}/>
<Route path="/studentEdit" element={<StudentEdit/>}/>
</Routes>
</BrowserRouter>
</div>
)
}
I think I see the issue now, the form element in the StudentEdit component isn't having the default form action prevented which is reloading the page and React app.
The "update" button has an invalid type attribute value. The only valid type attribute values are "submit" (which is the default value), "button", and "reset". I suspect the form is being submitted and reloading the React app.
Fix the update button's type attribute and prevent the form element's default form action.
Another issue is the api.put Promise chain, the .then has an invalid callback, it's immediately invoking the navigate function. This should be a proper callback function.
Example:
function StudentEdit() {
...
function editProfile(e) { // <-- receive `onSubmit` event object
e.preventDefault(); // <-- prevent the default action
const studentid = localStorage.getItem("studentid");
api.put(`/students/${studentid}`, data)
.then(() => navigate("/studentProfile")); // <-- callback calls `navigate`
}
return (
<div className={editstyle.box}>
<form
className={editstyle.form}
onSubmit={editProfile} // <-- editProfile is onSubmit handler
>
...
<div className={editstyle.btncol}>
<button
type="submit" // <-- type="submit" here to submit form
className={editstyle.btn}
>
Update
</button>
<button type="reset" className={editstyle.btn}>
Discard Changes
</button>
</div>
</form>
</div>
);
}
Try mobx or zustand it may suit your need better
I am new to programming and started learning React a few weeks ago. I am trying to create a weather app. I created a file called Weather.js where I the fetch api data that will be displayed. One of the inputs for the api link is lat/log. I decided to create another file called Button.js, where a user will enter their lat/long and submit it. Once submitted, that lat/long will get placed on the api link (in Weather.js), to fetch that person's weather forecast.
I am able to console.log the button data in Button.js.
How do I pass that data to Weather.js?
I think I'm supposed to use props and/or a callback function, but I am at a loss on how to do it properly. Nothing has worked so far.
Thank you for your help.
function Weather() {
const [loading, setLoading] = React.useState(false)
const [maxTemp, setMaxTemp] = React.useState([])
React.useEffect(() => {
setLoading(true)
fetch("https://api.openweathermap.org/data/2.5/onecall?lat=34.1030&lon=-118.4105&units=imperial&exclude=current,minutely,hourly,alerts&appid={api}")
.then(res => res.json())
.then((data) => {
setLoading(false)
setMaxTemp(data.daily[0].temp.max)
})
}, [])
if(loading === true){
return <div>Loading...</div>
} else return(
<div>
High: {Math.round(maxTemp)} <br />
</div>
)
}
ReactDOM.render(<Weather />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
function Button(props) {
const [lat, setLat] = React.useState([])
const handleSubmit = (event) => {
console.log(lat)
event.preventDefault();
}
return(
<form onSubmit={handleSubmit}>
<input type="text" value={lat} onChange={e => setLat(e.target.value)} />
<input type="submit" value="Submit" />
</form>
)
}
ReactDOM.render(<Button />, document.getElementById("root"));
<div id="root"></div><script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
so first you have to import the Button into your Weather file, then you have to pass a (setState) function into that button from the parent (weather.js) and then call that inside the button with passing the data:
and you don't have to pass the Button.js into React.DOM, we do that only 1 time for the most parent component.
Weather.js:
import Button from './button' // pass your correct paths
function Weather() {
const [loading, setLoading] = React.useState(false)
const [maxTemp, setMaxTemp] = React.useState([])
const [coords, setCoords] = React.useState(null)
React.useEffect(() => {
if(!coords) return. // checking if no coords to skip calling API
setLoading(true)
fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${coords.lat}&lon=${coords.long}&units=imperial&exclude=current,minutely,hourly,alerts&appid=${api}`)
.then(res => res.json())
.then((data) => {
setLoading(false)
setMaxTemp(data.daily[0].temp.max)
})
}, [coords]) // watching for coords change from button passed data
if(loading === true){
return <div>Loading...</div>
} else return(
<div>
High: {Math.round(maxTemp)} <br />
<Button setCoords={setCoords} /> // here we pass the setState function into the button to get the data back once its called from inside
</div>
)
}
ReactDOM.render(<Weather />, document.getElementById("root"));
Button.js:
function Button(props) {
const [lat, setLat] = useState("");
const [long, setLong] = useState("");
const handleSubmit = (event) => {
console.log(lat)
event.preventDefault();
props.setCoords({ lat, long }); // here we call the function that is passed from parent and give it the data
}
return(
<form onSubmit={handleSubmit}>
<input
placeholder="Lat"
type="number"
value={lat}
onChange={(e) => setLat(e.target.value)}
/>
<input
placeholder="Long"
type="number"
value={long}
onChange={(e) => setLong(e.target.value)}
/>
<input type="submit" value="Submit" />
</form>
)
}
You don't need to use two components. Simply add more state to your weather component.
You also don't need to use a form. Just create two input fields and a button.
Extract your effect code into a separate function. This way you can fire up API call on page load and on button click.
function Weather() {
const [loading, setLoading] = React.useState(false);
const [maxTemp, setMaxTemp] = React.useState([]);
const [lat, setLat] = React.useState(34.103);
const [lo, setLo] = React.useState(-118.4105);
const apiCall = () => {
setLoading(true);
fetch(
`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lo}&units=imperial&exclude=current,minutely,hourly,alerts&appid={api}`
)
.then((res) => res.json())
.then((data) => {
setLoading(false);
setMaxTemp(data.daily[0].temp.max);
});
};
React.useEffect(() => {
apiCall();
}, []);
return (
<React.Fragment>
<input
placeholder="Latitude"
type="range"
step="0.5"
max="90"
min="-90"
value={lat}
onChange={(e) => setLat(e.target.value)}
/>
<span>{lat}</span>
<br/>
<input
placeholder="Longitude"
type="range"
step="0.5"
max="180"
min="-180"
value={lo}
onChange={(e) => setLo(e.target.value)}
/>
<span>{lo}</span>
<br/>
<button onClick={apiCall}>Submit</button>
{loading ? (
<div>Loading...</div>
) : (
<div>
High: {Math.round(maxTemp)} <br />
</div>
)}
</React.Fragment>
);
}
ReactDOM.render(<Weather />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
I tried to put a form in a separate reusable component but when used that way I can't type anything into the input. I observed, that after entering one letter (it does not appear in the input box) it seems that React rerender the whole component and the name is updated with the inserted letter.
in the version 2 the same code works correctly.
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = key => event => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = event => {
event.preventDefault();
handleChange();
};
// VERSION 1. doesn't work
const FormEdit = () => (
<form>
<div className="form-group">
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
return (
<Layout>
<div>
{name} //<-it shows only one letter
<FormEdit />
</div>
</Layout>
);
// VERSION 2 -> works properly
return (
<Layout>
<div>
{name} //<-the updated name is shown immediately
<form>
<div className="form-group">
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
</div>
</Layout>
);
};
export default User;
The issue is directly related to declaring the FormEdit component within the other component. Here's why:
In a functional component, everything declared inside gets destroyed and re-created each render. It's no different than a normal function call. This is what makes React's hooks so special. They keep track of values in between renders and make sure they are re-created with the correct values.
You're declaring the FormEdit component inside a function, which means not only is it re-declared every render, but as a side-effect it also un-mounts and remounts each render as well.
This has a few different effects:
The component's input loses focus every render.
It's impossible for it to maintain its own state.
It's not very performant.
Below is a working example to demonstrate.
const {useState, useEffect} = React;
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
const FormEdit = () => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
}
return (
<div>
{name}
<FormEdit />
</div>
);
}
ReactDOM.render(<Example />, 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>
As for why you only see the first character; You are not giving the input a value, only an onChange. If the component does not unmount, this just makes it an "uncontrolled" component. The input still gets it's value updated, you just can't programatically control it. But, since it is unmounting and re-mounting every render, it loses its last value every time the user types.
Making it a controlled input would fix this:
const {useState, useEffect} = React;
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
const FormEdit = () => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input value={name} onChange={handleChange("name")} type="text"/>
// ^ Add this
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
}
return (
<div>
{name}
<FormEdit />
</div>
);
}
ReactDOM.render(<Example />, 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>
This is a little better, but still not ideal. Now it keeps the value each update, but it still loses focus. Not a very good user experience.
This final solution is to never declare a component within another component.
const {useState, useEffect} = React;
const FormEdit = (props) => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input value={props.name} onChange={props.handleChange("name")} type="text"/>
</div>
<button onClick={props.submitEdit}> Submit </button>
</form>
)
}
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
return (
<div>
{name}
<FormEdit name={name} handleChange={handleChange} submitEdit={submitEdit} />
</div>
);
}
ReactDOM.render(<Example />, 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>
Now it only mounts once, keeps focus, and updates as expected.
You would have to pass your form handlers to the child component as props so that the lifted state can be manipulated from the child.
// Parent Component
...
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = key => event => {
...
};
const submitEdit = event => {
...
};
return (
<Layout>
<div>
{name}
<FormEdit handleChange={handleChange} submitEdit={submitEdit}/>
</div>
</Layout>
);
and then in the child:
// Child Component
const FormEdit = (props) => (
<form>
<div className="form-group">
<input onChange={props.handleChange("name")} type="text"/>
</div>
<button onClick={props.submitEdit}> Submit </button>
</form>
)
Your FormEdit component which is inside the App component is causing the entire App component to re-render when the state gets updated onChange and hence you can only enter only one character at a time. It is generally not a great idea to declare a component within a component. Refer this link for more info. All you have to do is pull the FormEdit component out of the App component in its own separate function and pass the change handlers as props to the FormEdit component. Have a look at the working code below.
import React, { useState } from 'react';
const FormEdit = ({ handleChange, submitEdit, name }) => {
return (
<form>
<div className='form-group'>
<input onChange={handleChange('name')} type='text' value={name || ''} />
</div>
<button onClick={submitEdit} type='submit'>
Submit
</button>
</form>
);
};
export default function App() {
const [userdata, setUser] = useState();
const { name } = userdata || {};
const handleChange = key => event => {
setUser(prevState => {
return { ...prevState, [key]: event.target.value };
});
event.persist();
event.preventDefault();
};
const submitEdit = event => {
event.preventDefault();
handleChange();
};
return (
<div>
<div>
{name || ''}
<FormEdit
handleChange={handleChange}
submitEdit={submitEdit}
name={name}
/>
</div>
</div>
);
}