State (Komponentenzustand)

Themen

  • State-Grundlagen
  • Input-State
  • Immutability - Arrays und Objekte im State

State

Komponenten können einen internen Zustand (State) haben

Auf den State kann im Template verwiesen werden

Die Anzeige wird automatisch aktualisiert, wenn Teile des States neu gesetzt werden

State Hook

State Hook in Funktionskomponenten:

import { useState } from 'react';

function Slideshow() {
  const [imgId, setImgId] = useState(0);
  const [imgWidth, setImgWidth] = useState(300);
  const [imgHeight, setImgHeight] = useState(200);

  return ...
};

State Hook

const [imgId, setImgId] = useState(0);

useState kann in der Komponentenfunktion (wiederholt) aufgerufen werden, um State-Einträge zu deklarieren

Parameter: ursprünglicher State

Array von Rückgabewerten: aktueller State, Setter-Funktion

Verwenden des minimalen States

Richlinie: Verwende den minimalen State (d.h. keine redundanten Daten)

Weitere Daten können aus dem State abgeleitet werden

Beispiele:

  • Slideshow: speichere die Bild-ID - die Bild-URL kann davon abgeleitet werden
  • Todos: speichere ein Array von Todos mit ihrem Status - die Anzahl an erledigten oder nicht-erledigten Todos kann davon abgeleitet werden
  • Textfeld: speichere den Inhalte - der Gültigkeitsstatus kann davon abgeleitet werden

Ãœbung: Slideshow (Teil 1)

Implementiere die zuvor gesehene Slideshow-Komponente erneut; versuche, nicht auf den bisherigen Code zu blicken

  • zeige Bilder wie z.B. https://picsum.photos/300/200?image=0
  • Buttons für vorwärts und zurück
  • Button für zurück zum Start
  • verhindere, dass ins negative gezählt wird

Ãœbung: Slideshow (Teil 2)

Füge weitere Features hinzu, z.B.:

  • Button für zufälliges Bild
  • Buttons zum Ändern der Breite des Bildes (z.B. Umschalten zwischen 300x200 und 400x200)
  • kleines Vorschaubild für das vorherige und nächste Bild
  • ... (deine eigene Ideen)

vermeide vorerst folgendes:

  • Arrays / Objekte im State
  • Verwenden von Inputs (Textfeldern)
  • Verwenden von Timern (setTimeout, setInterval)
  • Styling

Input State

Input State

ein "uncontrolled input" in React:

<input />

React kennt den Inhalt des Inputs nicht und kann ihn nicht ändern

Input State

So können wir den Wert eines Inputs im State erfassen:

const [inputText, setInputText] = useState('');
<input
  value={inputText}
  onChange={(event) => setInputText(event.target.value)}
/>

Input State

Erklärung:

value={inputText}

bindet vom State auf den Wert des Inputs

onChange={(event) => setInputText(event.target.value)}

aktualisiert den State, wenn sich der Wert des Inputs ändert

Input State

warum event.target.value?

  • event.target repräsentiert das Input-Element
  • event.target.value ist der neue Wert des Input-Elements

Input State

Beispiel: Input, bei dem auch die Textlänge angezeigt wird:

function App() {
  const [text, setText] = useState('');

  return (
    <div>
      <input
        value={text}
        onChange={(event) => setText(event.target.value)}
      />
      <button onClick={() => setText('')}>clear</button>
      <p>This string has {text.length} characters.</p>
    </div>
  );
}

Input State

andere Möglichkeiten zum Arbeiten mit Input-State:

  • Verwalten von mehreren Inputs über ihre Namen (siehe React Dokumentation)
  • Auslesen von Input-Inhalten nur beim Submit eines Formulars (via FormData)
  • Verwenden einer externen Library (z.B. react-hook-form, formik)

Input-Typen

Input-Typen

  • text
  • textarea
  • checkbox
  • dropdown
  • numerisch
  • ...

Input-Typen

Text und Textarea:

<input
  value={title}
  onChange={(event) => setTitle(event.target.value)}
/>

Input-Typen

Checkbox:

