javascriptreactjsjsxnested-objectobject-destructuring

Object argument becomes nested after being passed to constructor in React


I'm having trouble understanding a very weird bug while doing Part 2 on Full Stack Open. In few words: passing an object to the component turns the object into a nested object. Namely:

entries.map(entry => { console.log(entry); return <PhonebookEntry  props={entry} />})

on App.js and

function PhonebookEntry(props) {
  console.log(props)
  //rest of code
}

on index.js don't work as expected.

When ran, the browser's console displays the following:

Object { name: "Derp", surname: "Derpington", phone: "012345", address: "2012 Street" }
App.js:33

Object { props: {…} }
props: Object { name: "Derp", surname: "Derpington", phone: "012345", … }
<prototype>: Object { … }
PhonebookEntry.js:2

Here's App.js:

import { useState } from 'react'
import PhonebookEntry from './PhonebookEntry.js'

export default function App() {

  function handleAddEntry(e) {
    e.preventDefault()
    setEntries(entries.concat({
      name: newName,
      surname: newSurname,
      phone: newPhone,
      address: newAddress
    }))
  }

  const [entries, setEntries] = useState([
    {
      name: "Derp",
      surname: "Derpington",
      phone: "012345",
      address: "2012 Street"
    }
  ])
  const [newName, setNewName] = useState('')
  const [newSurname, setNewSurname] = useState('')
  const [newPhone, setNewPhone] = useState('')
  const [newAddress, setNewAddress] = useState('')

  return (
    <div>
      <h1>Phonebook</h1>
      <ul>
        {entries.map(entry => { console.log(entry); return <PhonebookEntry  props={entry} />})}
      </ul>
      <form onSubmit={handleAddEntry}>
        Name: <input value={newName} onChange={(e) => setNewName(e.target.value) }/><br></br>
        Surname: <input value={newSurname} onChange={(e) => setNewSurname(e.target.value)}/><br></br>
        Phone: <input value={newPhone} onChange={(e) => setNewPhone(e.target.value)}/><br></br>
        Address: <input value={newAddress} onChange={(e) => setNewAddress(e.target.value)}/><br></br>
        <button type="submit">Add</button>
      </form>
    </div>
  )
  }

and here's PhonebookEntry.js:

function PhonebookEntry(props) {
  console.log(props)
  return (
      <li>
        <div>
          Name: {`${props.surname}, ${props.name}`}
        </div>
        <div>
          Phone: {props.phone}
        </div>
        <div>
          Address: {props.address}
        </div>
      </li>
    )
}

export default PhonebookEntry

Solution

  • It’s not a bug, it’s a feature.

    You can think of this:

    <Component foo={bar} />
    

    As this:

    Component({ foo: bar })
    

    Now, it’s a bit more complicated than just that, but really that’s what props in JSX represent.

    So, just like how we have spread parameters/arguments, we also have spread props:

    <PhonebookEntry {...entry} />
    

    This is the same thing as (roughly):

    PhonebookEntry({ ...entry })
    

    https://reactjs.org/docs/jsx-in-depth.html#spread-attributes

    Back to your original example,

    <PhonebookEntry props={entry} />
    

    Would just be

    PhonebookEntry({ props: entry })
    

    Which is why it’s nested.