Stan komponentów React.js

Ten wpis jest 8 częścią z 41 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Przyszedł czas na poznanie tajemniczego state w React.js. Udało nam się tworzyć komponenty, które pięknie wyświetlały przekazane propsy, ale trzeba przyznać szczerze: Bez wewnątrznego stanu nie da się zbudować funkcjonalnej aplikacji. Dzisiaj nauczysz się taki stan dodawać i wykorzystywać 🙂

Zacznijmy może od typowego przykładu powielonego w wielu kursach: Licznika. Stwórz komponent, który ma dwa przyciski (plus i minus) oraz output na wyświetlanie wyniku. Początkowo wartość wynosi 0, kliknięcie w przycisk odpowiednio zwiększa lub zmniejsza liczbę. Umiesz już obsłużyć kliknięcia, potrafisz też wyświetlać dane. Jak jednak je modyfikować?

Propsy są niemutowalne

Propsów nie da się zmienić z wnętrza komponentu. A jeśli spróbujesz to pewnie Ci się uda, ale będziesz mieć ogromne problemy — niespójne dane na ekranie, a może nawet jakieś błędy. Generalnie: Straszne rzeczy. Co do zasady: Propsów nie zmieniamy z wnętrza komponentu, do którego zostały one przekazane. I kropka.

Wchodzi state

A więc tutaj pojawia się słynny state. Do czego służy? Do przechowywania stanu komponentu. Ponadto, state można mutować dzięki funkcji setState. A więc jest to dokładnie ten brakujący element układanki, którego poszukujemy! Tak, tak, to właśnie w state będziesz przechowywać licznik, który chcesz zaimplementować.

Wszystko na temat stanu wyjaśniamy na szkoleniach. Jeśli coś jest dla Ciebie niejasne to Poznaj React na szkoleniu!

Jeszcze jedna mała uwaga: Do state nie dobierzesz się w funkcyjnych komponentach. Stąd też ich nazwa: Stateless Functional Components. Potrzebna będzie klasa. Skoro to jest już jasne, weźmy się za pisanie kodu:

class App extends React.Component {
  render() {
    return (
      <div>
        <button>+</button>
        <output>{this.state.counter}</output>
        <button>-</button>
      </div>
    );
  }
}

Tak mniej-więcej będzie wyglądała nasza funkcja render. Jednak jeśli teraz odpalisz ten kod to dostaniesz w konsoli wyjątek, coś podobnego do Cannot read property 'counter' of null. Chwila drapania się po głowie i… no jasne, przecież nigdzie nie podaliśmy czym w ogóle jest state! Do tego potrzebny nam będzie konstruktor klasy. Dopisz na początku swojego komponentu:

  constructor() {
    super();
    this.state = {counter: 0};
  }

Przypomnę tylko, że jeśli klasa po czymś dziedziczy (tak jak tutaj po React.Component) to wewnątrz konstruktora musisz wywołać super(). Potem ustawiasz state na taki, jaki ma on być domyślnie — zanim zostaną wykonane jakiekolwiek akcje przez użytkownika. Teraz aplikacja renderuje się poprawnie, aczkolwiek nic spektakularnego się jeszcze nie dzieje!

this w React

Dopisujemy dwa onClick do przycisków i dwie metody w klasie: Jedna do zwiększania, a druga do zmniejszania wartości w liczniku. Posłuży do tego funkcja setState, w której odpowiednio ustawiamy licznik na (obecna wartość + 1) lub (obecna wartość - 1):

<button onClick={this.increment}>+</button>
  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

Jednak po kliknięciu w przycisk dostajemy tylko błąd: Cannot read property 'setState' of undefined. Cooo?

Wspominałem, że przy klasach pojawi nam się błąd związany z this. Każdy kto zna JS widzi już w czym problem: this w momencie wywołania funkcji increment nie jest związane z instancją komponentu. Jak rozwiązać ten problem?

Jest kilka sposobów, które omówię później. Na razie weźmiemy najprostszy: bind. Zmień kod w JSX:

<button onClick={this.increment.bind(this)}>+</button>

Woah, działa!

