Blog

reactRedux

React Redux: come collegare React con Redux

Tempo di lettura: 9 minuti

Nei tutorial precedenti abbiamo visto come funziona Redux e creato una piccola applicazione. In questa guida ci accingeremo a creare un collegamento React Redux ed andremo a completare l’applicazione iniziata la volta scorsa.

Se non lo avete fatto, prima di proseguire, vi consiglio di leggervi gli articoli precedenti:

  1. Redux e React: la guida pratica ed essenziale

  2. Redux e React: realizzare un’applicazione con Redux

 

React Redux: componenti smart e componenti dumb

Anche se si tratta di un argomento affrontato nel corso del primo tutorial diamo un veloce sguardo alle differenze tra i due componenti. Se vi ricordate vi ho fatto creare due cartelle distinte ossia containers per i componenti smart e components per i dumb.

Tale approccio offre il vantaggio di separare la logica di comportamento dalla vista.
I componenti di presentazione  sono definiti dumb in quanto si preoccupano della vista dell’App e non della logica della stessa. Questi ricevono proprietà e/o metodi dal componente genitore tramite le props (se non capite di cosa parlo vi consiglio di seguire questa guida).

Vediamo ora come organizzare il nostro proggetto:

react redux

I componenti Dumb o presentazionali

Ecco i componenti di presentazione che utilizzeremo:

components/addContactForm.js

import React from 'react';
 
const AddContactForm = ({onInputChange, onFormSubmit}) => 
    (
    <form>
            <div className="form-group">
                <label htmlFor="emailAddress">Email address</label>
                <input type="email" class="form-control" name="email" onChange={onInputChange} placeholder="name@example.com" />
            </div>
             
        {/* Some code omitted for brevity */}
               
            <div className="form-group">
                <label htmlFor="physicalAddress">Address</label>
                <textarea className="form-control" name="address" onChange={onInputChange} rows="3"></textarea>
            </div>
 
            <button type="submit" onClick={onFormSubmit} class="btn btn-primary"> Submit </button>
        </form>
    )
 
export default AddContactForm;

come potete notare utilizzo classi di bootstrap quindi aprite il file public/index.html ed aggiungete:

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

subito prima della chiusura del tag head.

Si tratta di un semplice modulo HTML per aggiungere un nuovo contatto. Il componente riceve il callback onInoputChange e onFormSubmit come props. L’evento onInputChange viene generato quando cambia il valore di input e onFormSubmit quando il modulo viene inviato.

components/ContactList.js

const ContactList = (props) => {
    return( <ul className="list-group" id="contact-list">
                {props.contactList.map(
                  (contact) => 
                  <li key={contact.email} className="list-group-item"> 
                    <ContactCard contact = {contact}/>
                  </li>
                )}
            </ul>)
}
 
export default ContactList;

Questo componente riceve un array di oggetti di contatto come props. Quindi utilizzo il metodo array.map() per estrarre i singoli dettagli e trasmetterli a <ContactCard/>.

components/ContactCard.js

const ContactCard = ({contact}) => {
     
    return(
        <div>
            <div className="col-xs-4 col-sm-3">
               {contact.photo !== undefined ?  <img src={contact.photo} alt={contact.name} className="img-fluid rounded-circle" /> :
                                         <img src="img/profile_img.png" alt ={contact.name} className="img-fluid rounded-circle" />}
            </div>
            <div className="col-xs-8 col-sm-9">
                <span className="name">{contact.name + ' ' + contact.surname}</span><br/>
                 
                {/* Some code omitted for brevity */}
                 
            </div>
          </div>
         
    )
}
 
export default ContactCard;

Questo riceve un oggetto di contatto e visualizza il nome e l’iimagine del contatto.

React Redux: i componenti Smart o containers

Vediamo quali saranno i componenti Smart o containers.

containers/Contact.js

class Contacts extends Component {
  
  constructor(props) {
      super(props);
      this.returnContactList = this.returnContactList.bind(this);
  }
  returnContactList() {
    // Retrieve contactlist from the store
  }
 
  render() {
    
    return (
        <div>
 
            <AddContact/>
            <br />
          <ContactList contactList= {this.returnContactList()} />
        </div>
    );
  }
}
 
 
export default Contacts;

Il metodo returnContactList() recupera l’array degli oggetti di contatto e lo passa al componente ContactList. Poichè returnContactList() recupera i dati dallo  store, al momento lo lasceremo in bianco.

containers/AddContact.js

class AddContact extends Component {
    constructor(props) {
        super(props);
         
        /* Function binding goes here. Omitted for brevity */
    }
 
