1

I'm trying to add in back-end with Firebase a document in a collection whose name is randomly generated thanks to a function firestoreAutoId() however when I submit the form React returns me an error saying "Error in creating user FirebaseError: Function DocumentReference.set() called with invalid data. Unsupported field value: undefined (found in field lastname in document av_deliveries/[object Object])" ", it may seem silly but I don't really understand why this error. It doesn't matter if I change the name or the value, it goes to another problem.

Here is the whole code for a better understanding:

AddDelivery.Js

import { createDelivery, firestoreAutoId } from '../firebase';
import './AddDelivery.css';

class AddDelivery extends Component {
    state = {when: '', whath: '', from: 0, to: 0, to_adress: '', to_tel: 0, name: '', to_name: '', name2: '', tel: 0, adress: '', info_comp: '', taken: false, taken_by: "X", city:''};
    handleChange = (e) => {
        const { name, value } = e.target;

        this.setState({ [name]: value });
    };
    handleSubmit = async (e) => {
        e.preventDefault();
        const {when, whath, from, to, to_adress, to_tel, name, to_name, name2, tel, adress, info_comp, taken, taken_by, city} = this.state;
        try {
            let id
            id = firestoreAutoId()
            await createDelivery({ id }, { when }, { whath }, { from }, { to }, { to_adress }, { to_tel } , { name } , { to_name }, { name2 }, { tel } , { adress } ,  { info_comp } , { taken } , { taken_by } , { city });
        } catch (error) {
            console.log('error', error);
        }
        this.setState({id: '', when: '', whath: '', from: 0, to: 0, to_adress: '', to_tel: 0, name: '', to_name: '', name2: '', tel: 0, adress: '', info_comp: '', taken: false, taken_by: "X", city:''});
    };
    render() {
    const {when, whath, from, to, to_adress, to_tel, name, to_name, name2, tel, adress, info_comp, taken, taken_by, city} = this.state;
    return (
        <div className="main-wrapper">
            <div className="form-main-wrapper">
                <p className="form-add-delivery-hero-title">Ajouter une livraison</p>
                <form className="form-wrapper" onSubmit={this.handleSubmit}>
                    <div className="form-when-wrapper">
                        <div className="form-when">
                            <label>Quand ?</label>
                            <input type="date" name="when" value={when} onChange={this.handleChange} required></input>
                        </div>
                        <div className="form-when-hour">
                            <label>A quelle heure ?</label>
                            <input type="time" name="whath" value={whath} onChange={this.handleChange} required></input>
                        </div>
                        <div className="form-city-name">
                            <label>Ville ?</label>
                            <select className="form-city-selector" name="city" value={city} onChange={this.handleChange}>
                                <option value="Lyon">Lyon</option>
                                <option value="Montpellier">Montreuil</option>
                                <option value="Paris">Paris</option>
                                <option value="Vélizy-Villacoublay">Vélizy-Villacoublay</option>
                                <option value="Viroflay">Viroflay</option>
                            </select>
                        </div>
                    </div>
                    .... Same for the rest of the form..

Firebase.js

export const createDelivery = async (id1, when1, whath1, from1, to1, adress_to1, tel_to1, name1, name2, tel1, adress1, info_comp1, taken1, taken_by1, city1) => {

  const userRef = firestore.doc(`av_deliveries/${id1}`);
  const snapshot = await userRef.get();

  if (!snapshot.exists) {
    const { id } = id1;
    const { when } = when1;
    const { whath } = whath1;
    const { from } = from1;
    const { to } = to1;
    const { adress_to } = adress_to1;
    const { tel_to } = tel_to1;
    const { name } = name1;
    const { lastname } = name2;
    const { tel } = tel1;
    const { adress } = adress1;
    const { info_comp } = info_comp1;
    const { taken } = taken1;
    const { taken_by } = taken_by1;
    const { city } = city1;

    try {
        await userRef.set({
        id,
        when,
        whath,
        from,
        to,
        adress_to,
        tel_to,
        name,
        lastname,
        tel,
        adress,
        info_comp,
        city,
      });
    } catch (error) {
      console.log('Error in creating user', error);
    }
  }
};

Thanks for your help!

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
tom debout
  • 69
  • 1
  • 6

1 Answers1

4

The bugs

1. Resetting the component's state

You initially set your state to:

state = {when: '', whath: '', from: 0, to: 0, to_adress: '', to_tel: 0, name: '', to_name: '', name2: '', tel: 0, adress: '', info_comp: '', taken: false, taken_by: "X", city:''};

but this doesn't match what you set the state to after creating a delivery:

this.setState({id: '', when: '', whath: '', from: 0, to: 0, to_adress: '', to_tel: 0, name: '', to_name: '', name2: '', tel: 0, adress: '', info_comp: '', taken: false, taken_by: "X", city:''});

You should store your default state as an object, and then use that to reset your component:

const DEFAULT_STATE = {when: '', whath: '', from: 0, to: 0, to_adress: '', to_tel: 0, name: '', to_name: '', name2: '', tel: 0, adress: '', info_comp: '', taken: false, taken_by: "X", city:''};
/* ... */
state = { ...DEFAULT_STATE }; // take a shallow copy of DEFAULT_STATE
/* ... */
this.setState({ ...DEFAULT_STATE }); // use a shallow copy of DEFAULT_STATE

2. Document ID

This line incorrectly sets the reference to av_deliveries/[object Object]:

firestore.doc(`av_deliveries/${id1}`); // id1 is an object of shape { id: string }

3. Incorrect Argument to createDelivery()

When you call createDelivery(), the ninth argument is called with { to_name: string }, yet you are expecting { lastname: string }.

The following line will always set lastname to undefined.

const { lastname } = { to_name: 'any string' };

4. Incorrect usage of the destructuring pattern

The purpose of the destructuring pattern is to place variables into a single object and then extract the properties you need. It's also meant to be used so that you don't provide a large number of arguments to a function.

This line:

await createDelivery({ id }, { when }, { whath }, { from }, { to }, { to_adress }, { to_tel } , { name } , { to_name }, { name2 }, { tel } , { adress } ,  { info_comp } , { taken } , { taken_by } , { city });

should be:

await createDelivery({ id, when, whath, from, to, to_adress, to_tel, name, to_name, name2, tel, adress, info_comp, taken, taken_by, city });

or alternatively, you could use either of these if the names match up:

await createDelivery(this.state);
// or
await createDelivery({ ...this.state });

These lines:

export const createDelivery = async (id1, when1, whath1, from1, to1, adress_to1, tel_to1, name1, name2, tel1, adress1, info_comp1, taken1, taken_by1, city1) => {
  /* ... */

  const { id } = id1;
  const { when } = when1;
  const { whath } = whath1;
  const { from } = from1;
  const { to } = to1;
  const { adress_to } = adress_to1;
  const { tel_to } = tel_to1;
  const { name } = name1;
  const { lastname } = name2;
  const { tel } = tel1;
  const { adress } = adress1;
  const { info_comp } = info_comp1;
  const { taken } = taken1;
  const { taken_by } = taken_by1;
  const { city } = city1;

  /* ... */
}

should be:

export const createDelivery = async ({ id, when, whath, from, to, to_adress, to_tel, name, to_name, name2, tel, adress, info_comp, taken, taken_by, city }) => {
  /* ... each property in the above object is available as a variable ... */
}

5. No need for firestoreAutoId()

These lines do the same thing (unless firestoreAutoId() is doing something unexpected):

const id = firestoreAutoId();
/* ... */
const userRef = firebase.firestore().doc(`av_deliveries/${id}`);
const userRef = firebase.firestore().collection("av_deliveries").doc();

6. Virtually no need to check existence of a new auto-id document

In your current code, you create a new document reference with a generated ID, read it from the database and then only write it when the data doesn't already exist.

The generated IDs have about 62^20 different combinations, to have a 1% chance of collision, you'd have to generate around 163 quadrillion IDs. For how unlikely that is, Firestore has a limit of 10k writes per second, and at that rate, you'd have to spend about 517000 years maxing that cap to get that many IDs.

So, these lines can be removed:

const snapshot = await userRef.get();
if (!snapshot.exists) { /* ... */ }

If you definitely don't want to have a collision, block it in your security rules rather than rely on clients.

Applying the changes

// AddDelivery.js
import { createDelivery } from '../firebase';
import './AddDelivery.css';

const DEFAULT_STATE = {when: '', whath: '', from: 0, to: 0, to_adress: '', to_tel: 0, name: '', to_name: '', name2: '', tel: 0, adress: '', info_comp: '', taken: false, taken_by: "X", city: ''};

class AddDelivery extends Component {
  state = { ...DEFAULT_STATE };
  
  handleChange = (e) => {
    const { name, value } = e.target;

    this.setState({ [name]: value });
  };
  
  handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await createDelivery(this.state);
    } catch (error) {
      // never called unless you have a syntax error?
      console.log('error', error);
    }
    this.setState({ ...DEFAULT_DATA });
  };
  
  render() {
    const {when, whath, from, to, to_adress, to_tel, name, to_name, name2, tel, adress, info_comp, taken, taken_by, city} = this.state;
    
    /* ... */
  }
}
// firebase.js
export const createDelivery = async ({ when, whath, from, to, to_adress, to_tel, name, to_name, name2, tel, adress, info_comp, taken, taken_by, city }) => {
  // create a new document in the "av_deliveries" collection.
  const userRef = firestore.collection("av_deliveries").doc();
  
  // to_name, taken and taken_by are unused?
  const data = {
    id: userRef.id,
    when,
    whath,
    from,
    to,
    adress_to: to_adress, // consider naming these the same
    tel_to: to_tel, // consider naming these the same
    name,
    lastname: name2, // consider naming these the same
    tel,
    adress,
    info_comp,
    city,
  };

  try {
    await userRef.set(data);
    return data; // return data when successful
  } catch (error) {
    console.log('Error in creating user', error);
    return false; // return false on failure
  }
};
samthecodingman
  • 23,122
  • 4
  • 30
  • 54
  • Indeed there was a problem with the array, and the variables have a very unexplicit name, sorry, however an error is displayed when I try to pass my id in a field id of the database ```Error in adding delivery FirebaseError: Function DocumentReference.set() called with invalid data. Unsupported field value: undefined (found in field id in document av_deliveries/wcuJS9gDJXaEPmmVrWrC)``` Otherwise without the id it works perfectly so thanks! – tom debout Mar 28 '21 at 14:45
  • @tomdebout expanded the answer with more bugs and recommendations – samthecodingman Mar 29 '21 at 05:23