0

I have a react component where I collect people's contact information from Google People API after having their permission. I collect the contacts name, email, phone numbers and addresses in an array. Then I pass the array as a prop to the child component. But although the child component apparently receives the prop, the console says its length is zero.

The console output reads like this: Preview of react component

The code where I build the array of objects is like this (I had previously built the whole object, now I just append strings to test if it's working, but I need to pass an array of objects to the component props):

    var Elementos = [];
    (I call the google api here).then(function (resp) {

            var Fila = 0;
            resp.result.connections.forEach(list => {
                var Contacto = {}
                if (list.names != undefined && list.names.length > 0) {
                    Contacto.Nombre = list.names[0].displayName;
                }
                if (list.emailAddresses != undefined && list.emailAddresses.length > 0) {
                    Contacto.Email = list.emailAddresses[0].value;
                }
                if (list.addresses != undefined && list.addresses.length > 0) {
                    Contacto.Direccion = list.addresses[0].value;
                }
                if(list.phoneNumbers != undefined && list.phoneNumbers.length > 0){
                    var telefonos = "";
                    list.phoneNumbers.forEach(item=>{
                        telefonos+=item.value+",";
                    })
                    telefonos = telefonos.substring(0, telefonos.length-1);
                    Contacto.Telefono = telefonos;
                }
                if (list.names != undefined && list.names.length > 0 && list.emailAddresses != undefined && list.emailAddresses.length > 0 && list.phoneNumbers != undefined && list.phoneNumbers.length > 0) {
                    Fila++;
                    Contacto.Numero = Fila;
                    Elementos.push(Contacto.Nombre +" "+Contacto.Telefono);
                }
            });
            return resp;
        })
        return datos;
    }).then((respuesta) => {
        this.setState(prevEstado => {
            prevEstado.Contactos = Elementos, prevEstado.DirectorioCargado = true;
            return prevEstado});});

Then I pass the properties like this:

return (
    <div>
        <DirectorioContactos titulo="Directorio de contactos" initialize={this.state.Contactos} />
    </div>
)

And finally in the child component I do this (after setting the state in the constructor):

render() {
        console.log('en propiedades');
        console.log(this.state.Contactos);
        console.log(this.state.Contactos.length);

        const algo = ["Entonces","Tu","Valiste","Tres kilos"];
        return (
            <div>
            <Jumbotron>
                <h2>{this.props.titulo}</h2>
                <p>Invita a los contactos que desees. Les enviaremos un correo informando tu decisión de agregarlos a nuestro directorio.</p>
            </Jumbotron>
            <Table striped bordered condensed hover>
                <thead>
                    <tr>
                        <th>#</th>
                        <th>Nombre y Apellidos</th>
                        <th>Teléfono</th>
                        <th>Correo electrónico</th>
                        <th>Dirección</th>
                    </tr>
                </thead>
                <tbody>{this.state.Contactos.map((item, i) => {
            return(<tr key={i}>
                <td>{i}</td>
                <td>{item}</td>
                <td></td>
                <td></td>
                <td></td>
                </tr>);})
                }</tbody>
            </Table>
            </div>
        );
    }

But as you can see in the image nothing get's rendered -the line in the console that reads 0 in the image, if you look carefully is the output to a call to console.log(the property.length)-. I also tested the map function and it works correctly when I use the constant value algo like algo.map((item, i).

So my question is ¿what I'm doing wrong? Consider please that I'm new to React, and from what I've read I understand it is possible to pass properties like this, I would even say that I understand it is the purpose of React js.

UPDATE: From the comments I've read (and also what I have read about React js) I really think that I'm missing something that I cannot put into words here: I think it's something about my understanding of what state and props are, because I found the problem begins when I set the Contactosstate. I did this:

