I have a problem with use state component of property where the state does not change when loading component. The state of the item should change since i am updating it after receiving a response in the previous form stepper but when i add a new dynamic input field, it changes the state but not for the first one
Here is the code that is bringing the error
export default function AddProp() {
const [propertyID, setPropertyID] = useState(0);
const [step,setStep] = useState(1);
const [values,setValues] = useState({
responseMessage: "",
loading: false,
name: "",
address: "",
county: "",
city: "",
zipcode: "",
type: "",
specifictype: "",})
const [formValues, setFormValues] = useState([
{ number_of_units: "", market_rent: "", square_feet: "", beds: "", property:propertyID.property },
])
let addFormFields = () => {
setFormValues([
...formValues,
{ number_of_units: "", market_rent: "", square_feet: "", beds: "" ,property:propertyID.property},
]);
};
let sendProperties = async () => {
const response = await axios
.post(
"http://127.0.0.1:8000/property/api/v1/addProperty/",
{
property_name: values.name,
address: values.address,
county: values.county,
city: values.city,
zipcode: values.zipcode,
property_type: values.type,
},
{ headers: headers }
);
setPropertyID(response.data);
if(response.status == 200){
setStep(step + 1 );
}else{
alert("An error occurred");
}
};
switch (step) {
case 3:
return (
<PropertyType
values={formValues}
handleChange={handleFormChange}
add={addFormFields}
remove={removeFormFields}
prevStep={prevStep}
nextStep={getandSend}
/>
);
case 4:
return <Success message={"DONE"} />;
default:
}
}
Instead of using the current formValues, pass a callback to setFormValues that takes in one parameter, say currentFormValues, and use that to update the state instead.
const addFormFields = () => {
setFormValues(currentFormValues => [
...currentFormValues,
{ number_of_units: "", market_rent: "", square_feet: "", beds: "" ,property:propertyID.property},
]);
};
This problem is related to stale state, a problem that occurs whenever we're trying to update state, often within a closure.
Related
I have this form in my code, I want to, after checking all validation and patterns, the form button to become enabled, but I don't know how I can put this in my form, or do I need to write any other functions and which way is most clean Code?
const [disable, setDisable] = React.useState(true);
const [staff, setStaff] = React.useState({
username: "",
email: "",
phone: "",
password: "",
});
const [errMessage, setErrMessage] = React.useState({
username: "",
email: "",
phone: "",
password: "",
});
const handleChange = (e) => {
switch (e.target.name) {
case "email": {
if (e.target.value.toLowerCase().match(emailValidation)) {
setErrMessage({ ...errMessage, email: "" });
setStaff({ ...staff, email: e.target.value });
} else {
setErrMessage({
...errMessage,
email: "It should be a valid email address",
});
}
}
case "password": {
if (e.target.value.length >= 12) {
setErrMessage({ ...errMessage, password: "" });
setStaff({ ...staff, password: e.target.value });
} else {
setErrMessage({
...errMessage,
password: "It should be at least 12 character",
});
}
}
default:
setStaff({
...staff,
[e.target.name]: e.target.value,
});
}
};
return( <button disabled={disable}>Submit</button>)
Since you are tracking the errors in that errMessage state, you don't need an additional state for disable. It could be a simple constant you can add above and outside handleChange:
const disable =
Object.values(errMessage).filter((v) => v).length > 0 ||
Object.values(staff).filter((v) => v).length !== 4;
<button disabled={disable}>Submit</button>
This way, the button is disabled when there is an error message, or when one field is empty.
So i've been working on this for awhile what i'm trying to do is change a checked value on checkbox click.
My initial state looks like this:
const [todoList, setTodoList] = useState({
foundation: {
steps: [
{ key: "1", title: "setup virtual office", isDone: false },
{ key: "2", title: "set mission and vision", isDone: false },
{ key: "3", title: "select business name", isDone: false },
{ key: "4", title: "buy domain", isDone: false },
],
},
discovery: {
steps: [
{ key: "1", title: "create roadmap", isDone: false },
{ key: "2", title: "competitor analysis", isDone: false },
],
}
});
and my map and onClick function (updateCheckFoundation works when click the checkbox)
{todoList.foundation.steps.map((item) => {
return (
<div>
<input type="checkbox" defaultChecked={item.isDone}
onClick={(event)=> updateCheckFoundation({
isDone:event.target.checked,
key:item.key
})}/>
<span>{item.title}</span>
</div>
);
})}
so how can ı update todoList use setState?
my code (updateCheckFoundation func.) like this and is not working :( :
const updateCheckFoundation = ({isDone, key}) => {
const updateTodoList = todoList.foundation.steps.map((todo)=> {
if(todo.key === key){
return {
...todo,
isDone
};
}
return todo;
});
setTodoList(updateTodoList);
}
Issue
Your updateCheckFoundation callback isn't maintaining the state invariant, and is in fact, dropping all but the foundation.steps array of state.
const updateCheckFoundation = ({isDone, key}) => {
const updateTodoList = todoList.foundation.steps.map((todo)=> {
if(todo.key === key){
return {
...todo,
isDone
};
}
return todo;
});
setTodoList(updateTodoList); // <-- only the state.foundation.steps array!!
}
Solution
In function components, when using the useState state updater functions you need to handle merging state (the root state), and nested state, yourself, manually.
const updateCheckFoundation = ({ isDone, key }) => {
setTodoList(state => ({
...state, // <-- shallow copy state object
foundation: {
...state.foundation, // <-- shallow copy
steps: state.foundation.steps.map(todo => todo.key === key
? { ...todo, isDone }
: todo)
},
}));
}
I am trying to add a "sorting" system to a clothing website I am building. The issue I am having is that whenever a new parameter is being added, it removes the old one added. I would guess the reason is that the variable holding the parameters are being re-rendered whenever you sort the products.
Here is my code:
const FetchAPI = (props) => {
const [product, setProducts] = useState([]);
// Key and Value
let facetKey = props.facetKey;
let facetValue = props.facetValue;
let params = {
store: "US",
offset: props.offset,
categoryId: props.categoryId,
limit: props.limit,
country: "US",
sort: "freshness",
currency: "USD",
sizeSchema: "US",
lang: "en-US",
};
if (facetKey) {
params = { ...params, offset: 0, limit: 0, [facetKey]: facetValue };
}
useEffect(() => {
const options = {
method: "GET",
url: "https://asos2.p.rapidapi.com/products/v2/list",
params: params,
headers: {
"x-rapidapi-key": "",
"x-rapidapi-host": "",
},
};
axios
.request(options)
.then(function (response) {
setProducts(response.data.products);
props.items(response.data.itemCount);
props.facets(response.data.facets);
})
.catch(function (error) {
console.error(error);
});
}, [props.limit, facetValue]);
return (
<div>
<div className={classes.container}>
{product.map((product) => (
<ProductCard
key={product.id}
img={product.imageUrl}
name={product.name}
price={product.price.current.text}
/>
))}
</div>
</div>
);
};
The re-rendering of params occurs because it is inside of the const FetchAPI, but I am not that sure how I can "ignore" that and make the params keep the first value. Perhaps could I solve this by putting the values in localstorage? Or is there a better way?
In my React app, I'm getting this error during onChange event with my email input field:
Warning: A component is changing a controlled input of
type text to be uncontrolled. Input elements should not switch from
controlled to uncontrolled (or vice versa).
Here's the onChange block that's causing this warning; The error goes away if I remove the first if block but of course I need it there for email validation.
validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
handleOnChange = e => {
const { name, value } = e.target;
const emailInput = e.target.value;
const emailValid = this.validateEmail(emailInput);
if (name === 'email') {
this.setState({
inputs: {
email: emailInput,
},
errors: {
email: !emailValid,
},
});
} else {
this.setState({
inputs: {
...this.state.inputs,
[name]: value,
},
errors: {
...this.state.errors,
[name]: false,
},
});
}
};
State:
constructor() {
super();
this.state = {
inputs: {
name: '',
email: '',
message: '',
},
phone: '',
show: true,
errors: {
name: false,
email: false,
message: false,
},
};
}
How do I keep my current code and address the warning?
You need to spread the existing/previous state in the if-block. You likely have other input tags that were initially connected to the input-state object which looks like:
inputs: {
name: "",
email: "",
message: ""
}
<input value={this.state.input.name} name="name"/>
<input value={this.state.input.email} name="email"/>
<input value={this.state.input.message} name="message"/>
but when you used this.setState() in your posted code, the connection is lost. You are setting the inputs state to an object with a single property of email:
inputs: {
email: "valueFromEventTarget"
}
What you need to do is spread the existing state so you don't lose the other key/value pairs in the input object: Update your handleChange() function to this:
handleOnChange = e => {
const { name, value } = e.target;
const emailInput = e.target.value;
const emailValid = this.validateEmail(emailInput);
if (name === 'email') {
this.setState({
inputs: {
...this.state.inputs,
email: emailInput,
},
errors: {
...this.state.errors,
email: !emailValid,
},
});
} else {
this.setState({
inputs: {
...this.state.inputs,
[name]: value,
},
errors: {
...this.state.errors,
[name]: false,
},
});
}
};
My app has a feature where users can filter results based on "blood group" and "city", and areas. Results will be retrieved from DB using Axios for Vuejs through "URL" query strings. Example url is: http://example.com/api/results?blood=a+&city=london
It should work in a way that when a user select just blood group from select menu: the url would exclude the city parameter. But from my current code, I can't get it stripped of, as a result, the database query returns no results on the basis that cityreturns null value.
Here's what I have in my Vue component:
<script>
export default {
props: ['user'],
data() {
return {
auth_user: this.user,
results: {},
blood_groups: "",
cities: "",
districts: "",
areas: "",
donorUrl: "/api/donors",
requestedBlood: "",
requestedCity: "",
requestedDist: "",
requestedArea: "",
params: {}
};
},
created() {
this.fetchDonors();
this.fetchCities();
},
methods: {
fetchDonors() {
let url = "/api/donors";
axios.get(url).then(response => {
this.results = response.data.data;
this.blood_groups = [...new Set(response.data.data.map(x=> x.blood_group))];
});
},
fetchCities() {
let url = "/api/location_type/cities";
axios.get(url).then(response => {
this.cities = response.data.cities
})
},
selected_blood_group(event) {
this.requestedBlood = event.target.value;
this.get();
},
get_city(event) {
this.requestedCity = event.target.value;
this.get();
},
get() {
let request = {
params: {
blood: this.requestedBlood,
city: this.requestedCity,
dist: this.requestedDist,
area: this.requestedArea
}
}
axios.get('/api/donors', request).then(response => {
this.results = response.data.data
})
}
},
};
</script>
My query is how can I remove or check if any of the following properties contains empty value, so that I do not include them in axios params?
let request = {
params: {
blood: this.requestedBlood,
city: this.requestedCity,
dist: this.requestedDist,
area: this.requestedArea
}
}
You can try below code.
Create a new object(called testParams) and add that object in params.suppose requestedCity is selected(not only but any variable is selected ). Then you can do like below.
if(requestedCity.length!=0)
{
testParams["city"]=requestedCity; // OTHERWISE DON'T ADD IN testParams object
}
Finally while making request through axios add testParams in params object like below.
axios.get('/yourUrl/',{
params:{
testParams //here vue will automatically sets 'testParams':testParams
}
})
I got it working with the following approach:
let request = {
blood: this.requestedBlood,
city: this.requestedCity,
dist: this.requestedDist,
area: this.requestedArea
}
for(let k in request)
if(!request[k]) delete request[k];
axios.get('/api/donors', {
params: request
}).then(response => {
this.results = response.data.data
})