    showAddContactBox() {
        /* Logic for toggling ContactForm */
    }
 
    handleInputChange(event) {
        const target = event.target;
        const value = target.value;
        const name = target.name;
 
        /* Logic for handling Input Change */
 
    }
 
    handleSubmit(e) {
        e.preventDefault();
         
        /* Logic for hiding the form and update the state */
    }
     
    /* Renders the AddContactForm */
    renderForm() {
        return(
            <div className="col-sm-8 offset-sm-2">
                <AddContactForm onFormSubmit={this.handleSubmit} onInputChange={this.handleInputChange} />
            </div>
        )
    }
    render() {
        return(
            <div>
                 
                { /* A conditional statement goes here that checks whether the form 
                    should be displayed or not */}
            </div>
            )
    }
}
 
 
export default AddContact;

Abbiamo creato tre componenti i quali gestiscono l’invio delle azioni per aggiornare lo stato. Al momento il metodo render() non contiene niente in quanto abbiamo prima bisogno di recuperare lo stato.

Libreria React-Redux

Per poter collegare React e Redux dobbiamo prima installare una libreria supplementare chiamata react-redux.

Aprite il terminale e digitate:

$ npm i --save react-redux

Questa esporta solamente due API, un componente <Provider/> ed una funzione connect().

Il componente Provider

Il pattern Provider  consente di passare lo stato di tutti i componenti che fanno parte dell’albero dell’applicazione quindi dimezzandoci il lavoro, notate il codice sotto:

codice demo

import { Provider } from 'react-redux'
 
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Avvolgiamo il provider intorno al componente dell’App visto che l’intera applicazione deve poter accedere allo store. In questo modo tutti i componenti hanno accesso ai dati.

Metodo connect()

Una volta messo a disposizione lo store all’applicazione dobbiamo collegare  React con questo. L’unico modo per comunicare con lo store è quello di  inviare azioni e di recuperare lo stato. In precedenza abbiamo visto come fare utilizzando i metodi store.dispatch() per inviare azioni e store.getState() per recuperare l’ultimo snapshot dello stato.Il metodo connect() ci consente di fare la medesima cosa con l’ausilio di due metodi denominati mapDispatchToProps e mapStateToProps. Vediamo l’esempio sotto:

import {connect} from 'react-redux'
 
const AddContact = ({newContact, addContact}) => {
  return (
    <div>
      {newContact.name} <br />
      {newContact.email} <br />
      {newContact.phone} <br />
       
      Are you sure you want to add this contact?
      <span onClick={addContact}> Yes </span>
    </div>
  )
}
 
const mapStateToProps = state => {
  return {
    newContact : state.contacts.newContact
  }
}
 
const mapDispatchToProps = dispatch => {
  return {
    addContact : () => dispatch(addContact())
  }
}
 
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(AddContact)

mapStateToProps e mapDispatchToProps restituiscono entrambi un oggetto e la chiave di questo oggetto diventa una props del componente collegato. Per esempio , state.contacts.newContact è mappata a props.newContact. L’action creator addContact() viene mappato a props.addContact.

Infine la riga :

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(AddContact)

ci consente di esportare direttamente il componente AddContact. Questa ci fornisce addContact e newContact come props per il componente <AddContact/>.

Come connettere React e Redux

Bene una volta installato la libreria react-redux aprite il file src/index.js per avvolgere, come detto in precedenza, il Provider intorno al componente radice App:

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import App from './App';
import configureStore from './store/createStore';

const store = configureStore();

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>
    ,
    document.getElementById('root'));

Connettere i connettori di React a Redux

La funzione di connessione connect()  consente l’associazione del contenitore di React a Redux. Quindi questa verrà utilizzata per :

  1. Sottoscrizione dello store e mapping dello stato alla props
  2. Invio azioni e mappare i callback di dispatch alle props

Una volta collegata l’applicazione a Redux , è possibile utilizzare this.props per accedere allo stato attuale e spedire azioni. Vediamo quindi come funziona il componente AddContact. Questo mi deve inviare tre azioni ed ottenere lo stato di due proprietà dallo store:

AddContact.js

Innanzitutto importate connect in AddContact.js

import {connect} from 'react-redux';

quindi realiziamo i  metodi mapStateToProps e mapDispatchToProps all’interno del costrutture della classe ovviamente.

function mapStateToProps(state) {
    return {
 
        isHidden : state.ui.isAddContactFormHidden,
        newContact: state.contacts.newContact
    }
}
 