.then((respuesta) => {
                this.setState({Contactos:Elementos, DirectorioCargado :true
                },()=>{
                    console.log('Después de cargar estado');
                    console.log(this.state.Contactos);
                    console.log(this.state.Contactos.length);
                });

And from right there it shows there's a length of zero in the array, although the log shows elements in it. For reference, I do this in the constructor of the parent component:

super();
        this.state = {
            Persona: {
                Nombres: '',
                ApellidoPaterno: '',
                ApellidoMaterno: '',
                Telefono: '',
                Calle: '',
                NumeroExterior: '',
                NumeroInterior: '',
                Colonia: '',
                Localidad: '',
                EntidadFederativa: '',
                CodigoPostal: '',
                Email: '',
                Mensaje: '',
                RecibeOfertas: false,
                InvitaContactos: false,
            },
            Contactos: [],
            DirectorioCargado: false

I ask again, is there a way to correctly set the array property Contactos. Forgive me as I said I'm just starting with React.

gerardo flores
  • 402
  • 1
  • 6
  • 28

2 Answers2

1

In child component you are trying to access this.state.Contactos but you pass contact list as initialize property. It should be available inside child component as this.props.initialize.

Gennady Dogaev
  • 5,902
  • 1
  • 15
  • 23
  • I have already tried using the prop directly: `console.log('en propiedades'); console.log(this.props.initialize); console.log(this.props.intialize.length);` shows the same output, and still length is zero for some reason that I really wish I could formulate as it is the core of my question – gerardo flores Jan 22 '20 at 02:34
0

This answer is based on my assumption since I cannot check the whole source code. I guess the DirectorioContactos component is a PureComponent.

Let's say you have a React component with state and you initialized it like this

var Elementos = [];
this.state = {
  Contactos: Elementos
}

// in render
<PureChildComponent initialize={this.state.Contactos} />

And then you update it like this

Elementos.push("foo");
this.setState({
  Contactos: Elementos
});

If the PureChildComponent is a PureComponent, then it won't get rerendered even after you update Elementos. This is because you updated Contactos by mutating it directly, which means the reference of Contactos hasn't been changed. And React won't rerender a PureComponent unless the reference is updated. So instead of Elementos.push("foo");, the right way is

this.setState({
  Contactos: [...Elementos, "foo"] // always create a new array
});


Besides, from the official document,

state is a reference to the component state at the time the change is being applied. It should not be directly mutated.

So instead of doing

this.setState(prevEstado => {
  prevEstado.Contactos = Elementos, prevEstado.DirectorioCargado = true;
  return prevEstado;
});

the right way is

this.setState(prevEstado => ({
  ...prevEstado,
  Contactos: Elementos,
  DirectorioCargado: true,
}));

or just

this.setState({
  Contactos: Elementos,
  DirectorioCargado: true,
});

because React will shallowly merge the new state with the previous state for you.

ssdh233
  • 145
  • 1
  • 11
  • I'm really not interested in changing the state from one component to another, I just want to manipulate the prop I pass to the child, in another state owned by the child component. I'll try what you mention, but my doubt is if there's something fundamentally wrong with what I did and have explained – gerardo flores Jan 22 '20 at 02:36
  • If `DirectorioContactos` is a PureComponent, then it wouldn't be rerendered unless you change the reference. That's where I guess you did something "fundamentally wrong". I cannot find out anything else unless you post the full code – ssdh233 Jan 22 '20 at 02:47
  • Yes, I think I get what you mean, the problem I face is I push the elements to the array before I set the state because as you have seen I return from a promise and only then I have access to the state, I cannot currently do what you suggest with `Contactos: [...Elementos, "foo"]`. By the way, what does that code mean? Do I have to add the current element in a `forEach` using that syntax. Excuse me I hadn't seen it like that. From [this answer](https://stackoverflow.com/questions/26253351/correct-modification-of-state-arrays-in-reactjs) I guess I'll have to refactor my code – gerardo flores Jan 22 '20 at 02:57
  • Ah sorry, instead of `Contactos: [...Elementos, "foo"]`, you can do `Contactos : Elementos.concat(["foo"])`, they do the same thing. `Contactos: [...Elementos, "foo"]` is the [new ES6 syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) – ssdh233 Jan 22 '20 at 03:03
  • And the problem is not because "you push the elements to the array before I set the state", it's because doing `Elementos.push("foo")` will not change the reference of `Elementos.push `, but doing `Elementos = Elementos.concat(["foo"])` will. And depends on this React will decide to rerender the component or not if you are using PureComponent – ssdh233 Jan 22 '20 at 03:07