Gestione dello state

Intermediate

Man mano che la tua applicazione cresce, è utile essere più intenzionali su come viene organizzato il tuo state e come i dati fluiscono tra i tuoi componenti. Lo state ridondante o duplicato è una fonte comune di bug. In questo capitolo, imparerai come strutturare bene il tuo state, come mantenere la logica di aggiornamento dello state manutenibile e come condividere lo state tra componenti distanti.

Reagire all’input con lo state

Con React, non modificherai direttamente l’interfaccia utente dal codice. Ad esempio, non scriverai comandi come “disabilita il pulsante”, “abilita il pulsante”, “mostra il messaggio di successo”, ecc. Invece, descriverai l’interfaccia utente che desideri vedere per i diversi stati visivi del tuo componente (“state iniziale”, “state di digitazione”, “state di successo”) e quindi attiverai i cambiamenti di state in risposta all’input dell’utente. Questo è simile al modo in cui i designer pensano all’interfaccia utente.

Ecco un modulo di quiz costruito con React. Nota come utilizza la variabile di state status per determinare se abilitare o disabilitare il pulsante di invio e se mostrare invece il messaggio di successo.

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  if (status === 'success') {
    return <h1>That's right!</h1>
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={
          answer.length === 0 ||
          status === 'submitting'
        }>
          Submit
        </button>
        {error !== null &&
          <p className="Error">
            {error.message}
          </p>
        }
      </form>
    </>
  );
}

function submitForm(answer) {
  // Pretend it's hitting the network.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== 'lima'
      if (shouldError) {
        reject(new Error('Good guess but a wrong answer. Try again!'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

Ready to learn this topic?

Leggi Reagire all’input con lo state per imparare come affrontare le interazioni con una mentalità basata sullo state.

Read More

Scelta della struttura dello state

Strutturare bene lo state può fare la differenza tra un componente piacevole da modificare e debuggare e uno che è una fonte costante di bug. Il principio più importante è che lo state non dovrebbe contenere informazioni ridondanti o duplicate. Se c’è state non necessario, è facile dimenticarsi di aggiornarlo e introdurre bug!

Ad esempio, questo modulo ha una variabile di state fullName ridondante :

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + ' ' + lastName);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
    setFullName(firstName + ' ' + e.target.value);
  }

  return (
    <>
      <h2>Let’s check you in</h2>
      <label>
        First name:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        Your ticket will be issued to: <b>{fullName}</b>
      </p>
    </>
  );
}

Puoi rimuoverla e semplificare il codice calcolando fullName durante il rendering del componente:

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  const fullName = firstName + ' ' + lastName;

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
  }

  return (
    <>
      <h2>Let’s check you in</h2>
      <label>
        First name:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        Your ticket will be issued to: <b>{fullName}</b>
      </p>
    </>
  );
}

Questo potrebbe essere un cambiamento di poco conto, ma molti bug nella applicazioni React vengono risolti in questo modo.

Ready to learn this topic?

Leggi Scegliere la struttura dello state per imparare come progettare la forma dello state per evitare bug.

Read More

Condivisione dello state tra i componenti

A volte, desideri che lo state di due componenti cambi sempre insieme. Per farlo, rimuovi lo state da entrambi i componenti, spostalo al loro genitore comune più vicino e quindi passalo loro tramite le props. Questo è noto come “alzare lo state” ed è una delle cose più comuni che farai scrivendo codice React.

In questo esempio, solo un pannello dovrebbe essere attivo alla volta. Per ottenere questo risultato, anziché mantenere lo state attivo all’interno di ciascun pannello individuale, il componente genitore tiene traccia dello state e specifica le props per i suoi figli.

import { useState } from 'react';

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel
        title="About"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel
        title="Etymology"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

function Panel({
  title,
  children,
  isActive,
  onShow
}) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={onShow}>
          Show
        </button>
      )}
    </section>
  );
}

Ready to learn this topic?

Leggi Condivisione dello State tra i Componenti per imparare come alzare lo state e mantenere i componenti sincronizzati.

Read More

Preservazione e ripristino dello state

Quando si ridisegna un componente, React deve decidere quali parti dell’albero mantenere (e aggiornare) e quali parti scartare o ricreare da zero. Nella maggior parte dei casi, il comportamento automatico di React funziona abbastanza bene. Per impostazione predefinita, React conserva le parti dell’albero che “corrispondono” all’albero dei componenti precedentemente renderizzato.