bind jest najprostszym rozwiązaniem, ale wcale nie najlepszym. Sam może stwarzać problemy, np. z wydajnością albo z tym, że referencja do funkcji się za każdym razem zmienia… Powrócę do tego tematu wkrótce.

Demo

Nauczyłaś/eś się używać state w React.js. Na razie w prosty sposób, ale ten temat jeszcze rozwinę w kolejnym wpisie. Tymczasem demo:

See the Pen Stan komponentów React.js by Michał Miszczyszyn (@mmiszy) on CodePen.

Ćwiczenie

Ćwiczenie: Dodaj dwa nowe liczniki. Pierwszy, który będzie zliczał wszystkie kliknięcia w przyciski (tzn. kliknięcie w + i - daje 0 na obecnym liczniku oraz 2 na nowym liczniku), oraz drugi, który będzie zliczał podwójne kliknięcia (tzw. double click) na elemencie z wynikiem. Jak wygląda teraz Twój state? Czy napotkałaś/eś jakieś problemy, albo coś Cię zaskoczyło? Napisz o tym w komentarzu 🙂

Nawigacja po kursie:
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Subscribe
Powiadom o
guest
31 komentarzy
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Krzysztof Pawłowski
Krzysztof Pawłowski
2 lat temu

class App extends React.Component {
constructor() {
super();
this.state = {counter: 0, sumOfClicks: 0, dblClicks: 0};
}

render() {
return (


{this.state.counter}

Suma kliknięć: {this.state.sumOfClicks} | Podwójne kliknięcia:{this.state.dblClicks}

);
}

increment() {
this.setState({
counter: this.state.counter + 1,
sumOfClicks: this.state.sumOfClicks + 1
})
}

decrement() {
this.setState({
counter: this.state.counter – 1,
sumOfClicks: this.state.sumOfClicks + 1
})
}

counterDblClicks() {
this.setState({
dblClicks: this.state.dblClicks + 1
})
}
}
ReactDOM.render(, document.getElementById(„app”));

Czy o to chodziło w ćwiczeniu? Bo nie jestem pewny…

Andrzej Jaworski
Andrzej Jaworski
1 rok temu

Witam, fajny kurs.

Ale w tym dokładnie przypadku to onDoubleClick nie dziła dokładnie jak by się chciało, ponieważ zlicza podwójne kliknięcia w element do którego jest przypisane. A miało zliczać kliknięcia w przyciski.

Moja propozycja to wsadzić te przyciski w jakieś divy i do nich podpiąć onDoubleClick. Działać działa. Oto kod Krzysztofa po małej modyfikacji:

class App extends React.Component {
constructor() {
super();
this.state = {counter: 0, sumOfClicks: 0, dblClicks: 0};
}

render() {
return (

{this.state.counter}

Suma kliknięć: {this.state.sumOfClicks} | Podwójne kliknięcia:{this.state.dblClicks}

);
}

increment() {
this.setState({
counter: this.state.counter + 1,
sumOfClicks: this.state.sumOfClicks + 1
})
}

decrement() {
this.setState({
counter: this.state.counter – 1,
sumOfClicks: this.state.sumOfClicks + 1
})
}

counterDblClicks() {
this.setState({
dblClicks: this.state.dblClicks + 1
})
}
}
ReactDOM.render(, document.getElementById(„app”));

Andrzej Jaworski
Andrzej Jaworski
2 lat temu

Racja doczytałem. Uznajmy to za małe urozmaicenie.

Dizu Blog
2 lat temu

Cześć,
Czy możesz proszę napisać coś więcej o super(). Nie do końca rozumiem po co? 🙂
Pozdrowienia 🙂

Mat Mic
Mat Mic
2 lat temu

Cześć Michał, w sumie natrafiłem na Twój blog już po pewnym czasie, gdy już poniekąd przejrzałem tutorial główny Reacta (np. grę Tic Tac Toe), ale ten kurs tutaj fajnie mi to wszystko usystematyzował. Mi udało się to sklecić w taki sposób, nie wiem czy to dobrze, że rozbijam przyciski na dwa odrębne functional components.

P.S Ups, to jest kod który pisałem od podstaw czytania tego artykułu, a nie zadanie domowe jeszcze 😀
https://codesandbox.io/s/5vq4l41q14

No to… następny wpis! 😀

