I'm trying to submit a form that sends a http request onSubmit, but I'm getting undefined when setting the state on the values. I'm not sure why the values are not being passed upon click and being set with the set...() function.
Below is the code of that component. Upon first submit action I get an error because the "surveyUserAnswers" are undefined, but in the next submissions it works. Not sure why? Could someone advise.
I'm very new to typescript and react hooks, so excuse my code! thanks
import React, { useRef, useState } from "react";
import Loader from "../UI/loader/loader";
import axios from "axios";
import "./surveybox.css";
interface surveryAnswer {
id: number;
answers: string[];
}
const SurveyBox: React.FC = () => {
const [surveyUserAnswers, setSurveyUserAnswers] = useState<surveryAnswer>();
const [loading, setLoading] = useState(false);
const programmingQRef = useRef<HTMLSelectElement>(null);
const skillsQRef = useRef<HTMLSelectElement>(null);
const stateManagementQRef = useRef<HTMLSelectElement>(null);
const programmerTypeQRef = useRef<HTMLSelectElement>(null);
const onSubmitSurvey = (e: React.FormEvent): void => {
e.preventDefault();
setLoading((prevLoading) => !prevLoading);
setSurveyUserAnswers({
id: Math.random(),
answers: [
programmerTypeQRef.current!.value,
skillsQRef.current!.value,
stateManagementQRef.current!.value,
programmerTypeQRef.current!.value,
],
});
axios
.post(`${DB_URL}/users-answers.json`, surveyUserAnswers)
.then((res) => {
setLoading((prevLoading) => !prevLoading);
})
.catch((error) => {
console.log(error);
setLoading((prevLoading) => !prevLoading);
});
};
return (
<div className="surveybox-container">
{loading ? (
<div className={"loader-holder"}>
<Loader />
</div>
) : (
<React.Fragment>
<h2>Quick survey!</h2>
<form action="submit" onSubmit={onSubmitSurvey}>
<label>favorite programming framework?</label>
<select ref={programmingQRef} name="programming">
<option value="React">React</option>
<option value="Vue">Vue</option>
<option value="Angular">Angular</option>
<option value="None of the above">None of the above</option>
</select>
<br></br>
<label>what a junior developer should have?</label>
<select ref={skillsQRef} name="skills">
<option value="Eagerness to lear">Eagerness to learn</option>
<option value="CS Degree">CS Degree</option>
<option value="Commercial experience">
Commercial experience
</option>
<option value="Portfolio">Portfolio</option>
</select>
<br></br>
<label>Redux or Context Api?</label>
<select ref={stateManagementQRef} name="state-management">
<option value="Redux">Redux</option>
<option value="Context Api">Context Api</option>
</select>
<br></br>
<label>Backend, Frontend, Mobile?</label>
<select ref={programmerTypeQRef} name="profession">
<option value="Back-end">back-end</option>
<option value="Front-end">front-end</option>
<option value="mobile">mobile</option>
</select>
<br></br>
<button type="submit">submit</button>
</form>
</React.Fragment>
)}
</div>
);
};
export default SurveyBox;
Setting the state is an async action, and the updated state would only be available at the next render.
In your case, the default state is undefined, and this is what you send at the 1st submit. The state is now updated, and when you submit again, you send the previous answer, and so on...
To solve this, prepare a const (newAnswer), and set it to the state, and use it in the api call.
Note: in your case, you're not using the surveyUserAnswers at all, so you can remove this state entirely.
const onSubmitSurvey = (e: React.FormEvent): void => {
e.preventDefault();
setLoading((prevLoading) => !prevLoading);
const newAnswer = {
id: Math.random(),
answers: [
programmerTypeQRef.current!.value,
skillsQRef.current!.value,
stateManagementQRef.current!.value,
programmerTypeQRef.current!.value,
],
}
setSurveyUserAnswers(newAnswer);
axios
.post(`${DB_URL}/users-answers.json`, newAnswer)
.then((res) => {
setLoading((prevLoading) => !prevLoading);
})
.catch((error) => {
console.log(error);
setLoading((prevLoading) => !prevLoading);
});
};
Related
The below is my code.
What i am doing is a currency converter app by fetching external API. It is guaranteed process.env.REACT_APP_DATA_SOURCE is always correct.
The problem here is: If I change the currency, either inputCurrency or outputCurrency, I need to click "Submit" button twice instead of once to display a correct answer. Why is it the case?
I can't come up with any better wording so I used "delay" in the heading.
Thank you!
import './App.css';
import { useState, useEffect } from 'react';
import { Select, Button, ButtonGroup } from '#chakra-ui/react';
function App() {
const [exchangeRate, setExchangeRate] = useState(7.8491);
const [inputCurrency, setInputCurrency] = useState('USD');
const [inputAmount, setInputAmount] = useState(1);
const [outputCurrency, setOutputCurrency] = useState('HKD');
const [outputAmount, setOutputAmount] = useState(7.8491);
useEffect(() => {
const fetchData = async (url) => {
await fetch(url)
.then((response) => response.json())
.then((data) => setExchangeRate(data.conversion_rates[outputCurrency]));
};
try {
let url = `${process.env.REACT_APP_DATA_SOURCE}${inputCurrency}`;
fetchData(url);
} catch (err) {
console.log(err);
}
}, [inputCurrency, outputCurrency]);
const handleSubmit = async (e) => {
e.preventDefault();
setInputCurrency(e.target.inputCurrency.value);
setInputAmount(e.target.inputAmount.value);
setOutputCurrency(e.target.outputCurrency.value);
let result = inputAmount * exchangeRate;
setOutputAmount(result);
};
return (
<div className="main-body">
<form onSubmit={handleSubmit}>
<h3>Set input amount:</h3>
<input className="inputAmount" name="inputAmount" type="number" />
<h3>Set input currency:</h3>
<Select name="inputCurrency">
<option value="USD">USD</option>
<option value="HKD">HKD</option>
<option value="CNY">CNY</option>
<option value="GBP">GBP</option>
<option value="CAD">CAD</option>
</Select>
<h3>Set output currency:</h3>
<Select name="outputCurrency">
<option value="USD">USD</option>
<option value="HKD">HKD</option>
<option value="CNY">CNY</option>
<option value="GBP">GBP</option>
<option value="CAD">CAD</option>
</Select>
<Button colorScheme="blue" type="submit">
Submit
</Button>
</form>
<h3>
{inputAmount} {inputCurrency} is equal to:
</h3>
<h3>
{outputAmount} {outputCurrency}
</h3>
</div>
);
}
export default App;
You are calculating the output amount at the wrong place. First, you update the inputCurrency and outputCurrency in handleSubmit. This means the useEffect will be 'triggered' and the new exchange rate will be fetched. But that's not gonna happen immediately. Definitely not before result is calculated.
const handleSubmit = async (e) => {
e.preventDefault();
setInputCurrency(e.target.inputCurrency.value);
setInputAmount(e.target.inputAmount.value);
setOutputCurrency(e.target.outputCurrency.value);
//This will ALWAYS use the old previous exchange rate.
let result = inputAmount * exchangeRate;
setOutputAmount(result);
};
What you want is to recalculate outputAmount whenever the exchangeRate or inputAmount changes. You can use another useEffect like:
useEffect(() => {
//Take these out from handleSubmit.
let result = inputAmount * exchangeRate;
setOutputAmount(result);
}, [inputAmount, exchangeRate])
From "orders" component, sending a order id to "update" component. Then trying to update "the status" of the order containing the id.
Logging the id value in the console works, but not setting a state with it.
"Update" component:
const UpdateStatus = (props) => {
const location = useLocation();
const [orderId, setOrderId] = useState(null);
const [status, setStatus] = useState("pending");
useEffect(() => {
setOrderId(location.state.id); // errors here
console.log(location.state.id) // but gives a valid id
}, [location]);
const handleChange = e => {
setStatus(e.target.value);
console.log(e.target.value)
}
const history = useHistory();
const handleClick = () => {
if (orderId){
axios.patch(`http://localhost:5000/orders/change-status/${orderId}`, {status: status}, {withCredentials: true})
.then((res) => {
console.log(res);
history.push("/get-orders");
})
}
}
return (
<div>
<h2> Update Order Status </h2>
<form>
<label>Change status to: </label>
<select name="status" id="order-status" onChange={handleChange}>
<option value="pending">Pending</option>
<option value="accepted">Accepted</option>
<option value="delivered">Delivered</option>
</select>
<br/><br/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
</div>
);
}
"Orders" component:
const handleClick = orderId => {
history.push({
pathname: '/update-status',
state: { id: orderId }
});
}
Try something like:
useEffect(() => {
if(location?.state?.id)
setOrderId(location.state.id);
}, [location?.state?.id]);
Try this:
useEffect(() => {
setOrderId(location.state?.id);
..........
}, [location]);
Hi Guys I'm new to React. I am trying to create a cascade drop down list using react hooks and the way I did it works well but I feel something wrong in the way I did it. Please check this code and tell me there is a way that I can improve my code.Thanks in advance
import React, { useState } from 'react';
import './App.css';
function App() {
const[data, setName] = useState({
countrie:"",
state:""
});
let state;
const countrie =['Germany','India','France']
const gstate = ['Duesseldorf', 'Leinfelden-Echterdingen', 'Eschborn']
const istate = ['Delhi', 'Kolkata', 'Mumbai', 'Bangalore']
const fstate =['Auvergne','Bretagne','Corse','Centre']
if(data.countrie==="Germany"){
state = gstate.map((gstate,key)=> <option key={key} value={gstate}>{gstate}</option>)
}else if(data.countrie==="India"){
state = istate.map((istate,key)=> <option key={key} value={istate}>{istate}</option>)
}else{
state = fstate.map((fstate,key)=> <option key={key} value={fstate}>{fstate}</option>)
}
const countries = countrie.map((countrie,key)=> <option key={key} value={countrie}>{countrie}</option>)
function handleCountry(e){
setName({...data,countrie:e.target.value});
}
function handleStateChange(e){
setName({...data,state:e.target.value});
}
return (
<form onSubmit={handleSubmit}>
<div>
<select value={data.countrie} onChange={handleCountry}>
{countries}
</select>
</div>
<div>
<select value={data.state} onChange={handleStateChange}>
{state}
</select>
</div>
<input type="submit" />
</form>
);
}
export default App;
The best suggestion I have is to change the data structure which combines the country and states. Doing so makes it a lot easier to map over each country and getting the states without having to map variables. This makes it also more scalable.
Here is an example using the country data as a collection:
Codesandbox
import React, { useState } from "react";
const countriesData = [
{
name: "Germany",
states: ["Duesseldorf", "Leinfelden-Echterdingen", "Eschborn"]
},
{
name: "India",
states: ["Delhi", "Kolkata", "Mumbai", "Bangalore"]
},
{
name: "France",
states: ["Auvergne", "Bretagne", "Corse", "Centre"]
}
];
function Form() {
const [{ country, state }, setData] = useState({
country: "Germany",
state: ""
});
const countries = countriesData.map((country) => (
<option key={country.name} value={country.name}>
{country.name}
</option>
));
const states = countriesData.find(item => item.name === country)?.states.map((state) => (
<option key={state} value={state}>
{state}
</option>
));
function handleCountryChange(event) {
setData(data => ({ state: '', country: event.target.value }));
}
function handleStateChange(event) {
setData(data => ({ ...data, state: event.target.value }));
}
return (
<form onSubmit={() => console.log("Submitted")}>
<div>
<select value={country} onChange={handleCountryChange}>
{countries}
</select>
</div>
<div>
<select value={state} onChange={handleStateChange}>
{states}
</select>
</div>
<input type="submit" />
</form>
);
}
export default Form;
I've been working on this project for the last couple of hours and I was pretty sure that this final hour would be my last. No errors are appearing. My thinking is that when I pick a hero from the drop down, the page will update depending on my choice. I may have something that isn't firing that I'm not picking up on.
import React, {useEffect, useState} from 'react'
import axios from 'axios'
require("regenerator-runtime/runtime");
const App = () => {
const [hero, selectedHero] = useState(
'Select a Hero'
);
const handleChange = event => selectedHero(event.target.value);
return(
<HeroSelect heroSelect={hero} onChangeHeadline={handleChange} />
);
};
const HeroSelect = ({heroSelect, onChangeHeadline}) => {
const [data, setData] = useState({heroes: []});
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://api.opendota.com/api/heroStats',
);
setData({...data, heroes: result.data});
};
fetchData();
}, []);
return (
<div>
<h1>{heroSelect}</h1>
<select>
{data.heroes.map(item => (
<option key={item.id} value={heroSelect} onChange={onChangeHeadline} >
{item.localized_name}
</option>
))}
</select>
</div>
)
};
export default App
Define your onChange={onChangeHeadline} on Select tag not on option tag
<select onChange={onChangeHeadline}>
{data.heroes.map(item => (
<option key={item.id} value={item.localized_name}>
{item.localized_name}
</option>
))}
</select>
You should be firing your onChange event on the select tag itself.
<select onChange={onChangeHeadline} >
.....
.....
</select>
I reckon you didn't declare an onChange on the select.
Using This method:
<select id="lang" onChange={this.change} value={this.state.value}>
<option value="select">Select</option>
<option value="Java">Java</option>
<option value="C++">C++</option>
</select>
I'm trying to update params then using those values in a search function. In my code, the dispatch does not update the state.params as intended.
import React, { useReducer } from 'react'
const SearchForm = () => {
let searchParams = {}
const initialState = {
params: {}
}
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE':
return {
...state,
params: action.searchParams
}
default:
return state
}
}
const [state, dispatch] = useReducer(reducer, initialState)
const handleChange = (e) => {
const { name, value } = e.target
searchParams = { ...searchParams, [name]: value }
}
const handleClick = () => {
searchParams = { ...searchParams }
console.log('SearchParams: ', searchParams)
dispatch({ type: 'UPDATE', searchParams })
// state.params returns nothing here
console.log('Handle Click Dispatch: ', state.params)
}
return (
<div>
<select name="gender" onChange={handleChange}>
<option value="">-- Choose --</option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select><br />
<select name="ethnicity" onChange={handleChange}>
<option value="">-- Choose --</option>
<option value="African-American">African American</option>
<option value="Caucasian">Caucasian</option>
<option value="Hispanic">Hispanic</option>
</select><br />
<button type="submit" onClick={handleClick}>Submit</button>
</div>
)
}
export default SearchForm
handleChange should update the searchParams variable then dispatch in the onClick method making it available for the rest of the app.
What am I missing?
As stated in the comment; you cannot get the state after a dispatch because dispatch is asynchronous and doesn't return anything.
You could add a useEffect and do something there when state.params changed, set initial value of state.params to NONE so you can skip the first time state.params changed:
import React, {
useReducer,
useState,
useEffect,
useCallback,
} from 'react';
const NONE = {};
const initialState = {
params: NONE,
};
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE':
console.log('in reducer:', action.params);
return {
...state,
params: action.params,
};
default:
return state;
}
};
const SearchForm = () => {
const [searchParams, setSearchParams] = useState({});
const [state, dispatch] = useReducer(
reducer,
initialState
);
const handleChange = useCallback(
e => {
const { name, value } = e.target;
setSearchParams({ ...searchParams, [name]: value });
},
[searchParams]
);
const handleClick = useCallback(() => {
console.log('SearchParams: ', searchParams);
dispatch({ type: 'UPDATE', params: searchParams });
}, [searchParams]);
const { params: stateParams } = state;
useEffect(() => {
if (stateParams !== NONE) {
console.log('params in state changed: ', stateParams);
}
}, [stateParams]);
return (
<div>
<select name="gender" onChange={handleChange}>
<option value="">-- Choose --</option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
<br />
<select name="ethnicity" onChange={handleChange}>
<option value="">-- Choose --</option>
<option value="African-American">
African American
</option>
<option value="Caucasian">Caucasian</option>
<option value="Hispanic">Hispanic</option>
</select>
<br />
<button type="submit" onClick={handleClick}>
Submit
</button>
</div>
);
};
export default SearchForm;
Try to use action.payload.searchParams instead of action.searchParams