sdrai
sdrai

Reputation: 23

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:

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

Upvotes: 2

Views: 234

Answers (2)

Zach
Zach

Reputation: 1964

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

Upvotes: 1

Michalis Garganourakis
Michalis Garganourakis

Reputation: 2930

Starting from your App, I would suggest you to move your contacts object to a constant, as useState is not needed at this level.

// constants.js
export const contacts = [
    {
      key: 0,
      name: 'Lennon',
      firstname: 'John',
      notes: 'smart guy'
    },
    {
      key: 1,
      name: 'Starr',
      firstname: 'Ringo',
      notes: 'funny guy'
    }
  ]
};

// App.js
import React from 'react';
import { contacts } from './constants';
import ContactsList from './components/contacts-list/contacts-list.component'
import './App.scss'

function App() {
  return (
    <div className='App'>
      <ContactsList contacts={contacts} />
    </div>
  )
}

export default App

Moving on ContactList component, as it's the component that renders each contact, I would build my state here. In that why, I would know if I need to render a contact or not beforehand.

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

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

function ContactsList(props) {
  const [contacts, setContacts] = useState(props.contacts);
  const handleDeletion = id => {
    setContacts(contacts.filter(contact => contact.id !== id));
  }

  return (
    <div className='contacts-list'>
      <span className='title'>Contacts</span>
      {contacts.length ? 
        contacts.map(contact => 
          <ContactCard
            id={contact.id}
            name={contact.name}
            firstname={contact.firstname}
            notes={contact.notes}
            handleDeletion={handleDeletion}
          />
        ) : null}
      <hr />
    </div>
  )
}

export default ContactsList

Notice that I am passing the function that handles the removal to my ContactCard, while I am still deciding here if I should show my contact.

import React from 'react'

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

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

export default ContactCard

I haven't tried the code, but I think you should move at this path.

Upvotes: 0

Related Questions