The Select Dropdown doesn't update back after reload - reactjs

I created a form where the user inputs data and when the button Save is clicked the data are saved to a database. Everything works fine. You can notice that the country is selected from a memo list and I used a function to save the data rather than a lambda function like the name and the address because I couldn't figure it out. My problem is when I reload the page the country doesn't get back to the form like the name and the address but is stored in the database. The issue is in the code below.
import React, {useState, useEffect , useMemo} from 'react';
import { Button, Row, Col, Form, InputGroup} from 'react-bootstrap';
import obtainFromServer from '../Services/fetchService';
import { useLocalState } from '../util/useLocalStorage';
import countryList from 'react-select-country-list';
import Select from 'react-select';
const TrademarkView = () => {
const [jwt, setJwt] = useLocalState("", "jwt"); //Remove setJwt if we don't use it at the end
const trademarkId = window.location.href.split("/trademarks/")[1];
const [trademark, setTrademark] = useState({
address: "",
country: "",
name: "",
});
//Countries
const [countryinit, setCountryinit] = useState({
value: "",
label: "",
});
const options = useMemo(() => countryList().getData(), []);
const changeHandler = countryinit => {
setCountryinit(countryinit);
console.log(countryinit);
updateTrademark("country",countryinit.label)
}
const styleCountries = {
display: "inline-block",
width: "300px"
};
function updateTrademark(prop, value){
const newTrademark = {...trademark}
newTrademark[prop] = value;
setTrademark(newTrademark);
}
function saveTrademark(){
obtainFromServer(`/api/trademarks/${trademarkId}`, "PUT", jwt, trademark).then(
(trademarkData) =>{
setTrademark(trademarkData);
});
}
useEffect(() => {
obtainFromServer(`/api/trademarks/${trademarkId}`, "GET", jwt)
.then(trademarkData => {
if(trademarkData.address === null) trademarkData.address = "";
if(trademarkData.name === null) trademarkData.name = "";
setTrademark(trademarkData);
});
},[])
return (
<div>
{trademark ? (
<>
<Row>
<Col>
<Form className='m-5'>
<Form.Group controlId="formGridName" className="mb-2">
<Form.Label>Name:</Form.Label>
<Form.Control value={trademark.name} onChange={(e) => updateTrademark("name",e.target.value)}/>
</Form.Group>
<Form.Group className="mb-3" controlId="formGridAddress">
<Form.Label>Address:</Form.Label>
<Form.Control as="textarea" placeholder="1234 Main St" rows={3} value={trademark.address} onChange={(e) => updateTrademark("address",e.target.value)}/>
</Form.Group>
<Form.Group className="mb-3" controlId="formGridCountry">
<Form.Label>Country:</Form.Label>
<div style={styleCountries}><Select options={options} value={countryinit} onChange={changeHandler} /></div>
</Form.Group>
</Form>
</Col>
<Row className='mx-1 px-5'>
<Button onClick={() => saveTrademark()}>Save</Button>
</Row>
</Row>
</>
) : (
<></>
)}
</div>
);
};
export default TrademarkView;
The countryinit has a value(it's the initials of the country) and a label(it's the name of the country). You can see that when I am updating the database I am only sending the label. I have a console.log so you can see the country object.
How can I get the country to be selected after a reload?

I finally figured it out. Changed the select to this and it worked.
<Select options={options} value={options.filter((option) => option.value === trademark.country)} onChange={(e) => updateTrademark("country",e.value)} />
Basically the problem as I mention before is that the country it was an object. And I couldn't send the whole object to the database and I was sending the label which it was wrong. Now I am sending the value to the database and when reading I am redirecting the value from the database to the selected value and setting the label.

Related

Can't type in react input field

