Search code examples
javascriptreactjsreact-hooksreact-state-management

State management from parent component using hooks


I'm quite new to Hooks and I am trying to build a small address book.

So I have two components:

  • A ContactCard component
  • A ContactsList component

I want cards to be removed when the X is clicked. I managed to toggle the deleted prop of my contact, but I can't figure out how to force re-render the ContactsList then

import React, { useState } from 'react'
import ContactsList from './components/contacts-list/contacts-list.component'
import './App.scss'

function App() {
  const [contacts] = useState([
    {
      key: 0,
      name: 'Lennon',
      firstname: 'John',
      notes: 'smart guy',
      deleted: false
    },
    {
      key: 1,
      name: 'Starr',
      firstname: 'Ringo',
      notes: 'funny guy',
      deleted: false
    }
  ])
  return (
    <div className='App'>
      <ContactsList contacts={contacts} />
    </div>
  )
}

export default App

import React, { useState, useEffect } from 'react'
import ContactCard from '../contact-card/contact-card.component'

import './contacts-list.styles.scss'

function ContactsList(props) {
  const [contacts, setContacts] = useState(props.contacts)

  return (
    <div className='contacts-list'>
      <span className='title'>Contacts</span>
      {contacts
        .filter(contact => contact.deleted === false)
        .map(contact => (
          <ContactCard
            name={contact.name}
            firstname={contact.firstname}
            notes={contact.notes}
            deleted={contact.deleted}
          />
        ))}
      <hr />
    </div>
  )
}

export default ContactsList

import React, { useState } from 'react'

import './contact-card.styles.scss'

function ContactCard(props) {
  const [contact, setContact] = useState([
    {
      name: props.name,
      firstname: props.firstname,
      notes: props.notes,
      deleted: false
    }
  ])

  function deleteContact() {
    const currentContact = [...contact]
    currentContact[0].deleted = true
    setContact(currentContact)
  }

  return (
    <div className='contact-card'>
      <span className='contact-name'>{props.name}</span>
      <span className='delete-contact' onClick={deleteContact}>
        &#10005;
      </span>
      <br />
      <span className='contact-firstname'>{props.firstname}</span>
      <hr className='separator' />
      <span className='contact-notes'>{props.notes}</span>
    </div>
  )
}

export default ContactCard


Solution

  • Really a few options here, the simplest is probably just to pass in an 'onContactDeleted' prop and callback to the parents to let them know to update the state. This method isn't always the cleanest, especially with highly nested components but I would recommend it as a start is as it really is the most vanilla way that will help you understand how prop and state changes work. Note that I kept your soft delete method but you could also just remove it from the list.

    Card

    import React, { useState } from 'react'
    
    import './contact-card.styles.scss'
    
    function ContactCard(props) {
    
      function deleteContact(key) {
        props.onContactDeleted(key)
      }
    
      return (
        <div className='contact-card'>
          <span className='contact-name'>{props.name}</span>
          <span className='delete-contact' onClick={() => deleteContact(props.contactKey)}>
            &#10005;
          </span>
          <br />
          <span className='contact-firstname'>{props.firstname}</span>
          <hr className='separator' />
          <span className='contact-notes'>{props.notes}</span>
        </div>
      )
    }
    
    export default ContactCard
    

    List

    import React, { useState, useEffect } from 'react'
    import ContactCard from '../contact-card/contact-card.component'
    
    import './contacts-list.styles.scss'
    
    function ContactsList(props) {
      const [contacts, setContacts] = useState(props.contacts)
    
      return (
        <div className='contacts-list'>
          <span className='title'>Contacts</span>
          {contacts
            .filter(contact => contact.deleted === false)
            .map(contact => (
              <ContactCard
                contactKey={contact.key}
                name={contact.name}
                firstname={contact.firstname}
                notes={contact.notes}
                deleted={contact.deleted}
                onContactDeleted={props.onContactDeleted}
              />
            ))}
          <hr />
        </div>
      )
    }
    
    export default ContactsList
    

    App

    import ContactsList from './components/contacts-list/contacts-list.component'
    import './App.scss'
    
    function App() {
      const [contacts, setContacts] = useState([
        {
          key: 0,
          name: 'Lennon',
          firstname: 'John',
          notes: 'smart guy',
          deleted: false
        },
        {
          key: 1,
          name: 'Starr',
          firstname: 'Ringo',
          notes: 'funny guy',
          deleted: false
        }
      ])
      return (
        <div className='App'>
          <ContactsList contacts={contacts} 
                        onContactDeleted={
                          (key_to_delete) => {
                             //note this might not be correct, use it as pseudocode
                             var copy = [...contacts]
                             var contact = copy.find(x => x.key == key_to_delete)
                             if(contact)
                             {
                                contact.deleted = true;
                                setContacts(copy)
                             }
                          }
                         }/>
        </div>
      )
    }
    
    export default App
    

    Once you have that you could use something like redux or the useContext hook to share the state and "cut out the middle man"

    Here is an example of the useContext hook that I quickly found online, not sure how good it is

    https://www.codementor.io/@sambhavgore/an-example-use-context-and-hooks-to-share-state-between-different-components-sgop6lnrd