git clone https://github.com/nilshartmann/react-training
cd react-training/blog-example/workspace
npm install
Slides: https://nils.buzz/techlab
oder: react-training/2019_hands_on.html
https://nilshartmann.net / @nilshartmann
Freiberuflicher Entwickler, Architekt, Trainer aus Hamburg
Java
JavaScript, TypeScript
React
Single-Page-Anwendungen
GraphQL
          
        git clone https://github.com/nilshartmann/react-training
cd react-training/blog-example/workspace
npm install
npm start
Aufrufen: http://localhost:3000
Slides: https://nils.buzz/techlab
Im Verzeichnis blog-example/workspace ist schon alles vorbereitet
Du kannst dort mit npm install und npm start die (leere) Anwendung starten
            In der Datei App.js kannst Du deine Anwendung implementieren 
Dazu bitte in
            index.js die App-Komponente importieren (siehe Kommentare dort)
          
Wenn Du Änderungen machst, wird die Anwendung automatisch gebaut und im Browser aktualisiert
cd code/blog-example/workspace
            npm start
            
        In beispiele befinden sich mehrere Stände der fertigen Anwendung. Diese kannst Du zum Beispiel
            als Hilfe bei Problemen verwenden.
01_addpost-form: Nur das Formular zum Eingeben eines neuen Blog Posts. Du siehst hier die useState-Verwendung.
02_blog-app: Eingabeformular und darunter die Liste mit den Blog Posts, aber ohne Server-Calls. Du siehst hier, wie zwischen Komponenten kommuniziert wird (App und AddPostForm)
03_blog-app-mit-server: Genau wie 02, nur mit Server-Calls, dh Blog Posts werden vom Server gelesen und dort gespeichert
04_blog-app-mit-router: Ähnlich Stand 03, aber die einzelnen Komponenten sind über Links erreichbar. Außerdem gibt es eine Ansicht für einen einzelnen Blog-Post
Du kannst im Team eine App ganz nach deinen Wünschen bauen 😊
Zur Inspiration hier einige Ideen:
(Du siehst, dass entspricht "zufällig" auch den Ständen im beispiele-Verzeichnis 😇)
Untersuchen der React Anwendung zur Laufzeit

Bootstrap von React Projekten
Fertige Konfiguration von Webpack, Bablel React, ...
Beispiel: npx create-react-app PROJEKTNAME
oder mit TypeScript Support: npx create-react-app PROJEKTNAME --typescript
            blog-example/workspace)HelloWorld.js
          
  import React from "react";
  
  export default function HelloWorld(props) {
    const [title, setTitle] = React.useState(props.initialTitle || "");
  
    return (
      <div>
        <input onChange={event => setTitle(event.target.value)} value={title} />
  
        <p>Eingegebener Titel: {greeting}</p>
        <button onClick={() => setTitle("")}>Clear</button>
      </div>
    );
  }
            
        
              index.html
            
  <html>
    <-- ... -->
    <body>
      
    </body>
  </html>
  
          
              index.js
            
  import React from 'react';
  import ReactDOM from 'react-dom';
  
  import HelloWorld from './HelloWorld';
  
  ReactDOM.render(<HelloWorld initialTitle="Moin moin"/>, 
    document.getElementById('root')
  );
  
          
<div><input type="text"/></div>
                    
            class-Attribut heißt className:
              
                        <h1 className="title">...</h1>
                    
            
<Counter label="Count" count={7} showValues={true} />
                    
            
const title = 'Hello, World';
<h1>{title.toUpperCase()}</h1>
                    
            
const styles = { marginLeft: '10px', border: '1px solid red' };
<h1 style={styles}>...</h1>
                    
            
function Choice() { 
  return <>
    <li>Yes</li> 
    <li>No</li>
  </>              
}  
                    
            
function ErrorMessage(props) {
  if (!props.msg) {
    return null; // oder false oder true
  }
  return Fehler: {props.msg}
;
}              
                    
            
  function MyComponent() {
    return 
      { /* hier ist javascript, deswegen block-kommentare erlaubt */ }
    ;
  }              
                      
            
            function Header(props) {
                return (
                  <h1 style={{color: props.titleColor}}>{props.title}</h1>
                );
              }
            }
            
          