Tuttavia, a volte questo non è ciò che si desidera. In questa chat app, digitare un messaggio e quindi passare a un altro destinatario non resetta l’input. Ciò può portare l’utente a inviare accidentalmente un messaggio alla persona sbagliata:

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat contact={to} />
    </div>
  )
}

const contacts = [
  { name: 'Taylor', email: 'taylor@mail.com' },
  { name: 'Alice', email: 'alice@mail.com' },
  { name: 'Bob', email: 'bob@mail.com' }
];

React ti consente di sovrascrivere il comportamento predefinito e forzare un componente a ripristinare il suo state passando una key, diversa, ad esempio <Chat key={email} />. Ciò indica a React che, se il destinatario è diverso, il componente Chat deve essere considerato un componente diverso e che deve essere ricreato da zero con i nuovi dati (e l’interfaccia utente come gli input). Ora, passando da un destinatario all’altro, l’input viene ripristinato, anche se si sta renderizzando lo stesso componente.

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.email} contact={to} />
    </div>
  )
}

const contacts = [
  { name: 'Taylor', email: 'taylor@mail.com' },
  { name: 'Alice', email: 'alice@mail.com' },
  { name: 'Bob', email: 'bob@mail.com' }
];

Ready to learn this topic?

Leggi Preservare e Reimpostare lo State per apprendere la durata dello state e come controllarlo.

Read More

Estrarre la logica dello state in un reducer

I componenti con molte aggiornamenti dello state distribuiti su numerosi gestori di eventi possono diventare complessi. Per questi casi, puoi consolidare tutta la logica di aggiornamento dello state al di fuori del tuo componente in una singola funzione, chiamata “riduttore” (reducer). I tuoi gestori di eventi diventano concisi perché specificano solo le “azioni” dell’utente. In fondo al file, la funzione del riduttore specifica come lo state dovrebbe aggiornarsi in risposta a ciascuna azione!

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false }
];

Ready to learn this topic?

Leggi Estrarre la Logica dello State in un Reducer per apprendere come consolidare la logica nel reducer della funzione.

Read More

Passaggio di dati in profondità con il context

Di solito, passerai le informazioni da un componente genitore a un componente figlio tramite le props. Tuttavia, il passaggio delle props può diventare scomodo se devi passare una determinata prop attraverso molti componenti o se molti componenti necessitano delle stesse informazioni. Il context consente al componente genitore di rendere disponibili alcune informazioni a tutti i componenti nell’albero sottostante, indipendentemente dalla profondità, senza passarle esplicitamente tramite le props.

In questo esempio, il componente Heading determina il livello del titolo “chiedendo” al componente Section più vicino il suo livello. Ogni Section tiene traccia del proprio livello chiedendo al Section genitore e aggiungendo uno. Ogni Section fornisce informazioni a tutti i componenti sottostanti senza passare le props in modo esplicito: lo fa attraverso il context.

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section>
      <Heading>Title</Heading>
      <Section>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Section>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Section>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

Ready to learn this topic?

Leggi Passaggio di Dati in Profondità con il Context per apprendere come utilizzare il context come alternativa al passaggio di props.

Read More

Scalare con reducer e context

I reducer ti permettono di consolidare la logica di aggiornamento dello state di un componente. Il contesto ti permette di passare informazioni in profondità ad altri componenti. Puoi combinare i reducer e il context insieme per gestire lo state di una schermata complessa.

Con questo approccio, un componente genitore con uno state complesso lo gestisce con un riduttore. Altri componenti situati in qualsiasi punto dell’albero possono leggere il suo state tramite il contesto. Possono anche inviare azioni per aggiornare tale state.

import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';

export default function TaskApp() {
  return (
    <TasksProvider>
      <h1>Day off in Kyoto</h1>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}

Ready to learn this topic?

Leggi Scalare con Reducer e Context per apprendere come la gestione dello state si scala in un’applicazione in crescita.

Read More

Cosa fare dopo?

Vai su Reacting to Input with State per iniziare a leggere questo capitolo pagina per pagina!

Oppure, se sei già familiare con questi argomenti, perché non leggere su Soluzioni alternative?