<input
  type="checkbox"
  checked={accept}
  onChange={(event) => setAccept(event.target.checked)}
/>

Numerische Inputs

Grundlegender Rat für numerische Inputs:

speichere den Inhalt als String (nicht als Zahl)

Grund: mögliche Inhalte eines Numerischen Inputs (während der Benutzer tippt):

""
"-"
"-3"
"-3."
"-3.0"

Numerische Inputs

Beispiel für numerischen Input mit direkten "Auswirkungen":

https://codesandbox.io/s/numeric-input-direct-results-5vde88

Andere Input-Typen

siehe https://reactjs.org/docs/forms.html

Ãœbung: Newsletter-Formular

Erstelle eine Komponente namens NewsletterSignup mit drei State-Einträgen:

email, repeatEmail, acceptTerms

Demo einer vollständigen Version: https://codesandbox.io/s/newsletter-form-pvgs6l

Bemerkung: aktivieren / deaktivieren eines buttons in JSX: <button disabled={...}>...</button>

Ãœbung: Newsletter-Formular

Extra: füge ein Dropdown hinzu, mit dem eine Sprache für den Newsletter ausgewählt werden kann

Immutable State

Immutable State

Immutability: Wichtiges Konzept in der funktionalen Programmierung und bei React

Daten werden nicht direkt abgeändert - stattdessen werden neue Daten auf Basis der alten generiert

Immutable State

Wenn unser State Arrays oder Objekte enthält, könnten wir versuchen, diese direkt abzuändern

Das sollten wir nicht tun - React bemerkt diese Änderungen nicht und aktualisiert die Ansicht eventuell nicht

Objekte im State sollten als unveränderlich erachtet werden

Immutable State

Wenn ein State-Setter aufgerufen wird:

React vergleicht die alte State-Referenz mit der neuen State-Referenz

Wenn die Referenz die gleiche ist, wird die Komponente nicht neu gerendert

Immutable State

Demo: https://codesandbox.io/s/immutable-state-demo-r2x1i

Datenverwaltung ohne Mutationen

Datenverwaltung ohne Mutationen

Möglichkeiten, um aus bestehenden Daten aktualisierte Daten zu erhalten:

  • abändern der ursprünglichen Daten
  • erstellen neuer - abgeleiteter - Daten basieren aus den ursprünglichen Daten

Datenverwaltung ohne Mutationen

Themen:

  • Hinzufügen von Properties zu einem Objekt
  • Ändern einzelner Properties in einem Objekt
  • Hinzufügen von Elementen zu einem Array
  • Ändern von Elementen in einem Array
  • Entfernen von Elementen aus einem Array

Datenverwaltung ohne Mutationen

Mechanismen:

  • Spread-Syntax (...)
  • besondere Array-Methoden (.map, .filter)

Datenverwaltung ohne Mutationen

Spread Syntax für Arrays:

const numbers = [1, 2, 3];
const moreNumbers = [...numbers, 4, 5, 6];
// moreNumbers: [1, 2, 3, 4, 5, 6]

Datenverwaltung ohne Mutationen

Spread Syntax für Objekte:

const person = {
  firstName: 'Joe',
  lastName: 'Doe',
  age: 31,
};
const newPerson = { ...person, email: 'j@d.com', age: 32 };
// {firstName: 'Joe', lastName: 'Doe', email: 'j@d.com', age: 32}

Datenverwaltung ohne Mutationen

die .map-Methode:

  • bestimmt für jeden Eintrag in einem Array einen neuen, abgeletiteten, Eintrag
  • verwendet eine Funktion, um die "Transformation" jedes Eintrags anzugeben
  • gibt ein neues Array mit diesen Einträgen zurück
const myNumbers = [1, 2, 3, 4];

const tripledNumbers = myNumbers.map((n) => 3 * n);
// [3, 6, 9, 12]
const squareRoots = myNumbers.map(Math.sqrt);
// [1, 1.41, 1.73, 2]

Datenverwaltung ohne Mutationen