// Mit Destructuring
function Header({titleColor, title}) {
    return (
      <h1 style={{color: titleColor}}>{title}</h1>
    );
  }
}
                
        
function HelloWorld(props) {
  const [title, setTitle] = React.useState(props.initialTitle);
  return <input onChange={e => setTitle(e.target.value) value={title} />;
}
                  
          use beginnen (useState,
              useEffect, ...)
            
                import React from "react";
                function HelloWorld(props) {
                  const [title, setTitle] = React.useState(props.initialTitle);
                  // ...
                }
                                  
              
                                      import React, { useState } from "react";
                      
                                      function HelloWorld(props) {
                                        const [title, setTitle] = useState(props.initialTitle);
                                        // ...
                                      }
                                                        
              React muss immer importiert werden, wenn JSX verwendet wird!)
                (https://reactjs.org/docs/hooks-rules.html)
Einschränkungen:
            Der Hooks-Mechanismus basiert intern darauf, dass React sich die Reihenfolge der
              useXyz-Aufrufe merkt!
          
              // ERLAUBT:
              function HelloWorld(props) {
                const [greeting, setGreeting] = React.useState(props.initialGreeting);
                const [name, setName] = React.useState(props.initialName);
                // ...
              }
                                
          
                                    // ERLAUBT:
                                    function HelloWorld(props) {
                                      const [greeting, setGreeting] = React.useState(props.initialGreeting);
                                      const uppercaseGreeting = greeting.toUpperCase(); 
                                      const [name, setName] = React.useState(props.initialName);
                                      // ...
                                    }
                                                      
          
                                    // VERBOTEN:
                                    function HelloWorld(props) {
                                      const [greeting, setGreeting] = React.useState(props.initialGreeting);
                                      if (greeting !== null) {
                                        const [name, setName] = React.useState(props.initialName);
                                      }
                                      // ...
                                    }
                                                      
          
                          // VERBOTEN:
                          function HelloWorld(props) {
                            const [greeting, setGreeting] = React.useState(props.initialGreeting);
                            if (greeting === null) {
                              return Please enter greeting first
;
                            }
                            // ...
                          }
                                            
        
              // VERBOTEN 
              function HelloWorld(props) {
                function initState() {
                  return React.useState(props.initialGreeting);
                }
                const [greeting, setGreeting] = initState();
              }
            
          
                // VERBOTEN (initState ist 'normale' Funktion)
                function initState() {
                  return React.useState(props.initialGreeting);
                }
                function HelloWorld(props) {
                  // wäre erlaubt, wenn initState 'useInitState' hieße
                  const [greeting, setGreeting] = initState();
                }
              
        
        "Rendern" hat leider doppelte Bedeutung!
        JSX bietet nichts für Listen
            Ausgabe typischerweise über
            Array.map()
          
Elemente einer Liste brauchen einen eindeutigen Key
const posts = [
  { id: 0, title: 'Hello World', body: 'Lorem ipsum' },
  { id: 1, title: 'React in a Nutshell', body: 'Lets get started with React' }
];
function PostList(props) {
  return {props.posts.map(post => (
    <div key={post.id}>
        <h1>{post.title}</h1>
        <p>{post.body}</p>
    </div>
  )}
}
        Zur Erinnerung: in React bauen wir Komponenten. Komponenten bestehen aus Logik, Zustand und UI (HTML-Elemente und Styling)
Ein bekanntes Muster ist, die Komponenten in zwei Arten aufzuteilen: Smart (oder Controller)- und Dumb oder (Presentation-)-Komponenten
Technisch sind die Komponenten identisch, also "normale" React-Komponenten
Nur ihre Aufgabe ist anders definiert...
Smart-Komponenten enthalten Logik und Zustand
Dumb-Komponenten sind nur zur Darstellung der Daten
Smart-Komponenten reichen Zustand in die Dumb-Komponenten. Diese zeigen den Zustand an
Smart-Komponenten reichen Callback-Funktion als Event-Handler an die Dumb-Komponenten
Wenn in Dumb-Komponenten ein Ereignis eintritt (z.B. Button-Click oder Texteingabe), wird eine Callback-Funktion aufgerufen
Die Callback-Funktion wird dann in der Smart-Komponente aufgerufen und die Verarbeitung ausgeführt
Die Smart-Komponente setzt ihren Zustand neu, und rendert sich und ihre Kinder (die Dumb-Komponenten) neu
Unsere Smart-Komponente hält eine Liste von Blog-Posts und steuert, welche Ansicht aktiv ist
Die Smart-Komponente gibt die Liste der Blog-Posts an die BlogList zum Anzeigen
Die Smart-Komponente gibt jeweils eine Callback-Funktion an die BlogList und die AddForm
function App() {
  const [posts, setPosts] = React.useState([]);
  const [view, setView] = React.useState("list");
  function addPost(newPost) {
    // Neuen Post hinzufügen
    setPosts([...posts, newPost]);
    // Wieder Liste anzeigen
    setView("list");
  }
  if (view === "list") {
    return <BlogList posts={posts} onAdd={() => setView("addForm")} />
  }
  return <AddForm onAdd={addPost} />
}            
 
          Die BlogList zeigt die übergebene Liste nur an und informiert die App, wenn auf den "Add"-Button gedrückt wurde
Die App kann dann die andere Komponente (AddForm) anzeigen
  function BlogList(props) {
    return <div>
      // ... Liste anzeigen ...
      <button onClick={props.onAdd}>Add Blog Post</button>
    </div>;
  }            
   
          Die AddForm erfasst einen neuen BlogPost und übergibt diesen der Callback-Funktion, so dass die App-Komponente ihn in die Liste der BlogPosts (State) einfügen kann
  function AddForm(props) {
    const [title, setTitle] = React.useState("");
    const [body, setBody] = React.useState("");
    function addPost() {
      const newPost = {
        title, body
      }
      // App-Komponente informieren
      props.onAdd(newPost);
    }
    return <div>
      // ... Formular rendern ...
      <button onClick={addPost}>Save Post</button>
    </div>;
  }            
   
          Der Server ist bereits fertig. Zum Starten:
                  cd react-training/blog-example/backend
                  npm start
                
          Der Server ist über Port 7000 erreichbar
Zum Testen: http://localhost:7000/posts
Die Endpunkte:
GET /posts Alle Blog Posts abfragen
GET /posts?short: Alle Blog Post IDs und deren Titel
GET /posts/:id Einen einzelnen Blog Post lesen
            POST /posts Einen neuen Blog Post anlegen. Als Payload/Body muss ein Objekt mit
            title und string übertragen werden. Als Ergebnis wird der
            komplette, neue Blog Post (inklusive ID) zurück gegeben
          
DELETE /posts/:id Einen Blog-Post löschen
Außerdem kann allen Requests der URL Parameter?slow übergeben werden, um die Antwort künstlich zu verlangsamen (wenn ihr z.B. mit Wartezuständen arbeiten wollt)
React macht keine Angabe, wie Server-Calls (technisch) gemacht werden
Häufig in React verwendet: fetch API
Daten lesen per GET
          // Für GET Zugriff reicht es, die URL anzugeben:
          try {
            const response = await fetch('http://localhost:7000/api/greetings')
            const json = await response.json();
            // ...
          } (catch ex) {
            console.error('request failed', ex)
          }
          
          
// Alternative mit Promise:
fetch('http://localhost:7000/posts')
  .then(response => response.json())
  .then(json => /* ... */)
  .catch(ex => console.error('request failed', ex));
        Daten lesen per POST
              fetch erwartet als zweiten Parameter ein Objekt mit
              Konfigurationsparametern, u.a:
            
method: gibt die HTTP Methode an ( PUT, POST,
                DELETE, ...)
              headers: Objekt mit HTTP Headern für den Requestbody: Der Request-Payload (als String)
const response = await fetch(url, {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(payload)
})
// ... 
    
        
  try {
    // 1. fetch gibt ein Promise zurück, dass mit dem
    // Response-Objekt aufgelöst wird, wenn die Antwort vom Server
    // kommt
    const response = await fetch('http://localhost:7000/posts');
  
    // 2. das Response Objekt enthält eine json() Funktion,
    // die das geparse JSON aus der Antwort zurückliefert
    const posts = await response.json();
  
    // WAS MACHEN WIR MIT DER ANTWORT?
    // ???
  } catch (err) {
    // 4. Falls etwas schief geht, Fehler loggen
    console.error('request failed', err);
  }
  
  
          
        Der useEffekt-Hook erlaubt es dir aber, Seiteneffekte zu definierten (späteren) Zeitpunkten auszuführen
Als 1. Parameter übergibst Du eine Callback-Funktion, die deinen Code enthält. Dieser darf Seiteneffekte enthalten, z.B. einen fetch-Aufruf
                function App(props) {
                  React.useEffect( 
                    () => console.log("Ich werde nach JEDEM Rendern ausgeführt")
                  );
                }
              
          
                function App(props) {
                  React.useEffect( 
                    () => console.log("Ich werde nur nach 1. Rendern ausgeführt"),
                    []
                  );
                }
              
          
                  function BlogPost(props) {
                    React.useEffect(
                      () => console.log("..."), 
                      [props.postId]
                    );
                  }
                
          Zwei Parameter:
              useEffect und (useState) werden verwendet um Daten nach dem
              1. Rendern zu laden:
            
                function App() {
                  const [posts, setPosts] = React.useState([]);
                
                  React.useEffect(() => {
                    fetch("http://localhost:7000/posts")
                      .then(response => response.json())
                      .then(json => setPosts(json));
                  }, []);
                  return {posts.map(p => (
                    <Post key={p.id} post={p} />
                  ))}
                }
                
                
          Lifecycle des gezeigten Beispiels:
Zum Beispiel als Folge einer Benutzerinteraktion:
Im Event-Handler dürfen wir direkt Seiteneffekte nutzen
function App(props) {
  // laden, wie gesehen
  React.useEffect( ... );
  function addPost(post) {
    fetch("http://localhost:7000/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(post)
    })
      .then(response => response.json())
      .then(newPost => setPosts([newPost, ...posts]));
  }
  return
    ...
      <CreatePostForm onAdd={newPost => addPost(newPost)} />
    ...
}
                
        Beispiel: code/blog-example/beispiele/04_blog-app-mit-router
                Mappen von URLs auf Komponenten
                
                (Navigation findet ohne Server-Roundtrip statt)
              
                Komponenten halten (Teil) des Zustandes der Anwendung
                
                Welche "Seite" ist sichtbar (Blog Liste, Formular oder Einzelansicht)?
                
                Welche Daten werden dafür geladen (z.B. Blog Post Id)
              
Top-Level-Objekt, das einmalig (oben) in der Komponenten Hierarchie eingebunden werden muss
Mehrere Ausprägungen zum Arbeiten mit den URL und der Browser History:
      import {HashRouter as Router} from "react-router-dom";
      
      const app = <Router><App/></Router>;
      
      ReactDOM.render(app, document.getElementById(...));
              
          Das Route-Objekt mappt Pfade auf Komponenten
if/ switch statement
                  path wird der Pfad übergeben, für den die Route matchen soll
              
      import {HashRouter as Router, Route} from "react-router-dom";
      
      const app = <Router>
        <Route path="/post/:postId"><BlogPostPage /></Route>
        <Route path="/add"><AddPostPage onAdd={...} /></Route>
        <Route path="/" exact><BlogListPage /></Route>
      </Router>;
      
      ReactDOM.render(app, document.getElementById(...));
              
          In Routen werden Pfade angegeben, die mit der aktuellen URL verglichen werden
exact kann das Verhalten verändert werdenpath matcht immer
      // trifft zu für / und /greeting
      <Route path="/">...</Route>
      
      // trifft nur zu für /
      <Route path="/" exact>...</Route>
      
      // passt auf JEDE URL:
      <Route>...</Route>
      
              
            
      <Route path="/posts/:postId"><BlogPostPage/></Route>
            
            
                import { useParams } from "react-router";
                function BlogPostPage() {
                  const params = useParams();
                  // params.postId enthält den variablen Wert aus der URL
                
          Switch sorgt dafür, dass nur die erste Komponente im Block gerendert wird
              
      import {HashRouter as Router, Route, Switch} from "react-router-dom";
      
      const app = (
        <Router>
          <Switch>
            <Route path="/post/:postId"><BlogPostPage /></Route>
            <Route path="/" exact><BlogListPage/></Route>
      
            // "No match": ohne Pfad
            <Route><NotFoundPage/></Route>
      
          </Switch>
        </Router>
      );
      
      ReactDOM.render(app, document.getElementById(...));
              
          Mit Link und NavLink können Links erzeugt werden
to wird das Ziel angegebena ElementactiveClassName und activeStyle auf
                NavLink können Styles übergeben werden, die angewendet werden, wenn der
                Link der aktiven Route entspricht
              
      import {Link, NavLink} from "react-router-dom";
      
      <Link to='/'>Show all Posts</Link>
      
      // Erzeugtes 'a' Element erhält 'highlight' CSS-Klasse, wenn die aktive Route
      <NavLink to='/add' activeClassName="highlight">Add Post</NavLink>
      
              
          
              Mit dem history-Objekt kann mit der Browser History interagiert werden
            
Mit der History kann auf andere URLs gesprungen werden oder die Location abgefragt werden
Komponenten bekommen das history-Objekt über den useHistory Hook
      import { useHistory } from "react-router";
      function App() {
        const history = useHistory();
        function onAdd(newBlogPost) {
           ...
           // gehe zu neuer URL
           history.push("/"); 
           // Alternativ: gehe zu neuer URL, lösche aber aktuelle aus 
           // History im Browser
           history.replace("/...") 
        }
        return ...;
      }
              
          Kontakt:
Mail: nils@nilshartmann.net
Twitter: @nilshartmann