A tak w ogóle, to jestem osobą, która bardzo chce zmienić typową branżę IT na właśnie programowanie, więc chwytam się czego się da, by nauczyć się jak najszybciej. Podziwiam ludzi, którzy mają wytrwałość by przejść przez te wszystkie dokumentacje.

Jaari
Jaari
2 lat temu

Czy jest jakaś różnica między inicjalizacją state’ów w konstruktorze, tak jak tutaj, a użyciem do tego funkcji getInitialState()?

Pozdrawiam!

Kuba Zalewski
Kuba Zalewski
2 lat temu

Wow. Nie przeczytałem dokładnie zadania i próbowałem dodawać dwa eventy na jeden przycisk, tym samym straciłem jakieś 20 minut, żeby tylko dowiedzieć się, że dwóch eventow nie da się podpiąć 😀 no cóż, przynajmniej się czegoś dowiedziałem, a kurs świetny 🙂 Moj kod do tego wpisu:

class App extends React.Component {
constructor() {
super();
this.state = {counter: 0, totalClicks: 0, doubleClicks: 0};
}

render() {
return (

 

Suma: {this.state.counter}
Kliknięć Łącznie: {this.state.totalClicks}
Podwójnych kliknięć: {this.state.doubleClicks}

);
}

increment() {
this.setState({
counter: this.state.counter + 1,
totalClicks: this.state.totalClicks +1
})
}

decrement() {
this.setState({
counter: this.state.counter – 1,
totalClicks: this.state.totalClicks +1
})
}

dblClicked() {
this.setState({
doubleClicks: this.state.doubleClicks +1
})
}
}

ReactDOM.render(, document.getElementById(„app”));

Marcin Groszkiewicz
Marcin Groszkiewicz
1 rok temu

Czy da się w setState wywołać metodę w tym wypadku np totalClicks?
totalClicks() {
this.state.totalClicks +1
}

rumc
rumc
2 lat temu

mówisz, że nie da się podpiąć na jednym elemencie click i double click,
ja podpiąłem i działa bez zarzutu, pytanie czy jest to zła konwencja? class Counter extends React.Component {
constructor() {
super()
this.state = {
counter : 0,
clicks : 0,
doubleClicks: 0
}
}

render () {
return (


{this.state.counter}
All clicks: {this.state.clicks}
Double clicks: {this.state.doubleClicks}

);
}

onDoubleClickHandler() {
this.setState({doubleClicks : this.state.doubleClicks + 1})
}

onIncrementHandler() {
this.setState({counter : this.state.counter + 1, clicks : this.state.clicks + 1})
}

onDecrementHandler() {
this.setState({counter : this.state.counter – 1, clicks : this.state.clicks + 1})
}

}

Ola Gruchała
Ola Gruchała
1 rok temu

Jestem mega wdzięczna za Twoją robotę – rewelacyjny kurs! To tutaj porządnie zrozumiałam props i state. I idę do kolejnych lekcji. Wklejać kodu nie będę, ale działa 🙂

Łukasz Dębowski
Łukasz Dębowski
1 rok temu