die .filter-Methode:

  • erstellt ein neues Array, in dem nur bestimmte Einträge im Array verbleiben
  • verwendet eine Funktion, um Einträge auf ein bestimmtes Kriterium zu testen
  • gibt ein neues Array zurück
const myNumbers = [1, 2, 3, 4];

const evenNumbers = myNumbers.filter((n) => n % 2 === 0);
// [2, 4]

Datenverwaltung ohne Mutationen

Mechanismen:

  • Hinzufügen von Properties zu einem Objekt: Spread Syntax
  • Ändern einzelner Properties in einem Objekt: Spread Syntax
  • Hinzufügen von Elementen zu einem Array: Spread Syntax
  • Ändern von Elementen in einem Array: map
  • Entfernen von Elementen aus einem Array: filter

Ãœbungen

Arbeiten mit Todos

Aufgabe: Vervollständige Funktionen, die Todos verwalten (ohne Mutationen)

Codesandbox Ausgangspunkt

Ãœbungen

Ãœbung: Erstelle die folgende reine Funktion, die ein Todo verarbeitet:

const todo1 = { id: 1, title: 'foo', completed: false };

function changeTitle(todo, newTitle) {
  // TODO: FINISH IMPLEMENTATION HERE
}

const todo2 = changeTitle(todo1, 'bar');
console.log(todo2);
// { id: 1, title: 'bar', completed: false}

Ãœbungen

Lösung:

function changeTitle(todo, newTitle) {
  return { ...todo, title: newTitle };
}

Ãœbungen

Ãœbungen: Erstelle reine Funktionen, die ein Array von Todo-Objekten behandeln:

const todos = [
  { id: 1, title: 'foo', completed: false },
  { id: 5, title: 'bar', completed: true },
  { id: 7, title: 'baz', completed: false },
];

Ãœbungen

Vervollständige diesen Code, sodass er ein neues, unerledigtes Todo mit einem gegebenen Titel und einer neuen ID hinzufügt:

function addTodo(todos, newTitle) {
  let maxId = 0;
  for (let todo of todos) {
    maxId = Math.max(maxId, todo.id);
  }
  // TODO: FINISH IMPLEMENTATION HERE
}

// add a todo with title 'qux'
const todosA = addTodo(todos, 'qux');
console.log(todosA);

Ãœbungen

mögliche Lösung:

function addTodo(todos, newTitle) {
  let maxId = 0;
  for (let todo of todos) {
    maxId = Math.max(maxId, todo.id);
  }
  return [
    ...todos,
    { id: maxId + 1, title: newTitle, completed: false },
  ];
}

Ãœbungen

Vervollständige diesen Code, sodass er ein bestimmtes Todo anhand der ID löscht:

function removeTodo(todos, id) {
  // TODO: FINISH IMPLEMENTATION HERE
}

const todosB = removeTodo(todos, 1);
console.log(todosB);

Ãœbungen

mögliche Lösung:

function removeTodo(todos, id) {
  return todos.filter((todo) => todo.id !== id);
}

Ãœbungen

Vervollständige diesen Code, sodass er den completed-Zustand eines bestimmten Todos ändert

function changeTodoCompleted(todos, id, completed) {
  // TODO: FINISH IMPLEMENTATION HERE
}

// change the completed status of todo 1 to true
const todosC = changeTodoCompleted(todos, 1, true);
console.log(todosC);

Ãœbungen

mögliche Lösung:

function changeTodoCompleted(todos, id, completed) {
  return todos.map((todo) => {
    if todo.id === id {
      return {...todo, completed: completed}
    } else {
      return todo;
    }
  })
}

Ãœbungen

Newsletter-Anmeldung von zuvor: Speichere den State in einem einzelnen Objekt mit drei Einträgen

const [data, setData] = useState({
  email: '',
  repeatEmail: '',
  acceptTerms: false,
});

Todos: Schreibe weitere reine Funktionen, die mit Todos arbeiten:

  • changeTodoTitle
  • toggleTodo (umschalten zwischen erledigt und nicht-erledigt)
  • updateTodo (ändern von title und completed in einem Aufruf)
  • removeAllCompletedTodos
  • ...