I have a simple form with an input field that I can't type on. I first thought the problem was with the onChange or the value props that were setting the input to readonly, but the fact is that I cant type with the browser suggestions and the state updates perfectly (See gif here) it's just that I won't let me type with the keyboard, even after reloading the page.
I also have a Login page that works perfectly except when I log out and redirect back to that page, it won't work until I reload the page, now it will work.
<input
value={name}
onChange={handleChange}
name="name"
/>
const [name, setName] = useState("");
const handleChange = (e:any) => {
setName(e.target.value);
}
Weird thing is that it's in like a readonly state but when I use browser suggestions it works and updates the state.
Here is the whole component:
import React, { useEffect, useState } from 'react';
import { useForm } from '../../utils/useForm';
import { CubeType } from '../../interfaces';
//import useStore from '../store/Store';
import { Modal, Button, Row, Col, FormGroup, FormLabel, FormControl } from 'react-bootstrap';
type Props = {
show: Boolean,
onClose: () => void,
cubeTypes: CubeType[]
};
const ModalTimelist = (props: Props) => {
//const store = useStore();
const [values, handleChangee] = useForm({ cubeType: 1, name: '' });
const [name, setName] = useState("");
const handleChange = (e:any) => {
setName(e.target.value);
}
useEffect(() => {
const modal = document.getElementsByClassName('modal')[0];
if(modal) modal.removeAttribute('tabindex');
}, [props.show]);
return (
<>
<Modal show={props.show} onHide={ props.onClose }>
<Modal.Header>
<Modal.Title>Timelist { name }</Modal.Title>
</Modal.Header>
<Modal.Body>
<Row>
<Col md="3">
<FormGroup>
<FormLabel>Cube Type</FormLabel>
<select
value={values.cubeType}
onChange={ handleChangee }
className="form-select"
name="cubeType"
>
{props.cubeTypes.map((it, idx) => {
return (<option value={ idx } key={"cube"+idx}>{it.name}</option>);
}) }
</select>
</FormGroup>
</Col>
<Col md="9">
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
value={name}
onChange={handleChange}
name="name"
/>
</FormGroup>
</Col>
</Row>
</Modal.Body>
<Modal.Footer>
<Button variant="success" onClick={() => props.onClose()}>
Save
</Button>
<Button variant="outline-danger" onClick={() => props.onClose()}>
Cancel
</Button>
</Modal.Footer>
</Modal>
</>
);
}
export default ModalTimelist;
value of input must be the state value otherwise it will not change use this code
const App = () => {
const [name,setName] = useState("")
const handle = ({target:{value}}) => setName(value)
return <input
value={name}
onChange={handle}
name="name"
/>
}
Use a debounce for setting name on state.
Example:
const handleChange = (e:any) => {
debounce(() => { setName(e.target.value) }, 300);
}
I tried the code and it works fine I think you should change the browser
and if you want
change this
const ModalTimelist = (props: Props) => {
with
const ModalTimelist:React.FC<Props> = (props) => {
Names specified by you in input field attributes must be same as useState names. Otherwise this problem occurs.
Example:
<input type={"text"} className="form-control" placeholder='Enter your User Name' name="username" value={username} onChange={(e)=>onInputChange(e)}/>
In name="username" , username spell must be same as the spell you used in State.

useEffect inside customHook not happens sometimes (from unknown reason). Using useRef

I have this custom hook which supposed to make debounce email validation.
Suddenly I notice that it's not happening in all types.
Sometimes in the first types it's happen sometimes not.
See the log of "useEffect" which won't happen (for me - in each case) with any type. And when it's happening it's taking the previous value.
the Custom hook:
export function useDebounceEmailValidation(value, delay) {
console.log("useDebounceEmailValidation ? ")
// State and setters for debounced value
const [valid, setValid] = useState(true);
const initRun = useRef(true);
console.log("init run = " , initRun.current)
useEffect(
() => {
console.log("useEffect?"); //---------------------------> this not happening on each render
//we don't want to do it on initial running
if(initRun.current){
initRun.current = false;
}
else{
// Update debounced value after delay
const handler = setTimeout(() => {
console.log("validating mail - " ,value);
setValid(validateEmail(value));
// setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
}
},
[value, delay] // Only re-call effect if value or delay changes
);
return valid;
}
the form component:
import React, {useLayoutEffect, useRef, useState} from 'react';
import Button from 'react-bootstrap/Button';
import {useDebounceEmailValidation} from "./utils-hooks";
import {Alert, Col, Form} from "react-bootstrap";
export function SubscribersForm() {
const [details, setDetails] = useState({
firstName: "",
lastName: "",
email: "",
tel: ""
});
const [popupMsg, setPopupMsg] = useState("default");
const [showMsg, setShowMsg] = useState(false);
const [isError, setIsError] = useState(false);
const emailChange = useRef(false);
const [validated, setValidated] = useState(false);
//For cases we need to access the email through local state and not by state
// (the value will persist between component re-rendering and the reference updating won't trigger a component re-rendering)
const emailRef = useRef("");
const validEmail = useDebounceEmailValidation(emailRef.current, 600);
// // general layout effect - will be happen on each component update - for DEBUG
// useLayoutEffect(() => {
// console.log("details = ", details);
// });
//happening after change in the popup message
useLayoutEffect(() => {
setTimeout(() => {
//resetting the msg
setPopupMsg("");
setShowMsg(
false);
}, 2000);
}, [popupMsg])
//happen after change in the details
useLayoutEffect(() => {
//handling email changing (validation)
if (emailChange.current) {
emailRef.current = details.email;
console.log("email.current = " , emailRef.current)
}
}, [details]);
const handleChange = (ev) => {
ev.persist();
if (ev.target.name === "email" ) {
emailChange.current = true;
} else {
emailChange.current = false;
}
setDetails(prevDetails => ({
...prevDetails,
[ev.target.name]: ev.target.value
}));
}
const onSubmit = (ev) => {
const form = ev.currentTarget;
//The default validation for the form
if (form.checkValidity() === false || !validEmail) {
ev.preventDefault();
ev.stopPropagation();
setValidated(true);
return;
}
ev.preventDefault();
alert("Those are the details - you can send it from here to the server !!! :) \n\n" +
"name = " + details.firstName + " " + details.lastName
+ "\nemail = " + details.email
+ "\nphone = " + details.tel);
//we set validation to false, because by default you don't want to show validation
setValidated(false);
setPopupMsg("Your details have been successfully saved");
setIsError(false);
setDetails({
firstName: "",
lastName: "",
email: "",
tel: ""
});
setShowMsg(true);
}
return (
<div className="subscribers-input">
<h3>Subscribers - Form</h3>
<Form className="needs-validation" noValidate validated={validated}
onSubmit={onSubmit}>{/*start of the form block */}
<Form.Row className="">{/*start of the form row of 12/12 columns*/}
<Col xs={12} className="">
<Form.Group controlId="firstName" className="">
<Form.Control
type="text"
placeholder="First name"
value={details.firstName}
onChange={handleChange}
name="firstName"
required
size="sm"
aria-label="first name"
/>
</Form.Group>
</Col>
</Form.Row>
<Form.Row className="">
<Col xs={12} className="">
<Form.Group controlId="lastName" className="">
<Form.Control
type="text"
placeholder="Last name"
value={details.lastName}
onChange={handleChange}
name="lastName"
required
size="sm"
aria-label="last name"
/>
</Form.Group>
</Col>
</Form.Row>
<Form.Row className="">
<Col xs={12} className="">
<Form.Group controlId="email" className="">
<Form.Control
type="email"
placeholder="Email"
value={details.email}
onChange={handleChange}
name="email"
required
size="sm"
aria-label="email"
isInvalid={!validEmail}
/>
<Form.Control.Feedback type="invalid">Email is Invalid</Form.Control.Feedback>
</Form.Group>
</Col>
</Form.Row>
<Form.Row className="">
<Col xs={12} className="">
<Form.Group controlId="tel" className="">
<Form.Control
type="tel"
placeholder="Phone"
value={details.tel}
onChange={handleChange}
name="tel"
required
size="sm"
aria-label="phone"
/>
</Form.Group>
</Col>
</Form.Row>
{showMsg &&
<Alert variant={isError ? 'danger' : 'success'}>{popupMsg}</Alert>
}
<Button type="submit" size="sm">Save</Button>
</Form>
</div>
)
}
See how the log not always happening.
there are two things to know: first, custom hooks only rerun when the component that we use our custom hook in (in our case, "SubscribersForm"), rerenders. second, the useEffect dependency array checks the equality of objects by references.
so to be sure that useEffect can intercept changes in its dependency array object we should pass new references for that object.
in the main component "SubscribersForm" you pass a reference to your "useDebounceEmailValidation" custom hook, so when you change the value of emailRef.current, no rerenders happen, and also the reference of the object is the same as before. you should use states in these cases

onChange event doesn't fire on first input

I'm new to React and I've got this registration (parent component) where it has an inventory (child component) and I'm changing the state of the inventory once the user inputs the quantity of the item that they hold so I can register them
But onChange seems to be delayed, I input 4 water bottles for example, and it console logs me the default value, only when I input the amount for another item like food it displays me the 4 water bottles and 0 food :(
This is what I'm working with...
Child component:
import React from "react";
import { Col, Row, FormGroup, Label, Input } from "reactstrap";
import waterIcon from "./../assets/water.png";
import soupIcon from "./../assets/food.png";
import medsIcon from "./../assets/aid.png";
import weaponIcon from "./../assets/gun.png";
function Inventory({ onSubmitInventory, currentInventory }) {
const onChangeWater = (e) => {
const newInventory = { ...currentInventory, water: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeSoup = (e) => {
const newInventory = { ...currentInventory, soup: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeMeds = (e) => {
const newInventory = { ...currentInventory, meds: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeWeapon = (e) => {
const newInventory = { ...currentInventory, weapon: e.target.value };
onSubmitInventory(newInventory);
};
return (
<FormGroup>
<Row className="justify-content-center text-center border-top pt-3">
<Col xs="3">
<img src={waterIcon} alt="Water" />
<Label for="inventoryWater">Fiji Water:</Label>
<Input
type="number"
name="inventoryWater"
id="inventoryWater"
placeholder="Enter amount..."
onChange={onChangeWater}
/>
</Col>
<Col xs="3">
<img src={soupIcon} alt="Soup" />
<Label for="inventorySoup">Campbell Soup:</Label>
<Input
type="number"
name="inventorySoup"
id="inventorySoup"
placeholder="Enter amount..."
onChange={onChangeSoup}
/>
</Col>
<Col xs="3">
<img src={medsIcon} alt="Aid" />
<Label for="inventoryMeds">First Aid Pouch:</Label>
<Input
type="number"
name="inventoryMeds"
id="inventoryMeds"
placeholder="Enter amount..."
onChange={onChangeMeds}
/>
</Col>
<Col xs="3">
<img
className="d-block"
style={{ margin: "0 auto" }}
src={weaponIcon}
alt="Gun"
/>
<Label for="inventoryWeapon">AK47:</Label>
<Input
type="number"
name="inventoryWeapon"
id="inventoryWeapon"
placeholder="Enter amount..."
onChange={onChangeWeapon}
/>
</Col>
</Row>
</FormGroup>
);
}
export default Inventory;
Parent component:
import React, { useState } from "react";
import { Col, Row, Button, Form, Label, Input } from "reactstrap";
import { useForm } from "react-hook-form";
import Inventory from "./Inventory";
import MapContainer from "./MapContainer";
function Register() {
const [lonlat, setLonlat] = useState("");
const onMarkerChange = (lonlat) => {
setLonlat(lonlat);
};
const [inventory, setInventory] = useState({
water: 0,
soup: 0,
meds: 0,
weapon: 0,
});
const onSubmitInventory = (newInventory) => {
setInventory(newInventory);
console.log(inventory);
};
const { register, handleSubmit, errors } = useForm();
const onSubmit = (data) => {
console.log(inventory);
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: data.personName }),
};
console.log(data);
};
return (
<Form
id="registerForm"
name="registerForm"
onSubmit={handleSubmit(onSubmit)}
>
<h4>New Person</h4>
<Row className="border-top pt-4">
<Col xs="8">
<Label for="personName">Your name:</Label>
<Input
className="gray-input"
type="text"
name="name"
id="personName"
placeholder="Enter your name here..."
innerRef={register}
/>
</Col>
<Col xs="2" className="text-center">
<Label for="personAge">Age:</Label>
<Input
className="gray-input"
type="number"
name="age"
id="personAge"
placeholder="Enter age..."
innerRef={register}
/>
</Col>
<Col xs="2" className="text-center">
<Label for="personGender">Gender:</Label>
<Input
type="select"
name="gender"
id="personGender"
innerRef={register}
>
<option defaultValue disabled>
-
</option>
<option>F</option>
<option>M</option>
</Input>
</Col>
</Row>
<Row>
<Col xs="12">
<Input
hidden
id="personLatLon"
name="personLatLon"
type="text"
defaultValue={lonlat}
innerRef={register}
/>
<MapContainer onMarkerChange={onMarkerChange} />
</Col>
</Row>
<h4>Inventory</h4>
<Inventory
onSubmitInventory={onSubmitInventory}
currentInventory={inventory}
/>
<Button
outline
color="secondary"
className="mt-2"
type="submit"
form="registerForm"
>
Submit
</Button>
</Form>
);
}
export default Register;
EDIT: Updated code with tips from answers, still facing the same problem :(
The only problem I see with your code is that you are trying to pass a second argument to setInventory. I don't believe this works with hooks like it did with class components. When I attempt to type that in typescript it throws an instant error.
Just pass the actual data you are trying to send and then call onSubmitInventory(inventory) for example:
const onChangeWeapon = (e) => {
const newInventory = {...inventory, weapon: e.target.value};
setInventory(newInventory);
onSubmitInventory(newInventory);
};
If you have to wait for the next render to call onSubmitInventory, it should be a useEffect in the parent. My next question would be why the parent needs the state and the child also has it as a state? Possibly it should be lifted up. But I'm not sure of that without seeing the parent.
Edit: Is your problem just with the console.log? If you run
const [state, setState] = useState(true);
// ...
setState(false);
console.log(state); // outputs true.
The value of state does not change until the next render. Calling setState will cause a new render, but until that happens, the old value is there. Add a console.log of state in the parent just in the body like here
const [inventory, setInventory] = useState({
water: 0,
soup: 0,
meds: 0,
weapon: 0,
});
console.log('I just rendered, inventory: ', inventory);
You'll see it updates!
The fact that the value of state does not change can bite you in this case:
const [state, setState] = useState(true); // closes over this state!
// ...
setState(!state); // closes over the state value above
setState(!state); // closes over the same state value above
// state will be false on next render
So I use this form if there is any possibility I call setState twice before a render:
const [state, setState] = useState(true);
// ...
setState(state => !state);
setState(state => !state);
// state will be true on next render
Inside your parent component's onSubmitInventory you have named the argument inventory but that already exists in the parent's scope. I suggest renaming that newInventory to be clear about which you are referencing.
Also, it seems like you are keeping track of inventory in both the parent and child's state. Keep it in the parent and pass it down to the child as a prop/props.

ANTD - How to change input value when blur another input

I'm using ANTD 4.1.2V and I have an input that receives a Zip code number within a form
When onblur event happens, I call a API passing the Zip code value and it returns me the address info.
What I need to do is to fill the others inputs with that values when onBlur event is triggered in Zip code input.
import React, { useState } from 'react';
import { Form, Input, Row, Col } from 'antd';
import MaskedInput from 'antd-mask-input';
import zipCodeAPI from '../../services/zipCodeAPI';
const ClientForm = () => {
const [loadingCep, setLoadingZipCode] = useState(false);
const [, setZipCode] = useState('');
const onChangePfCep = (value) => setZipCode(value);
const handleZipCode = async (cep) => {
setLoadingZipCode(true);
const { data } = await zipCodeAPI(cep);
const { street } = data;
setPublicPlace(street);
setLoadingZipCode(false);
};
return (
<Form
name="client-form"
onFinish={() => console.log('Test')}
layout="vertical"
initialValues={{ ...initialValues, layout: 'vertical' }}
>
<Row gutter={16} justify="space-between">
<Col span={3}>
<Form.Item label="ZIP CODE" name="zip_code">
<MaskedInput
mask="11111-111"
size="8"
onChange={({ target }) => onChangePfCep(target.value)}
// When onBlur here
onBlur={() => handleZipCode('04223-000')}
placeholder="XXXXX-XXX"
/>
</Form.Item>
</Col>
<Col span={7}>
<Form.Item label="Logradouro" name="street">
{/* Fill this input with the zipcodeAPI payload */}
<Input disabled />
</Form.Item>
</Col>
<Col span={2}>
<Form.Item label="Number" name="number">
{/* Fill this input with the zipcodeAPI payload */}
<Input />
</Form.Item>
</Col>
<Col span={5}>
<Form.Item label="Neighborhood" name="neighborhood">
{/* Fill this input with the zipcodeAPI payload */}
<Input disabled />
</Form.Item>
</Col>
</Row>
</Form>
);
};
export default ClientForm;
Does anybody know how to solve this?
You can do it like this
// you need to get the form instance from the useForm hook
const form = useForm();
const handleZipCode = async (cep) => {
setLoadingZipCode(true);
const { data } = await zipCodeAPI(cep);
const { street, number, neighborhood } = data; // changed
setPublicPlace(street);
setLoadingZipCode(false);
form.setFieldsValue({neighborhood, number, street}) // changed
};

How select checkbox in Reactjs?

I am trying when I click on a checkbox it should get selected and save its value true in localstorage so that if the page is refreshed it should get value from the localstorage, similarly for second checkbox if it is selected too then also save its value true in localstorage.
In simple way if I select a both the checkboxes it should retain even after page refresh this is what I am trying for
Here is my code is what I have tried
Link - https://codesandbox.io/s/musing-architecture-p2nrg?file=/src/App.js:0-1760
import React from "react";
import "./styles.css";
import { Form } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
export default function App() {
const data = {
naming: localStorage.getItem("naming") || false,
fullname: localStorage.getItem("fullname") || false
};
const [currentCheckboxId, setCheckboxId] = React.useState(data);
const setCheckbox = event => {
const naming = event.target.checked;
console.log(naming);
localStorage.setItem("naming", naming);
setCheckboxId({
...data,
naming: event.target.checked
});
};
const setCheckbox2 = event => {
const fullname = event.target.checked;
console.log(fullname);
localStorage.setItem("fullname", fullname);
setCheckboxId({
...data,
fullname: event.target.checked
});
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Form>
<>
<Form.Check
onChange={setCheckbox}
type="checkbox"
label="Check me out"
id="first"
checked={currentCheckboxId.naming}
/>
<Form.Group controlId="email">
<Form.Label>Email Address</Form.Label>
<Form.Control type="text" placeholder="Enter email" />
</Form.Group>
</>
<>
<Form.Check
onChange={setCheckbox2}
type="checkbox"
label="Check me out"
id="second"
checked={currentCheckboxId.fullname}
/>
<Form.Group controlId="fullname">
<Form.Label>Name</Form.Label>
<Form.Control type="text" placeholder="Enter name" />
</Form.Group>
</>
</Form>
</div>
);
}
Here is what you need to do:
Initialize the state with false
Use useEffect to run at mounted and retrieve checkbox values from LocalStorage and setState accordingly
Use setState with updater function to set new state which depends on current state
export default function App() {
// 1. Initially "false"
const [currentCheckboxId, setCheckboxId] = React.useState({
naming: false,
fullname: false
});
// 2. useEffect to run # mounted:
// get from LS and update the state
React.useEffect(() => {
const data = {
naming: localStorage.getItem('naming') === 'true' ? true : false,
fullname: localStorage.getItem('fullname') === 'true' ? true : false
};
setCheckboxId(data);
}, []);
const setCheckbox = event => {
const naming = event.target.checked;
console.log('naming', naming);
localStorage.setItem('naming', naming);
// 3. use "function" with prevData as first argument to setState
setCheckboxId(prevData => ({
...prevData,
naming: naming
}));
};
const setCheckbox2 = event => {
const fullname = event.target.checked;
console.log('fullname', fullname);
localStorage.setItem('fullname', fullname);
// 3. same as above
setCheckboxId(prevData => ({
...prevData,
fullname: fullname
}));
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Form>
<>
<Form.Check
onChange={setCheckbox}
type="checkbox"
label="Check me out"
id="first"
checked={currentCheckboxId.naming}
/>
{/* Rest of your code */}
}
Here is a playground.

Resources