Home > OS >  React Native: filtered array failed to pass value to usestates
React Native: filtered array failed to pass value to usestates

Time:04-10

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:&nbsp;&nbsp;&nbsp;</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:&nbsp;&nbsp;&nbsp;</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:&nbsp;&nbsp;&nbsp;</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, the medicine array is not ready yet, since the firestore call is asynchronous, so when medicine array arrives, the inputs useState (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 your editMedicine**** states, but then in your inputs onChange handlers you are passing strings: setEditMedicineName(e.target.value)}
  • The solution to the first problem is to work with React hook useEffects. The first useEffect is executed only once on mount, it calla firestore db, retrieves medicines list, and sets it to your react state medicine. When medicine is updated, the second useEffect 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:&nbsp;&nbsp;&nbsp;
            </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:&nbsp;&nbsp;&nbsp;
            </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:&nbsp;&nbsp;&nbsp;
            </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:

  1. You init medicine state property with an empty array -> OK
  2. You declare a new constant medicineStock which is a filtered version of medicine. At this moment medicineStock is an empty array too.
  3. 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.
  4. 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 course medicineStock 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 update editMedicineName, editMedicineRegNo and editMedicineType. 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());
    })
  },[])
    
  • Related