W czym takie rozwiązanie jest gorsze? (Zrobiłem coś podobnego zanim wszedłem w ten rozdział i działa, do tego jest to sporo mniej kodu, łatwiejsze do zrozumienia 🙂

class App extends React.Component {
constructor() {
super();
this.counter = 0;
}

render() {
return (


{this.counter}

);
}

increment() {
this.counter++
}

decrement() {
this.counter–
}
}

Łukasz Dębowski
Łukasz Dębowski
1 rok temu

Dobra ten akurat przykład nie działa 😀 Mój błąd ;p Ale np już coś takiego działa:

import React, { Component } from „react”;
import „./App.css”;

class ContactItem extends Component {
constructor(props) {
super(props);
this.counter = 0;
}

render() {
const imgUrl = comment image`;
return (

  • Avatar

    {this.props.name}

    {this.props.department}

  • );
    }

    handleOnClick = () => {
    console.log(`You clicked on ${this.props.name}! ${this.props.name} now has ${++this.counter} ${this.counter === 1 ? „click” : „clicks”}!`);
    };
    }

    export default ContactItem;

    Chodzi mi o zasadność uzywania this.setState() ponad zwykłe this.counter++

    Łukasz Dębowski
    Łukasz Dębowski
    1 rok temu

    Chyba już wiem, this.counter++ nie zaktualizuje mi elementu na stronie, prawda? Wyświetlenie w konsoli nie wymaga aktualizacji DOMu i dlatego nie było problemu? 🙂

    Bartosz Ficek
    Bartosz Ficek
    1 rok temu

    class Licznik extends React.Component{
    constructor(){
    super();
    this.state = {counter: 0, suum: 0, doubleClick: 0};
    }
    render(){
    return(


    {this.state.counter}


    {this.state.suum}

    {this.state.doubleClick}

    );}

    increment(){
    this.setState({
    counter: this.state.counter +1,
    suum: this.state.suum +1
    })
    }

    decrement(){
    this.setState({
    counter: this.state.counter -1,
    suum: this.state.suum +1
    })
    }
    double(){
    this.setState({
    doubleClick: this.state.doubleClick +1
    })
    }
    }
    Czy poprawnie ?

    Joanna Trapp
    Joanna Trapp
    1 rok temu

    Mój state:
    this.state = {counter: 0, sumOfClicks: 0, sumOfDoubleClicks: 0};

    pwinnicki
    pwinnicki
    1 rok temu

    Takie pytanie – w konstruktorze oraz w super nie podałem „props” a mimo to props zostały prawidłowo przekazane. Jaka jest więc różnica czy piszę:
    constructor() {
    super()

    czy też
    constructor(props) {
    super(props)

    pwinnicki
    pwinnicki
    1 rok temu

    Jedna funkcja increment z przekazaniem parametru o ile zmieni się licznik?

    increment = (ile) => e => {

    this.setState({

    counter: this.state.counter + ile

    })

    }

    render() {

    return (

    +

    {this.state.counter}

    );

    }

    pwinnicki
    pwinnicki
    1 rok temu

    class App extends React.Component {
    constructor() {
    super();
    this.state = {counter: 0, clicks: 0, dblclicks: 0};
    this.klikniecie = this.klikniecie.bind(this);
    }

    klikniecie = (ile) => e => {
    if (e.type==”click”) {
    this.setState(
    {
    counter: this.state.counter + ile,
    clicks: this.state.clicks + 1
    }
    );
    } else if (e.type==”dblclick”) {
    this.setState({
    dblclicks: this.state.dblclicks + 1
    });
    }
    }

    render() {
    return (


    {this.state.counter}

    Kliknięcia: {this.state.clicks}
    Podwójne kliknięcia: {this.state.dblclicks}

    );
    }

    }

    Piotr
    Piotr
    5 miesięcy temu

    Ja to wykoncypowałem tak:


    class ClickCounter extends React.Component {
    constructor() {
    super();
    this.state = {counter: 0, totalClicks: 0, totalDoubleClicks: 0};
    }

    render() {
    return (

    +
    {this.state.counter}
    -
    Total clicks: {this.state.totalClicks}
    Total double clicks: {this.state.totalDoubleClicks}

    );
    }

    increment() {
    this.setState({
    counter: this.state.counter + 1,
    totalClicks: this.state.totalClicks + 1
    })
    }

    decrement() {
    this.setState({
    counter: this.state.counter - 1,
    totalClicks: this.state.totalClicks + 1
    })
    }

    doubleClick() {
    this.setState({
    totalDoubleClicks: this.state.totalDoubleClicks + 1
    })
    }

    }

    export default ClickCounter

    Fkare
    Fkare
    4 miesięcy temu

    class App extends React.Component{
    render() {
    return (


    {this.state.counter}


    How many Clicks:
    {this.state.counter2}

    How many double Ckicks:
    {this.state.counter3}

    );
    }
    doubleClick(){
    this.setState({
    counter3: this.state.counter3 +1,
    })
    }
    addOne(){
    this.setState({
    counter: this.state.counter +1,
    counter2: this.state.counter2 +1
    })
    }
    lessOne(){
    this.setState({
    counter: this.state.counter -1,
    counter2: this.state.counter2 +1
    })
    }
    constructor() {
    super();
    this.state = {counter: 0, counter2: 0, counter3: 0};
    }
    }