function mapDispatchToProps(dispatch) {
    return {
        onFormSubmit: (newContact) => {
           dispatch(addContact(newContact));
        },
        onInputChange: (name,value) => {
     
            dispatch(handleInputChange(name,value));
        },
 
        onToggle: () => {
            dispatch(toggleContactForm());
        }
    }
}

mapStateToProps riceve lo stato dallo store come argomento. Restituisce un oggetto che descrive come l stato dello store viene mappatonel vostro props. mapDispatchToProps restituisce un oggetto simile il quale descrive in che modo vengono mappate le azioni dispatch sulla props.

In fine utilizziamo connect per associare il componente AddContact alle due funzioni come di seguito:

export default connect(mapStateToProps, mapDispatchToProps) (AddContact)

Aggiornare i componenti del contenitore per utilizzare i props

Le  props del componente sono in grado ora  di leggere lo stato dallo store e dalle azioni dispatch. Quindi aggiorniamo il componente modificando la logica dei metodi handleInputChange , handleSubmit e showAddContactBox come segue:

showAddContactBox() {
    const { onToggle }  = this.props;
    onToggle();
}
 
handleInputChange(event) {
    const target = event.target;
    const value = target.value;
    const name = target.name;
 
    const { onInputChange } = this.props;
    onInputChange(name,value); 
}
 
handleSubmit(e) {
    e.preventDefault();
    this.props.onToggle();
    this.props.onFormSubmit();
}

Una volta, quindi, definiti i metodi del gestore handler non ci resta aggiorante il metodo render() inserendo un istruzione condizionale che ci mostri o meno il modulo contatti:

render() {
    return(
        <div>         
            { this.props.isHidden === false ? this.renderForm(): <button onClick={this.showAddContactBox} className="btn"> Add Contact </button>}
        </div>
    )
}

Se isHidden è false ,il modulo viene eseguito. In caso contrario viene mostrato un pulsante.

Visualizzare i contatti

La parte più impegnativa è stata conclusa, ora non ci resta che visualizzare i contatti come un elenco. Modifichiamo containers/Contacts.js come segue:

import React, { Component } from 'react';
import { connect } from 'react-redux'; 
/* Component import omitted for brevity */
 
class Contact extends Component {
 
  constructor(props) {
    super(props);
    this.returnContactList = this.returnContactList.bind(this);
  }
 
   returnContactList() {
    return this.props.contactList;
  }
 
 
  render() {
 
    
    return (
        <div>
            <br />
            <AddContact/>
            <br />
          <ContactList contactList= {this.returnContactList()} />
        </div>
    );
  }
}
 
function mapStateToProps(state) {
  return {
    contactList : state.contacts.contactList,
     
  }
}
 
 
export default connect(mapStateToProps, null) (Contact);

La funzione mapStateToProps mappa l’oggetto dello store ai props contactList. Quindi ho utilizzato connect per associare il valore di props al componente Contacts. Il secondo argomento di connect è null , perchè non abbiamo azioni da inviare al momento.

Dichiarare le azioni come costanti

Ho ribadito più volte che le proprietà type sono stringhe e come tali sono soggette ad errori di battitura. Quindi è una best practice dichiararle come costanti, create una cartella:

mkdir -p src/costants

ora aprite la cartella appena creata e createvi il file action-type.js:

export const ADD_CONTACT = "ADD_CONTACT";
export const HANDLE_INPUT_CHANGE ="HANDLE_INPUT_CHANGE";
export const TOGGLE_CONTACT_FORM ="TOGGLE_CONTACT_FORM"

aprite il file actions/index.js ed aggiornate come segue:

import {ADD_CONTACT} from '../costants/action-type';
import { HANDLE_INPUT_CHANGE } from '../costants/action-type';
import { TOGGLE_CONTACT_FORM } from '../costants/action-type';

export const addContact = () => {
    return {
        type: ADD_CONTACT

    }
}

export const handleInputChange = (name, value) => {
    return {

        type: HANDLE_INPUT_CHANGE,
        payload: { [name]: value }
    }
}

export const toggleContactForm = () => {
    return {
        type: TOGGLE_CONTACT_FORM
    }
}

come vedete ho importato le costanti create in precedenza ed anzi che utilizzare le proprietà type come stringhe utilizzo le costanti ciò mi consente un auto complete riducendo il rischio di commettere errori.

 

 

 

Lucio Ticali

Chi è ?

Lucio Ticali è un Web & App developer con la passione per il web marketing,si occupa anche di tecniche di indicizzazione del sito e di promozione dello stesso (SEO e SEM).

Lascia la tua opinione