Übersicht: React

Setup mit Vite:

// Projekt-Ordner anlegen
mkdir my-project

// Unterornder für Front- und Backend anlegen
cd my-project
mkdir frontend
mkdir backend

// react-projekt mit vite initialisieren
npm create vite@latest frontend

// -> Installer-Anweisungen folgen

// dependencies installieren
cd frontend
npm install

// dev srever starten
npm run dev

// projekt bauen und buil previewen
npm run build
nmp run preview

weitere mögliche Schritte im Projekt:

Mehr Infos dazu in meinem Vite-Artikel.

Komponenten:

  • Namen werden PascalCase geschrieben
  • müssen genau 1 Element zurückgeben (dieses Element kann mehrere Kind-Elemente enthalten) => <></> (= Fragment)
  • Best-Practice: Ablage jeweils in einer eigenen .jsx, die identisch zur Komponente benannt ist

JSX:

REACT verwendet JSX, das ist Javascript, das wie HTML aussieht. Es gibt einige Unterschiede zu echtem HTML:

  • Styling von Elementen über „className=‘ ‚ „, statt über „class=‘ ‚ „
  • in <label> muss „htmlFor“ statt „for“ genommen werden
  • in <input> muss „defaultValue“ statt „value“ verwendet werden
  • jeder return darf nur 1 Element zurückgeben !=> Inhalt wrappen in <div></div> oder <></>
  • JS-Code/Template Literals müssen in {} stehen
  • default-Einheit für Zahlen ist px, wenn kenn string mit anderer Einheit angegeben ist
  • inline-CSS sollte vermieden werden

Patterns für Bedingungen:

  • if…return… => für early exits oder klare mehrzeilige Blöcke
  • Bedingung ? A : B => schnelles tooglen
  • Bedingung ?? (<div>…</div>) => div nur zeigen wenn Bedingung true
  • switch() case => für mehr als zwei Optionen

Anlegen von Komponenten:

// Deklaration,  z.B. in ./src/components/Header.jsx
const Header = () => (<header>...some content...</header>)

// oder (don't do):
function Header() {
  return (<header>...some content...</header>)
}
// Verwendung,  z.B. in ./App.jsx
import Header from "./Header"
//oder (better):
import Header from "./Header.jsx"

const App = ()=> (
  <div id="wrapper">
    <Header />
    ...
  </div>
)

Styling von Komponenten: (Verwendung von className)

// CSS-Klassen
import "./style.css".  // importiert .css direkt in js-file

const NavBar = () => (<nav className="cusom-class tailwind-class">...</nav>)

// inline CSS (don't do!)
const NavBar = () => {
  return <nav style={{background:"red", border:"1px solid green"}}>...</nav>
}

Übergabe von Properties:

// Deklaration der Komponente in PageHeading.jsx
const PageHeading = (props) => (
  <>
    <h1>{props.title}</h1>
    <h3>Author: {props.name}</h3>
  </>
)

// oder destructured:
const PageHeading = ({title,name}) => (
  <>
    <h1>{title}</h1>
    <h3>Author: {name}</h3>
  </>
)
// Verwendung der Komponente, z.B. in App.jsx
import PageHeading from "./componets/PageHeading"

const book = {
  title: "Harry Potter",
  author: "J.K.Rowling"
}

...
<PageHeading {...book}/>
...
// oder:
...
<PageHeading title=book.title name=book.author/>
...
// oder:
// (ABER! = never do):
// hierbei wird nur die Referenz übergeben
// changes in data -> changes original book
...
<PageHeading data={book}/> 
...

Daten können über Properties nur an Kind-Elemente übergeben werden, nicht in die andere Richtung.

Rendern von Listen:

// Erstellen eine bsp-Objektes mit Daten
const starters = [
  { id: 1,
    name: 'Bulbasaur',
    sprite:'https://.../sprites/pokemon/1.png',
    type: 'Grass · Poison',
  },
  { id: 4,
    name: 'Charmander',
    sprite:'https://.../sprites/pokemon/4.png',
    type: 'Fire',
  },
  { id: 7,
    name: 'Squirtle',
    sprite:'https://../sprites/pokemon/7.png',
    type: 'Water',
  },
];

// Deklarieren einzelner Komopnenten
const PokeCard = ({ poke }) => (
  <article>
    <img src={poke.sprite} alt={poke.name} />
    <h4>{poke.name}</h4>
    <p>{poke.type}</p>
  </article>
);

const PokeGrid = ({ pokemon }) => (
  <section>
    {pokemon.map((p) => (
      <PokeCard key={p.id} poke={p} />
    ))}
  </section>
);

// Verwendung der Komponenten
const App = () => (
  <main>
    <h3>
      First-generation Starters
    </h3>
    {/* List rendered via .map() */}
    <PokeGrid pokemon={starters} />
    {/* Quick example: show only water types */}
    <h3>Water squad</h3>
    {starters
      .filter((p) => p.type.includes('Water'))
      .map((p) => (
        <span key={p.id}>
          {p.name}
        </span>
      ))}
  </main>
);

Jedes Listen-Element benötigt einen eindeutigen key, damit react die Elemente zuordnen kann.

Events handeln:

