In my project, I had retrieved a list of arrays from firestore database in UseEffect(). After that, I had filtered the list of arrays into a single array called medicineStock and passing each value to new React useStates (editMedicineName, editMedicineRegNo, and editMedicineType) and making them as values in my input, however it doesn't pass anything into each useStates and my input field stays empty.
function EditMedicine() {
const { medicineId } = useParams();
const [medicine, setMedicine] = useState([]);
useEffect(() => {
const q = query(collection(db, 'medicine'), where("isAccepted", "==", true) )
onSnapshot(q, (querySnapshot) => {
setMedicine(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
},[])
const medicineStock = medicine.filter(medicineStock => medicineStock.id === medicineId);
const [editMedicineName, setEditMedicineName] = useState(medicineStock.map(item => item.data.medicineName).toString());
const [editMedicineRegNo, setEditMedicineRegNo] = useState(medicineStock.map(item => item.data.medicineRegNo).toString());
const [editMedicineType, setEditMedicineType] = useState(medicineStock.map(item => item.data.medicineType).toString());
return (
<div className='editMedicine'>
<div className="editMedicine-title">Edit Medicine</div>
<form className="editMedicine-form" >
<div>
<p>
<label className="editMedicine-dataTitle">Name: </label>
<input
name="medicineName"
type="text"
required
className="editMedicine-input"
value={editMedicineName}
onChange={(e) => setEditMedicineName(e.target.value)}
/>
</p>
<p>
<label className="editMedicine-dataTitle">Reg No: </label>
<input
name="medicineRegNo"
type="text"
required
className="editMedicine-input"
value={editMedicineRegNo}
onChange={(e) => setEditMedicineRegNo(e.target.value)}
/>
</p>
<p>
<label className="editMedicine-dataTitle">Type: </label>
<select className="editMedicine-input" value={editMedicineType} onChange={(e) => setEditMedicineType(e.target.value)}>
<option value="Tablet">Tablet</option>
<option value="Capsule">Capsule</option>
<option value="Liquid">Liquid</option>
<option value="Spray">Spray</option>
<option value="Cream">Cream</option>
<option value="Ointment">Ointment</option>
</select>
</p>
</div>
</form>
</div>
)
}
I tired to console.log the medicineStock which contain the filtered single array in the medicine object and console return this:
[]
length: 0
[[Prototype]]: Array(0)
[{…}]
0:
data:
donationId: "XOCXD3FFaWGjO3oZcJmc"
medicineName: "Fluticasone Nasal Spray BP 50 mcg"
medicineRegNo: "MAL14125126AZ"
medicineType: "Spray"
[[Prototype]]: Object
id: "Ko6KqA9nyIotUVOkmfPL"
[[Prototype]]: Object
length: 1
[[Prototype]]: Array(0)
Where the first console.log return no value, while second console.log return the value I want to put into the React usestates. Is there any solution to solve this issue?
CodePudding user response:
I'll highlight problems and solutions related to your code:
- When you try to initialize your inputs
useState, themedicinearray is not ready yet, since the firestore call is asynchronous, so whenmedicinearray arrives, the inputsuseState(editMedicinName...) initial values will be stuck to an empty array. - You also have some problems in your data manipulation since you are placing a
type Arrayinside youreditMedicine****states, but then in your inputsonChangehandlers you are passing strings:setEditMedicineName(e.target.value)} - The solution to the first problem is to work with React hook
useEffects. The firstuseEffectis executed only once on mount, it calla firestore db, retrieves medicines list, and sets it to your react statemedicine. Whenmedicineis updated, the seconduseEffectwill be triggered, and inside of it you can update your input base values. - The second problem is addressed by setting directly strings values as your input base values.
- Last but not least, I expect you don't want to show your form until you don't
receive
medicinedata, so it's a good idea to place a ternary operator after the return, so you can show a spinner during loading operations.
function EditMedicine() {
const { medicineId } = useParams();
const [medicine, setMedicine] = useState([]);
useEffect(() => {
const q = query(
collection(db, 'medicine'),
where('isAccepted', '==', true)
);
onSnapshot(q, (querySnapshot) => {
// setMedicine is called asynchronously
setMedicine(
querySnapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}))
);
});
}, []);
// Filter medicine array, when it will be retrieved from firestore
const medicineStock = useMemo(
() =>
medicine.filter((medicineStock) => medicineStock.id === medicineId)[0]
.data,
[medicine]
);
const [editMedicineName, setEditMedicineName] = useState(null);
const [editMedicineRegNo, setEditMedicineRegNo] = useState(null);
const [editMedicineType, setEditMedicineType] = useState(null);
/* Set editMedicineName, editMedicineRegNo and editMedicineType when medicine array is retrieved */
useEffect(() => {
if (medicine) {
setEditMedicineName(medicineStock.medicineName);
setEditMedicineRegNo(medicineStock.medicineRegNo);
setEditMedicineType(medicineStock.medicinType);
}
}, [medicine]);
return !medicine ? "Loading..." :(
<div className="editMedicine">
<div className="editMedicine-title">Edit Medicine</div>
<form className="editMedicine-form">
<div>
<p>
<label className="editMedicine-dataTitle">
Name:
</label>
<input
name="medicineName"
type="text"
required
className="editMedicine-input"
value={editMedicineName}
onChange={(e) => setEditMedicineName(e.target.value)}
/>
</p>
<p>
<label className="editMedicine-dataTitle">
Reg No:
</label>
<input
name="medicineRegNo"
type="text"
required
className="editMedicine-input"
value={editMedicineRegNo}
onChange={(e) => setEditMedicineRegNo(e.target.value)}
/>
</p>
<p>
<label className="editMedicine-dataTitle">
Type:
</label>
<select
className="editMedicine-input"
value={editMedicineType}
onChange={(e) => setEditMedicineType(e.target.value)}
>
<option value="Tablet">Tablet</option>
<option value="Capsule">Capsule</option>
<option value="Liquid">Liquid</option>
<option value="Spray">Spray</option>
<option value="Cream">Cream</option>
<option value="Ointment">Ointment</option>
</select>
</p>
</div>
</form>
</div>
);
}
CodePudding user response:
There is few misunderstoods about how useState works in your code and generally how you can trigger a rerendering.
What is happening in your code:
- You init
medicinestate property with an empty array -> OK - You declare a new constant
medicineStockwhich is a filtered version ofmedicine. At this momentmedicineStockis an empty array too. - You init 3 new states properties with an initialValue based on medicineStock. It will produce 3 new empty strings. Here you have to understand that the initialValue of a state is not designed to update it but only to init it. If you update medicineStock, it will not update
editMedicineNamefor example. - Then you fetch some data and call
setMedicineto store it. This will have for effect to trigger a new rendering. So the body of your function will be read again. Of coursemedicineStockwill have a new value a this moment and that's why you are able to see it with your log. But as I said before, this will not updateeditMedicineName,editMedicineRegNoandeditMedicineType. The only way to update these values is to call their owns setters.
How to fix it:
I don't really understand what you are trying to achieve here but a simple fix for your code would be:
const [editMedicineName, setEditMedicineName] = useState();
const [editMedicineRegNo, setEditMedicineRegNo] = useState();
const [editMedicineType, setEditMedicineType] = useState();
useEffect(() => {
const q = query(collection(db, 'medicine'), where("isAccepted", "==", true) )
onSnapshot(q, (querySnapshot) => {
const medecine = querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
}));
const medicineStock = medicine.filter(medicineStock => medicineStock.id === medicineId);
setEditMedicineName(medicineStock.map(item => item.data.medicineName).toString());
setEditMedicineRegNo(medicineStock.map(item => item.data.medicineRegNo).toString());
setEditMedicineType(medicineStock.map(item => item.data.medicineType).toString());
})
},[])
