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
, themedicine
array is not ready yet, since the firestore call is asynchronous, so whenmedicine
array 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 Array
inside youreditMedicine****
states, but then in your inputsonChange
handlers you are passing strings:setEditMedicineName(e.target.value)}
- The solution to the first problem is to work with React hook
useEffects
. The firstuseEffect
is executed only once on mount, it calla firestore db, retrieves medicines list, and sets it to your react statemedicine
. Whenmedicine
is updated, the seconduseEffect
will 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
medicine
data, 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
medicine
state property with an empty array -> OK - You declare a new constant
medicineStock
which is a filtered version ofmedicine
. At this momentmedicineStock
is 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
editMedicineName
for example. - Then you fetch some data and call
setMedicine
to store it. This will have for effect to trigger a new rendering. So the body of your function will be read again. Of coursemedicineStock
will 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
,editMedicineRegNo
andeditMedicineType
. 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());
})
},[])