// Definieren der Handler
const App = () => {
  const handleClick = (e) => {
    console.log('Clicked — SyntheticEvent:', e);
  };

  const handleKeyDown = (e) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      alert(`Pressed Enter: "${e.target.value}"`);
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault(); // stop page reload
    const input = e.target.elements.message.value; 
    alert(`Form submitted 🎉: ${input}`);
  };

// Verknüpfung der Handler
  return (
    <main>
      <div>
        
        <button onClick={handleClick}>
          Click me
        </button>
        
        <input
          type="text"
          placeholder="Type and press Enter"
          onKeyDown={handleKeyDown}/>
          
        <form onSubmit={handleSubmit}>
          <input
            name="message"
            type="text"
            placeholder="Type, hit Enter or Btn"/>
          <button>Submit form</button>
        </form>
        
      </div>
    </main>
  );
};

Events werden über camelCase-Bezeichner dem JSX-Element hinzugefügt. REACT führt die referenzierte Funktion in einem syntetischen Event auf.

Hooks:

useState()

// Syntax:
const [value, setterFncForVal] = useState(initialValue)

// Ändern des Wertes
setterFncForVal(newValue) // direkte Zuweisung neuer Wertes (nur wenn vorheriger Zustand unrelevant nutzen)
setterFncForVal((oldValue) => oldValue +1 ) // callback mit geplanten Werten


Beispiel: Counter

import { useState } from 'react';

const App = ()=> {
  const [counter, setCounter] = useState(0);                // Initialwert 0
 
  return (
    <>
      <button onClick={()=> setCounter((prev) => prev +1)}>+</button>       
      <span>{counter}</span>
      <button onClick={()=> setCounter((prev) => prev -1)}>-</button>
    </>
  )  
}


Beispiel: String ändern

// onClick
const App = ()=> {
  const [message, setMessage] = useState("Hello");
 
  return (
    <>
      <span>{message}</span>
      <button onClick={()=> setMessage("World")}>change Message</button>       
    </>
  )  
}

// onChange
const App = ()=> {
  const [text, setText] = useState("");
 
  return (
    <>
      <input value={text} onChange={(e)=> setText(e.target.value)} />  
      <span>{text}</span>     
    </>
  )  
}


Beispiel: Checkbox

const MyCheckbox = () => {
  const [liked, setLiked] = useState(true);

  return (
    <>
      <input type="checkbox" checked={liked} onChange={(e)=>setLike(e.target.checked} />
      <span>I liked this</span>
      <p> You {liked ? 'liked' : 'did not like'} this. </p>
    </>
  )
}


Beispiel: Formular

const Form = () => {
  const [form, setForm] = useState({                          // Initialwert
    firstName: '',
    lastName: '',
    email: '',
    message: '',
  });

  const handleChange = (e) => {
    setForm({...form, [e.target.name]: e.target.value});      // übernimm Daten, dann überschreib Element
  };

  return (
  <>
    <form>
      <input
        name="firstName"
        value={form.firstName}
        onChange={handleChange}
      />
      <input
        name="lastName"
        value={form.lastName}
        onChange={handleChange}
      />
      <input
        name="email"
        value={form.email}
        onChange={handleChange}
        type="email"
      />
      <textarea
        name="message"
        value={form.message}
        onChange={handleChange}
      />
    </form>
    <p>
      <strong>Preview:</strong>
    </p>
    <p>
      {form.firstName} {form.lastName}
    </p>
    <p>{form.email}</p>
    <p>{form.message}</p>
  </div>
  </>
  );
};

? verwendet um Komponenten (und ihre Kinder) dynamisch zu Re-Rendern, z.B. bei Änderung der verwendeten Daten oder User-Aktion

= nimmt als Argument den Initialwert (beliebiger Datentyp)

= liefert Array mit aktuellem Wert und zugehöriger Setter-Funktion (die auch das Re-Rendering der Komponente triggert)

Weitergabe des State-Wertes:

  • der State-Wert kann (über props) nur nach unten weiter gegeben werden

Hinweise:

  • InitialWert sollte nur über setterFnc geändert werden
  • muss in der Komponente importiert werden, in der er verwendet werden soll
  • bei mehrfacher Verwendung einer Komponente ist jeder useStateWert unabhängig und isoliert (bis man die Komponenten verknüpft)
// direktes Setzen des Wertes
setCounter(counter + 1);
setCounter(counter + 1);
setCounter(counter + 1);    
// = 1, weil jede Zeile den gleichen, unveränderten // Wert 0 nutzt


// Setzen per CallBack-Fuunktion
setCounter((prev) => prev + 1);
setCounter((prev) => prev + 1);
setCounter((prev) => prev + 1);
// = 3, weil hier jeweils der fürs nächte Rendering // vorgemerkte Wert verwendet wird
  • ohne callback-Funktion steht der geänderte Wert erst nach dem nächsten Rendering zur Verfügung, d.h. nach dem abgeschlossen Durchlauf der Funktionen im Scope (Komponente)
  • Objekte sollten niemals direkt mutiert werden, nur über Setter-Funktion

Nutzung mit Formularen:

  • jedem Formelement ein name geben
  • jedem Formelement einen Eventhandler und Funktion übergeben
  • im Handler name und value aus event-target holen
  • neuen State-Wert als Objekt aus Spread-Operator von aktuellem Wert und aktualisierter Instanz erzeugen (Beim Setzen eines Objektes muss immer das komplette Objekt übergeben werden)
  • Link: Controlled Inputs
  • Link: Example Form

#TODO