Pytania rekrutacyjne Redux

45 pytań — 20 odpowiedzi dostępnych od razu

Przygotuj się do rozmowy rekrutacyjnej z Redux, korzystając z darmowego podglądu pytań i odpowiedzi z zestawu 45 zagadnień. To sposób na szybką powtórkę zasad zarządzania stanem w aplikacji frontendowej.

Fiszki obejmują store, actions, reducera oraz Redux Toolkit. Pomagają wyjaśnić przepływ danych, niezmienność stanu i sytuacje, w których centralny store ma uzasadnienie.

Darmowy start

Pierwsze 20 odpowiedzi są dostępne od razu

Przeglądaj pełną listę 45 pytań. Pełne odpowiedzi, przykłady kodu i materiały do nauki odblokujesz w panelu.

Odblokuj 25 odpowiedzi

Podstawy Redux

Czym jest Redux i jaki problem rozwiązuje w aplikacjach JavaScript?

Odpowiedź w 30 sekund: Redux to przewidywalny kontener stanu dla aplikacji JavaScript, który centralizuje stan aplikacji w jednym miejscu (store). Rozwiązuje problem zarządzania stanem współdzielonym między wieloma komponentami, eliminując chaos związany z przekazywaniem propsów przez wiele poziomów (prop drilling) i nieprzewidywalnymi mutacjami stanu.

Odpowiedź w 2 minuty: Redux został stworzony przez Dana Abramova w 2015 roku jako odpowiedź na rosnącą złożoność zarządzania stanem w dużych aplikacjach SPA, szczególnie tych zbudowanych w React. W aplikacjach tego typu stan jest często rozproszony między wieloma komponentami, co prowadzi do trudnych do debugowania błędów — szczególnie gdy ten sam fragment danych musi być dostępny w odległych częściach drzewa komponentów.

Redux rozwiązuje kilka konkretnych problemów. Po pierwsze, eliminuje prop drilling, czyli konieczność przekazywania danych przez kolejne poziomy komponentów, które same z tych danych nie korzystają. Po drugie, wprowadza jasny i przewidywalny przepływ danych — każda zmiana stanu musi przejść przez akcję (action) i czysty reduktor (reducer), co eliminuje ukryte mutacje. Po trzecie, ułatwia debugowanie dzięki narzędziom takim jak Redux DevTools, które pozwalają na time-travel debugging — cofanie się i przewijanie kolejnych stanów aplikacji.

Warto pamiętać, że Redux to nie tylko biblioteka, ale przede wszystkim wzorzec architektoniczny inspirowany Fluxem od Facebooka oraz elementami funkcyjnymi z języka Elm. Współcześnie zaleca się używanie Redux Toolkit (RTK), który znacznie redukuje boilerplate i jest oficjalnie rekomendowanym sposobem pisania logiki Redux.

Redux nie jest jednak rozwiązaniem dla każdego — w mniejszych aplikacjach często wystarczy useState, useReducer lub Context API. Mark Erikson, główny maintainer Redux, sam wielokrotnie powtarzał, że Redux powinien być używany tylko wtedy, gdy faktycznie rozwiązuje konkretny problem.

Przykład kodu:

import { createStore } from 'redux';

// Reduktor - czysta funkcja opisująca jak stan zmienia się w odpowiedzi na akcję
function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      // Zwracamy nowy obiekt stanu, nigdy nie mutujemy istniejącego
      return { value: state.value + 1 };
    case 'counter/decremented':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

// Tworzymy store - centralne miejsce przechowywania stanu
const store = createStore(counterReducer);

// Subskrybujemy zmiany - funkcja zostanie wywołana po każdej akcji
store.subscribe(() => console.log('Nowy stan:', store.getState()));

// Wywołujemy akcje - jedyny sposób na zmianę stanu w Redux
store.dispatch({ type: 'counter/incremented' }); // { value: 1 }
store.dispatch({ type: 'counter/incremented' }); // { value: 2 }
store.dispatch({ type: 'counter/decremented' }); // { value: 1 }

Materiały

Jakie są trzy fundamentalne zasady Redux (Three Principles)?

Odpowiedź w 30 sekund: Trzy zasady Redux to: (1) jedno źródło prawdy — cały stan aplikacji w jednym store, (2) stan jest tylko do odczytu — można go zmienić wyłącznie przez dispatchowanie akcji, (3) zmiany są dokonywane przez czyste funkcje (reduktory), które przyjmują poprzedni stan i akcję, a zwracają nowy stan.

Odpowiedź w 2 minuty: Pierwsza zasada — Single Source of Truth (Jedno źródło prawdy) — mówi, że cały stan aplikacji jest przechowywany w drzewie obiektów wewnątrz jednego store. To radykalnie upraszcza debugowanie i pozwala na implementację funkcji takich jak undo/redo, persystencja stanu czy serwerowe renderowanie. Wcześniej w aplikacjach typu MVC stan był rozproszony między wieloma kontrolerami i widokami, co utrudniało śledzenie zmian.

Druga zasada — State is Read-Only (Stan jest tylko do odczytu) — oznacza, że jedynym sposobem zmiany stanu jest wyemitowanie akcji, czyli obiektu opisującego co się stało. Komponenty nie mogą bezpośrednio modyfikować stanu — muszą wywołać dispatch z odpowiednią akcją. Dzięki temu mamy pełną kontrolę nad tym, kto, kiedy i dlaczego zmienia stan. Wszystkie zmiany są scentralizowane i wykonywane w ścisłej kolejności, więc nie musimy się martwić o race conditions.

Trzecia zasada — Changes are Made with Pure Functions (Zmiany są dokonywane przez czyste funkcje) — definiuje, że reduktory to czyste funkcje, które przyjmują poprzedni stan i akcję, a zwracają nowy stan. Czyste znaczy: brak efektów ubocznych, brak mutacji argumentów, brak wywołań API, brak Math.random() czy Date.now(). Ta sama akcja na tym samym stanie zawsze daje ten sam wynik. To kluczowe dla przewidywalności, testowalności i możliwości time-travel debuggingu.

Te trzy zasady razem tworzą fundament, na którym opiera się przewidywalność Redux. Mark Erikson często podkreśla, że ich naruszenie (np. mutacja stanu w reduktorze) jest najczęstszym źródłem trudnych do wykrycia błędów w aplikacjach Redux.

Przykład kodu:

// Zasada 1: Jedno źródło prawdy - cały stan w jednym obiekcie
const initialState = {
  user: { name: 'Anna', loggedIn: true },
  todos: [
    { id: 1, text: 'Nauczyć się Redux', completed: false }
  ],
  filter: 'all'
};

// Zasada 2: Stan tylko do odczytu - zmieniamy go przez akcje
const addTodoAction = {
  type: 'todos/added',
  payload: { id: 2, text: 'Napisać testy', completed: false }
};

// Zasada 3: Czyste funkcje - reduktor nie mutuje, zwraca nowy obiekt
function todosReducer(state = initialState, action) {
  switch (action.type) {
    case 'todos/added':
      // ŹLE: state.todos.push(action.payload) - to mutacja!
      // DOBRZE: zwracamy nowy obiekt z nową tablicą
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
    case 'todos/toggled':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    default:
      return state;
  }
}

Materiały

Czym jest jednokierunkowy przepływ danych (unidirectional data flow) w Redux?

Odpowiedź w 30 sekund: Unidirectional data flow oznacza, że dane w Redux płyną zawsze w jednym kierunku: View dispatchuje Action, Action trafia do Reducera, Reducer tworzy nowy State w Store, a Store powiadamia View o zmianie. Ten ścisły cykl gwarantuje przewidywalność i eliminuje bidirectional binding znany z frameworków takich jak Angular 1.x.

Odpowiedź w 2 minuty: Jednokierunkowy przepływ danych to architektoniczny wzorzec, w którym dane w aplikacji poruszają się tylko w jednym kierunku przez wyraźnie zdefiniowane etapy. W Redux ten cykl wygląda następująco: użytkownik wchodzi w interakcję z UI (np. klika przycisk), co powoduje dispatchowanie akcji. Akcja to zwykły obiekt JavaScript opisujący "co się stało". Następnie store przekazuje aktualny stan i akcję do reduktora, który zwraca nowy stan. Po aktualizacji store powiadamia wszystkich subskrybentów (np. komponenty React poprzez react-redux), które re-renderują się z nowymi danymi.

Kluczową cechą tego wzorca jest brak skrótów. Nie można "po cichu" zmienić stanu z poziomu komponentu — każda zmiana musi przejść przez pełny cykl. To rozwiązuje klasyczny problem dwukierunkowego bindingu, gdzie zmiana w jednym widoku mogła kaskadowo wpływać na inne widoki w nieprzewidywalny sposób, co było bolączką AngularJS 1.x.

Praktyczne korzyści unidirectional data flow to: łatwiejsze debugowanie (zawsze wiadomo, skąd wzięła się zmiana stanu), możliwość time-travel debuggingu (każdą zmianę można odtworzyć), prostsze testowanie (reduktory to czyste funkcje) oraz lepsza wydajność (możliwość selektywnego renderowania na podstawie zmian w store).

Warto zauważyć, że Redux nie wymyślił tego wzorca — wywodzi się on z architektury Flux od Facebooka oraz języka Elm. Jednak Redux znacząco uprościł implementację, redukując ją do jednego store i czystych reduktorów.

flowchart LR
    A[View<br/>Komponent UI] -->|1. dispatch action| B[Action<br/>type: 'todo/added']
    B -->|2. przekazana do| C[Reducer<br/>czysta funkcja]
    C -->|3. zwraca nowy state| D[Store<br/>centralny stan]
    D -->|4. notify subscribers| A

    style A fill:#61DAFB,stroke:#000,color:#000
    style B fill:#FFD93D,stroke:#000,color:#000
    style C fill:#6BCB77,stroke:#000,color:#000
    style D fill:#764ABC,stroke:#000,color:#fff

Przykład kodu:

import { createStore } from 'redux';

// 1. Reducer - czysta funkcja zmieniająca stan w odpowiedzi na akcje
function todosReducer(state = [], action) {
  switch (action.type) {
    case 'todo/added':
      // Zwracamy nową tablicę zamiast mutować istniejącą
      return [...state, { id: Date.now(), text: action.payload }];
    default:
      return state;
  }
}

// 2. Store - przechowuje stan i koordynuje przepływ
const store = createStore(todosReducer);

// 3. View - subskrybuje zmiany stanu (symulacja komponentu)
store.subscribe(() => {
  console.log('View renderuje się z nowym stanem:', store.getState());
});

// 4. User interaction - dispatch akcji uruchamia cały cykl
// View -> Action -> Reducer -> Store -> View
store.dispatch({ type: 'todo/added', payload: 'Nauczyć się jednokierunkowego przepływu' });
// Output: View renderuje się z nowym stanem: [{ id: ..., text: '...' }]

store.dispatch({ type: 'todo/added', payload: 'Zrozumieć dlaczego to przewidywalne' });
// Cykl powtarza się - znowu pełna droga przez wszystkie etapy

Materiały

Kiedy warto użyć Redux, a kiedy wystarczy lokalny state komponentu?

Odpowiedź w 30 sekund: Redux warto użyć, gdy stan jest współdzielony między wieloma niezwiązanymi komponentami, gdy logika aktualizacji stanu jest złożona, gdy potrzebujesz pełnej historii zmian (debugowanie, undo/redo) lub gdy aplikacja jest duża. Dla prostych formularzy, lokalnych stanów UI czy małych aplikacji wystarczy useState/useReducer w komponencie.

Odpowiedź w 2 minuty: Wybór między Redux a lokalnym stanem to klasyczny przykład decyzji architektonicznej, którą należy podejmować świadomie. Sam Dan Abramov w słynnym artykule "You Might Not Need Redux" przestrzegał przed dodawaniem Redux "na wszelki wypadek". Z kolei Mark Erikson w "When (and when not) to Reach for Redux" przedstawił konkretne kryteria, kiedy Redux faktycznie się opłaca.

Użyj Redux, gdy:

  • masz znaczne ilości stanu aplikacji potrzebnego w wielu miejscach aplikacji,
  • stan jest często aktualizowany,
  • logika aktualizacji stanu może być skomplikowana,
  • aplikacja ma średnią lub dużą bazę kodu i pracuje nad nią wielu programistów,
  • chcesz mieć podgląd jak stan jest aktualizowany w czasie (time-travel debugging),
  • musisz synchronizować stan między wieloma drzewami komponentów (np. modal, sidebar, lista),
  • chcesz cache'ować dane z API w sposób przewidywalny (chociaż tu dziś polecane jest RTK Query).

Wystarczy lokalny stan, gdy:

  • stan dotyczy tylko jednego komponentu lub jego dzieci (np. open/closed dropdown, wartość inputa),
  • aplikacja jest mała i prosta,
  • nie potrzebujesz historii zmian ani zaawansowanego debugowania,
  • nie współdzielisz stanu między odległymi częściami drzewa komponentów.

Warto pamiętać, że to nie jest decyzja "wszystko albo nic". W praktyce większość aplikacji używa kombinacji: globalny stan w Redux (dane użytkownika, autoryzacja, cache API), a lokalny stan w komponentach (formularze, stany UI, hover'y). Również Context API może być dobrym rozwiązaniem dla rzeczy zmieniających się rzadko (np. theme, język).

Współcześnie, dzięki Redux Toolkit (RTK) i RTK Query, próg wejścia w Redux jest znacznie niższy niż kiedyś — boilerplate jest minimalny, więc argument "Redux to za dużo kodu" stracił na sile.

Przykład kodu:

// === PRZYKŁAD 1: Lokalny state - WYSTARCZAJĄCY ===
// Stan dotyczy tylko tego komponentu - useState w zupełności wystarczy
function DropdownMenu() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Menu</button>
      {isOpen && <ul><li>Opcja 1</li><li>Opcja 2</li></ul>}
    </div>
  );
}

// === PRZYKŁAD 2: Redux - UZASADNIONY ===
// Dane użytkownika potrzebne w wielu miejscach aplikacji
// (Header, Sidebar, Profile, Settings, Checkout...)
import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: { name: null, email: null, isLoggedIn: false },
  reducers: {
    loggedIn: (state, action) => {
      // RTK używa Immer pod spodem - możemy "mutować" stan
      state.name = action.payload.name;
      state.email = action.payload.email;
      state.isLoggedIn = true;
    },
    loggedOut: (state) => {
      state.name = null;
      state.email = null;
      state.isLoggedIn = false;
    }
  }
});

// Header czyta z Redux
function Header() {
  const user = useSelector(state => state.user);
  return <div>Witaj, {user.name}!</div>;
}

// Profile też czyta z tego samego store - jeden źródło prawdy
function Profile() {
  const user = useSelector(state => state.user);
  return <div>Email: {user.email}</div>;
}

Materiały

Czym różni się Redux od Context API w React?

Odpowiedź w 30 sekund: Context API to mechanizm Reacta do przekazywania danych przez drzewo komponentów bez prop drillingu — nie jest narzędziem do zarządzania stanem. Redux to pełnoprawne rozwiązanie do zarządzania stanem z reduktorami, middleware, DevTools, optymalizacją renderowania i przewidywalnym przepływem danych. Context świetnie sprawdza się przy rzadko zmieniających się danych (theme, język), Redux przy złożonej, dynamicznej logice biznesowej.

Odpowiedź w 2 minuty: To jedno z najczęstszych nieporozumień we współczesnym React. Mark Erikson napisał o tym osobny artykuł "Blogged Answers: Why React Context is Not a 'State Management' Tool (and Why It Doesn't Replace Redux)", w którym jasno tłumaczy różnicę.

Context API to mechanizm dependency injection w React. Pozwala udostępnić wartość (cokolwiek to jest) wszystkim komponentom poniżej w drzewie bez przekazywania jej przez propsy. To wszystko. Context sam w sobie nie zarządza stanem, nie wymusza wzorca aktualizacji, nie ma optymalizacji renderowania, nie ma DevTools. Często łączy się go z useReducer, żeby uzyskać namiastkę zarządzania stanem.

Redux to kompletna biblioteka zarządzania stanem oparta na czystych reduktorach, akcjach i scentralizowanym store. Oferuje middleware (np. thunk, saga), Redux DevTools z time-travel debuggingiem, selektory z memoizacją (reselect), optymalizację re-renderów poprzez react-redux, a w wersji RTK Query — także cache'owanie zapytań API.

Kluczowe różnice praktyczne:

  • Re-renderowanie: gdy wartość w Context się zmieni, wszystkie konsumujące komponenty re-renderują się — nawet jeśli używają tylko fragmentu tej wartości. Redux z react-redux używa shallow comparison i renderuje tylko komponenty, których wybrany fragment stanu faktycznie się zmienił.
  • Middleware: Context nie ma żadnego sposobu na przechwytywanie akcji. Redux ma rozbudowany ekosystem middleware do logowania, async operacji, persystencji itd.
  • DevTools: Redux DevTools pozwala na cofanie czasu, eksport/import stanu, śledzenie akcji. Context tego nie ma.
  • Boilerplate: Context jest bardziej zwięzły dla prostych przypadków. RTK znacznie zredukował boilerplate Redux.

Tabela porównawcza:

Cecha Redux Context API
Typ narzędzia Biblioteka zarządzania stanem Mechanizm dependency injection
Przeznaczenie Złożona logika biznesowa, globalny stan Przekazywanie wartości bez prop drillingu
Wzorzec aktualizacji Wymuszony: action → reducer → store Brak — dowolna wartość
Optymalizacja re-renderów Tak (shallow compare w useSelector) Nie — re-render wszystkich konsumentów
Middleware Tak (thunk, saga, RTK Query) Brak
DevTools Redux DevTools (time-travel) Brak dedykowanych
Async logic Wbudowane w RTK (createAsyncThunk, RTK Query) Trzeba zaimplementować ręcznie
Memoizacja selektorów Reselect, RTK createSelector Brak
Idealne dla Częste aktualizacje, złożona logika, duże aplikacje Theme, język, zalogowany user (rzadko zmienne)
Krzywa uczenia Wyższa (akcje, reduktory, middleware) Niska (Provider + useContext)
Boilerplate (z RTK) Mały Bardzo mały

W praktyce te narzędzia często się uzupełniają. Redux zarządza złożonym stanem aplikacji, a Context dostarcza rzeczy specyficzne dla poddrzewa (np. theme w obrębie sekcji, formularz w obrębie modala).

Przykład kodu:

// === CONTEXT API - dobre dla rzadko zmieniających się danych ===
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  // Theme zmienia się rzadko - Context jest idealny
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Layout />
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  // Prosty hook konsumujący - bez boilerplate
  const { theme, setTheme } = useContext(ThemeContext);
  return <button className={theme}>Przełącz motyw</button>;
}

// === REDUX (z RTK) - dobre dla często zmieniających się danych ===
import { createSlice, configureStore } from '@reduxjs/toolkit';
import { useSelector, useDispatch, Provider } from 'react-redux';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], total: 0 },
  reducers: {
    itemAdded: (state, action) => {
      // Koszyk zmienia się często - Redux daje nam optymalizację
      state.items.push(action.payload);
      state.total += action.payload.price;
    }
  }
});

const store = configureStore({ reducer: { cart: cartSlice.reducer } });

function CartCounter() {
  // useSelector używa shallow compare - re-render tylko gdy items.length się zmieni
  const itemCount = useSelector(state => state.cart.items.length);
  return <span>Pozycje w koszyku: {itemCount}</span>;
}

function CartTotal() {
  // Ten komponent re-renderuje się TYLKO gdy total się zmieni,
  // niezależnie od zmian w items
  const total = useSelector(state => state.cart.total);
  return <span>Suma: {total} zł</span>;
}

Materiały

Store

Czym jest Store w Redux i jaką pełni rolę?

Odpowiedź w 30 sekund: Store to centralny obiekt w Redux, który przechowuje cały stan aplikacji w jednym miejscu (single source of truth). Pełni rolę kontenera stanu, umożliwia jego odczyt przez getState(), modyfikację przez dispatch(action) oraz subskrypcję zmian przez subscribe(listener).

Odpowiedź w 2 minuty: Store w Redux to obiekt łączący w sobie trzy kluczowe elementy architektury: aktualny stan aplikacji, reduktor (lub korzeń drzewa reduktorów) odpowiedzialny za jego aktualizację oraz mechanizm powiadamiania subskrybentów o zmianach. Stanowi serce wzorca Flux/Redux i jest jedynym źródłem prawdy o stanie aplikacji.

Główną rolą Store jest centralizacja zarządzania stanem. Zamiast rozsianych po komponentach kawałków stanu, mamy jeden obiekt, do którego można uzyskać dostęp z dowolnego miejsca aplikacji. Dzięki temu łatwiej debugować aplikację (znamy każdą zmianę stanu), implementować funkcje typu undo/redo, a także persystować stan między sesjami.

Store wymusza też jednokierunkowy przepływ danych: stan jest tylko do odczytu, a jego modyfikacja możliwa jest wyłącznie poprzez wysłanie akcji (dispatch). To eliminuje wiele problemów typowych dla aplikacji z dwukierunkowym wiązaniem danych - łatwiej przewidzieć skutki zmian i śledzić, kto i kiedy zmodyfikował stan.

W praktyce z Store komunikujemy się głównie pośrednio - przez bibliotekę React Redux (hooki useSelector, useDispatch), która abstrahuje bezpośrednie wywołania metod Store.

Przykład kodu:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';

// Tworzymy Store - centralny kontener stanu aplikacji
const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

// Store przechowuje stan i udostępnia metody do pracy z nim
console.log(store.getState()); // { counter: { value: 0 } }

// Wysłanie akcji - jedyny sposób na zmianę stanu
store.dispatch({ type: 'counter/increment' });

// Subskrypcja zmian - reaguje na każdą aktualizację stanu
const unsubscribe = store.subscribe(() => {
  console.log('Stan się zmienił:', store.getState());
});

// Anulowanie subskrypcji, gdy już jej nie potrzebujemy
unsubscribe();

Materiały

Jak utworzyć Store za pomocą createStore i configureStore?

Odpowiedź w 30 sekund: createStore to historyczna funkcja Redux Core (obecnie deprecated od Redux Toolkit 1.x), tworząca Store ze zwykłego reduktora. configureStore z Redux Toolkit to zalecany sposób - automatycznie konfiguruje middleware (thunk, immutability check), DevTools i obsługuje combineReducers w polu reducer.

Odpowiedź w 2 minuty: W klasycznym Redux Store tworzyło się funkcją createStore(reducer, preloadedState, enhancer) z pakietu redux. Wymagało to ręcznej konfiguracji middleware (przez applyMiddleware), integracji z Redux DevTools (przez composeWithDevTools) oraz ręcznego łączenia reduktorów (combineReducers). To podejście, choć działa, prowadzi do dużej ilości boilerplate'u i łatwo o pomyłki konfiguracyjne.

Od czasu wprowadzenia Redux Toolkit (RTK), oficjalnie zalecanym sposobem jest funkcja configureStore z pakietu @reduxjs/toolkit. Ona robi wszystko za nas: dodaje domyślny middleware (Redux Thunk dla operacji asynchronicznych, sprawdzanie niezmienialności stanu, wykrywanie nieserializowalnych wartości w stanie/akcjach), włącza integrację z Redux DevTools w trybie deweloperskim, automatycznie wywołuje combineReducers, gdy reducer jest obiektem.

Porównanie obu podejść:

Cecha createStore (legacy) configureStore (RTK)
Pakiet redux @reduxjs/toolkit
Status Deprecated od RTK 1.x Zalecany
Middleware Ręcznie (applyMiddleware) Automatycznie (thunk + checks)
DevTools Ręcznie (composeWithDevTools) Automatycznie
combineReducers Ręcznie Automatycznie z obiektu
Wykrywanie błędów Brak Immutability/serializability checks
Typowanie TS Wymaga ręcznej pracy Lepsze inferowanie typów
Boilerplate Dużo Minimalny

W nowych projektach zawsze należy używać configureStore. Funkcja createStore została oznaczona jako deprecated nie dlatego, że przestaje działać, ale po to, by zachęcić deweloperów do migracji na RTK.

Przykład kodu:

// PODEJŚCIE LEGACY (createStore) - nie zalecane
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';

const rootReducer = combineReducers({
  counter: counterReducer,
  user: userReducer,
});

// Ręczna konfiguracja - dużo kodu szablonowego
const legacyStore = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(thunk))
);

// PODEJŚCIE NOWOCZESNE (configureStore z RTK) - zalecane
import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: {
    counter: counterReducer, // combineReducers wywołane automatycznie
    user: userReducer,
  },
  // Thunk middleware, immutability check, serializability check
  // oraz DevTools zostały dodane automatycznie
});

// Można rozszerzyć domyślny middleware o własne
const storeWithExtra = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(logger),
});

Materiały

Jakie metody udostępnia obiekt Store (getState, dispatch, subscribe)?

Odpowiedź w 30 sekund: Store udostępnia trzy główne metody: getState() zwraca aktualny stan aplikacji, dispatch(action) wysyła akcję do reduktorów (jedyny sposób na zmianę stanu), subscribe(listener) rejestruje funkcję wywoływaną przy każdej zmianie stanu. Dodatkowo dostępne są replaceReducer() i [Symbol.observable]().

Odpowiedź w 2 minuty: Obiekt Store w Redux ma minimalne, lecz wystarczające API składające się głównie z trzech metod, które realizują wzorzec jednokierunkowego przepływu danych.

getState() to metoda zwracająca aktualny stan aplikacji - cały obiekt drzewa stanu. Jest synchroniczna i zwraca referencję do bieżącego stanu (nie kopię), dlatego nie wolno modyfikować zwróconego obiektu. Najczęściej używana wewnątrz reduktorów (do odczytu poprzedniego stanu) lub w kodzie middleware. W komponentach React zamiast getState używa się useSelector z biblioteki React Redux.

dispatch(action) to jedyny sposób na wywołanie zmiany stanu. Przyjmuje obiekt akcji (zawierający przynajmniej pole type) i przekazuje go przez stos middleware do reduktora głównego. Reduktor wylicza nowy stan, a Store zastępuje stary stan nowym. Po aktualizacji wszyscy subskrybenci są synchronicznie powiadamiani. Z middlewarem typu Thunk można dispatchować również funkcje.

subscribe(listener) rejestruje funkcję zwrotną wywoływaną po każdej aktualizacji stanu. Zwraca funkcję anulującą subskrypcję, którą należy wywołać, gdy listener nie jest już potrzebny - inaczej powstanie wyciek pamięci. Listener nie otrzymuje stanu jako argumentu - musi sam wywołać getState(). W praktyce subscribe jest używany przez biblioteki integracyjne (React Redux), a nie bezpośrednio w komponentach.

Dwie dodatkowe metody: replaceReducer(nextReducer) używana do code splittingu i hot reloadingu reduktorów, oraz [Symbol.observable]() umożliwiająca interop z bibliotekami reaktywnymi typu RxJS.

Przykład kodu:

import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; },
    addBy: (state, action) => { state.value += action.payload; },
  },
});

const store = configureStore({ reducer: { counter: counterSlice.reducer } });
const { increment, addBy } = counterSlice.actions;

// 1. getState() - odczyt aktualnego stanu
console.log(store.getState()); // { counter: { value: 0 } }

// 2. subscribe() - rejestracja listenera zmian stanu
const unsubscribe = store.subscribe(() => {
  // Listener nie otrzymuje stanu - musimy sami go pobrać
  const currentState = store.getState();
  console.log('Nowa wartość licznika:', currentState.counter.value);
});

// 3. dispatch() - wysłanie akcji zmieniającej stan
store.dispatch(increment());      // Listener loguje: 1
store.dispatch(addBy(5));         // Listener loguje: 6

// Pamiętaj o anulowaniu subskrypcji - inaczej wyciek pamięci!
unsubscribe();

store.dispatch(increment()); // Listener już nie reaguje

Materiały

Dlaczego w aplikacji Redux powinien istnieć tylko jeden Store?

Odpowiedź w 30 sekund: Jeden Store realizuje zasadę "single source of truth" - cały stan aplikacji w jednym miejscu, łatwiejszy do śledzenia, debugowania i serializacji. Wiele Store'ów rozbija przewidywalność, utrudnia time-travel debugging i podział stanu można zrealizować przez combineReducers zamiast wielu Store'ów.

Odpowiedź w 2 minuty: Zasada jednego Store'a to jedna z trzech fundamentalnych zasad Redux (obok "state is read-only" i "changes are made with pure functions"). Wynika z filozofii Flux i ma kilka istotnych uzasadnień praktycznych.

Single source of truth. Cały stan w jednym miejscu eliminuje problem synchronizacji wielu Store'ów. Nie ma sytuacji, gdzie ten sam fragment danych istnieje w dwóch miejscach i mogą się rozjechać. Komponenty zawsze odczytują ten sam, aktualny stan.

Łatwiejsze debugowanie. Redux DevTools pokazuje historię wszystkich akcji i stanów - z jednym Store'em mamy pełen obraz tego, co się dzieje w aplikacji. Time-travel debugging (cofanie się do wcześniejszego stanu) działa, ponieważ stan jest pojedynczą strukturą serializowalną do JSON. Z wieloma Store'ami musielibyśmy synchronizować ich historię.

Persystencja stanu. Zapisanie stanu do localStorage albo na backend, a potem przywrócenie sprowadza się do serializacji jednego obiektu. Z wieloma Store'ami musielibyśmy koordynować ich osobne zapisywanie i odtwarzanie.

Łatwiejsza integracja z middleware. Logowanie, monitoring, analityka, undo/redo - wszystkie te funkcje implementuje się raz dla całej aplikacji.

A co z separacją obowiązków? W Redux dzielimy stan nie przez wiele Store'ów, ale przez wiele reduktorów łączonych funkcją combineReducers. Każdy reduktor zarządza swoją "sekcją" globalnego stanu (np. state.user, state.cart, state.products), ale całość żyje w jednym Store. To zapewnia modularność bez rezygnacji z zalet pojedynczego źródła prawdy.

Wyjątki istnieją - mikrofronty albo izolowane widżety na stronie mogą mieć własne Store'y. Ale dla pojedynczej aplikacji SPA jeden Store jest jednoznacznym wyborem.

Przykład kodu:

// ŹLE - wiele Store'ów (anty-wzorzec)
const userStore = configureStore({ reducer: userReducer });
const cartStore = configureStore({ reducer: cartReducer });
const productsStore = configureStore({ reducer: productsReducer });

// Problemy:
// - Trudna synchronizacja (gdy zalogowany user wpływa na cart)
// - DevTools pokazuje tylko jeden Store
// - Persystencja wymaga osobnego zapisu każdego Store'a
// - Middleware trzeba konfigurować trzy razy

// DOBRZE - jeden Store, wiele reduktorów
import { configureStore, combineReducers } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: {
    // Każdy fragment stanu ma swój reduktor
    user: userReducer,      // dostępne jako state.user
    cart: cartReducer,      // dostępne jako state.cart
    products: productsReducer, // dostępne jako state.products
  },
});

// Korzyści:
// - Wszystko w jednym DevTools
// - Łatwa serializacja: JSON.stringify(store.getState())
// - Selektory mogą łączyć dane z różnych sekcji
const selectCartWithUserName = (state) => ({
  cart: state.cart,
  ownerName: state.user.name,
});

Materiały

Jak zainicjować Store z początkowym stanem (preloaded state)?

Odpowiedź w 30 sekund: W configureStore przekazuje się opcję preloadedState z obiektem początkowego stanu - przydatne do hydracji ze SSR, przywracania z localStorage lub testów. Stan musi pasować strukturą do drzewa reduktorów. W createStore jest to drugi argument funkcji.

Odpowiedź w 2 minuty: Preloaded state to początkowy stan, który nadpisuje wartości domyślne zwracane przez reduktory. Jest to mechanizm pozwalający na inicjalizację Store'a danymi pochodzącymi spoza aplikacji - z serwera (SSR/hydration), z pamięci przeglądarki (localStorage/sessionStorage) lub z fixtures w testach.

W Redux Toolkit przekazuje się go jako opcję preloadedState do configureStore. Struktura tego obiektu musi odpowiadać strukturze drzewa reduktorów - jeśli reduktor jest mapą { user, cart }, to preloaded state może zawierać klucze user i/lub cart. Brakujące klucze zostaną zainicjalizowane domyślnymi wartościami z reduktorów (initialState).

Typowe zastosowania preloaded state:

  1. Server-Side Rendering (SSR) - serwer renderuje stronę z określonym stanem, serializuje go i wstrzykuje do HTML. Klient hydruje Store tym stanem, dzięki czemu nie ma migotania UI ani konieczności ponownego pobierania danych.

  2. Persystencja w localStorage - przy starcie aplikacji odczytujemy zapisany stan i przekazujemy jako preloaded state. Biblioteki typu redux-persist automatyzują ten proces.

  3. Testy jednostkowe - możemy uruchomić komponenty Reduxowe z dowolnym stanem początkowym, bez konieczności dispatchowania akcji w setup'ie testu.

  4. Funkcje typu "demo mode" - inicjalizacja Store'a przykładowymi danymi dla niezalogowanych użytkowników.

Trzeba uważać na kilka pułapek. Po pierwsze, preloaded state musi być serializowalny (bez funkcji, Date, Map, Set) - inaczej serializableCheck middleware zgłosi błąd. Po drugie, struktura musi być kompatybilna z reduktorami - nieoczekiwane klucze będą ignorowane, ale brakujące pola w obiektach mogą prowadzić do błędów w komponentach. Po trzecie, w starym createStore preloaded state był drugim argumentem - w RTK to opcja w obiekcie konfiguracyjnym.

Przykład kodu:

import { configureStore, createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: { name: 'Gość', loggedIn: false },
  reducers: {
    login: (state, action) => {
      state.name = action.payload;
      state.loggedIn = true;
    },
  },
});

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] },
  reducers: {},
});

// 1. SSR / hydration - stan z serwera wstrzyknięty do HTML
const stateFromServer = window.__INITIAL_STATE__ || {};

// 2. Przywrócenie z localStorage
function loadFromStorage() {
  try {
    const saved = localStorage.getItem('appState');
    return saved ? JSON.parse(saved) : undefined;
  } catch {
    return undefined;
  }
}

const persistedState = loadFromStorage();

// 3. Konfiguracja Store'a z preloaded state
const store = configureStore({
  reducer: {
    user: userSlice.reducer,
    cart: cartSlice.reducer,
  },
  // Stan początkowy nadpisuje initialState z reduktorów
  preloadedState: persistedState || {
    user: { name: 'Jan Kowalski', loggedIn: true },
    // Brakujący 'cart' - zostanie zainicjalizowany przez reduktor
  },
});

console.log(store.getState());
// { user: { name: 'Jan Kowalski', loggedIn: true }, cart: { items: [] } }

// 4. Zapis do localStorage przy każdej zmianie stanu
store.subscribe(() => {
  localStorage.setItem('appState', JSON.stringify(store.getState()));
});

// 5. Użycie w testach - Store z konkretnym stanem
function setupTestStore(preloadedState) {
  return configureStore({
    reducer: { user: userSlice.reducer, cart: cartSlice.reducer },
    preloadedState,
  });
}

const testStore = setupTestStore({
  user: { name: 'Test User', loggedIn: true },
  cart: { items: [{ id: 1, name: 'Książka' }] },
});

Materiały

Actions i Action Creators

Czym jest Action w Redux i jaką ma strukturę?

Odpowiedź w 30 sekund: Action to zwykły obiekt JavaScript opisujący, co się wydarzyło w aplikacji. Jest jedynym źródłem informacji dla store i musi zawierać pole type identyfikujące rodzaj zdarzenia. Opcjonalnie może przenosić dodatkowe dane potrzebne do aktualizacji stanu.

Odpowiedź w 2 minuty: Action w Redux to plain object, który reprezentuje intencję zmiany stanu. Zgodnie z oficjalną dokumentacją Redux, akcje są "the only source of information for the store" — store nigdy nie jest modyfikowany bezpośrednio, zawsze za pośrednictwem dispatchowanych akcji. Każdy action musi mieć pole type (zwykle string), które jednoznacznie opisuje, co się stało, np. 'todos/todoAdded'.

Poza polem type action może zawierać dowolne inne pola przenoszące dane. Konwencja zaleca, aby dodatkowe informacje umieszczać w polu payload, co poprawia czytelność i ułatwia tworzenie generycznego middleware. Akcje powinny być serializowalne — nie powinny zawierać funkcji, klas, instancji Promise ani innych nieserializowalnych wartości, ponieważ uniemożliwia to działanie Redux DevTools (time-travel debugging) oraz persystencji stanu.

W praktyce akcje opisują "co się wydarzyło" (np. userLoggedIn), a nie "jak zmienić stan" — logika zmiany stanu należy do reducerów. Konwencja nazewnicza zalecana przez Redux Style Guide to feature/eventName w formie czasu przeszłego, np. cart/itemAdded.

Przykład kodu:

// Najprostszy action - tylko type
const incrementAction = { type: 'counter/incremented' };

// Action z payloadem
const addTodoAction = {
  type: 'todos/todoAdded',
  payload: {
    id: 1,
    text: 'Nauczyć się Redux',
    completed: false
  }
};

// Action z metadanymi i flagą błędu (zgodne z FSA)
const fetchUserFailed = {
  type: 'users/fetchFailed',
  payload: new Error('Network error'),
  error: true,
  meta: { timestamp: Date.now() }
};

// Dispatchowanie akcji
store.dispatch(incrementAction);
store.dispatch(addTodoAction);

Materiały

Co to jest Action Creator i dlaczego warto go używać?

Odpowiedź w 30 sekund: Action Creator to funkcja, która tworzy i zwraca obiekt action. Zamiast ręcznie pisać literały obiektów w każdym miejscu dispatchowania, wywołujemy funkcję z odpowiednimi argumentami. Eliminuje duplikację, centralizuje logikę tworzenia akcji i zmniejsza ryzyko literówek w polach type.

Odpowiedź w 2 minuty: Action Creator to po prostu funkcja, która przyjmuje argumenty i zwraca obiekt action. To wzorzec rekomendowany przez Redux Style Guide, ponieważ rozwiązuje kilka problemów: enkapsuluje konstrukcję obiektu action w jednym miejscu, ułatwia refaktoryzację (zmiana struktury action wymaga modyfikacji tylko jednej funkcji) oraz redukuje boilerplate w komponentach.

Bez action creators każdy komponent musiałby znać dokładną strukturę action, w tym poprawny string dla type. Action creators eliminują "magiczne stringi" rozsiane po kodzie i pozwalają na lepsze wsparcie ze strony narzędzi (autocomplete, type checking w TypeScript). Mogą również zawierać logikę walidacji argumentów lub transformacji danych przed utworzeniem action.

Redux Toolkit (RTK) znacząco upraszcza tworzenie action creators dzięki funkcji createAction. Generuje ona action creator z automatycznie ustawionym type, a także eksponuje sam string typu jako property .type (przydatne w reducerach z createReducer lub w extraReducers). RTK wspiera też tzw. "prepare callback" — funkcję wstępnego przetwarzania argumentów, pozwalającą np. wygenerować ID, dodać timestamp lub przekształcić payload przed jego umieszczeniem w action. To eliminuje potrzebę pisania logiki przygotowania w komponencie.

Przykład kodu:

// Klasyczny action creator (vanilla Redux)
function addTodo(text) {
  return {
    type: 'todos/todoAdded',
    payload: { id: Date.now(), text, completed: false }
  };
}

// Użycie
store.dispatch(addTodo('Nauczyć się RTK'));

// Redux Toolkit - createAction
import { createAction, nanoid } from '@reduxjs/toolkit';

const incremented = createAction('counter/incremented');
// incremented() -> { type: 'counter/incremented' }
// incremented.type -> 'counter/incremented'

// createAction z prepare callback - dla złożonej logiki
const todoAdded = createAction('todos/todoAdded', (text) => {
  return {
    payload: {
      id: nanoid(),        // automatycznie generowane ID
      text,
      createdAt: new Date().toISOString(),
      completed: false
    }
  };
});

// Użycie - bez konieczności znania struktury action
store.dispatch(todoAdded('Kupić mleko'));
// dispatchuje: { type: 'todos/todoAdded', payload: { id: '...', text: 'Kupić mleko', ... } }

Materiały

Czym różni się action type od action payload?

Odpowiedź w 30 sekund: type to string identyfikujący rodzaj zdarzenia (np. 'todos/todoAdded') — jest obowiązkowy i służy reducerom do rozpoznania, jak zaktualizować stan. payload to opcjonalne pole przenoszące dane potrzebne do tej aktualizacji (np. obiekt nowego todo). Type mówi "co się stało", payload "z jakimi danymi".

Odpowiedź w 2 minuty: Pole type to jedyny obowiązkowy element action. Musi być serializowalną wartością — w praktyce zawsze stringiem — i powinno jednoznacznie opisywać typ zdarzenia. Reducery wykorzystują type w instrukcjach switch lub mapowaniu w createReducer/createSlice, aby zdecydować, jak przekształcić stan. Konwencja Redux Style Guide zaleca format domena/zdarzenie w czasie przeszłym (np. cart/itemAdded, auth/userLoggedOut), co poprawia czytelność i ułatwia debugowanie w Redux DevTools.

payload to konwencjonalne, opcjonalne pole, w którym umieszcza się dane towarzyszące akcji. Konwencja ta wywodzi się z propozycji Flux Standard Action (FSA) i jest powszechnie przyjęta w ekosystemie Redux, w tym w Redux Toolkit. Trzymanie wszystkich dodatkowych danych w payload (zamiast rozrzucania ich po kilku polach na najwyższym poziomie obiektu) ułatwia pisanie generycznego middleware i utrzymuje spójną strukturę między akcjami.

Kluczowa różnica: type jest deklaracją intencji ("DODANO TODO"), a payload to konkretne dane tej intencji (treść tego todo, jego ID itp.). Reducer NIE powinien interpretować payloadu, aby zdecydować, czy w ogóle ma zareagować — ta decyzja należy wyłącznie do dopasowania type. Payload może być dowolnej struktury: prymityw, obiekt, tablica — byleby był serializowalny.

Przykład kodu:

// Action z wyraźnym podziałem na type i payload
const action = {
  type: 'users/profileUpdated',  // CO się stało
  payload: {                      // Z jakimi DANYMI
    userId: 42,
    name: 'Jan Kowalski',
    email: 'jan@example.com'
  }
};

// Reducer - decyzja oparta wyłącznie na type
function usersReducer(state = {}, action) {
  switch (action.type) {                    // sprawdzamy TYPE
    case 'users/profileUpdated':
      return {
        ...state,
        [action.payload.userId]: action.payload  // używamy PAYLOAD
      };
    case 'users/deleted':
      const { [action.payload.userId]: _, ...rest } = state;
      return rest;
    default:
      return state;
  }
}

// W RTK - createSlice automatycznie generuje type w formacie 'sliceName/reducerName'
import { createSlice } from '@reduxjs/toolkit';

const usersSlice = createSlice({
  name: 'users',
  initialState: {},
  reducers: {
    profileUpdated: (state, action) => {
      // action.type === 'users/profileUpdated' (auto)
      // action.payload === to, co przekazaliśmy do dispatcha
      state[action.payload.userId] = action.payload;
    }
  }
});

Materiały

Jak definiować stałe dla action types i dlaczego to dobra praktyka?

Odpowiedź w 30 sekund: Action types definiuje się jako stałe (const) w osobnym pliku, np. actionTypes.js, i importuje zarówno do action creators, jak i reducerów. Eliminuje to ryzyko literówek w stringach, ułatwia refaktoryzację, daje lepsze wsparcie IDE (autocomplete) i centralizuje zarządzanie typami akcji. W Redux Toolkit ten problem znika, bo createSlice generuje stałe automatycznie.

Odpowiedź w 2 minuty: W klasycznym Redux action types były często powielane jako literały stringowe — raz w action creatorze, raz w reducerze, czasami jeszcze w testach. To prowadziło do trudnych do wykrycia bugów: literówka w stringu nie powoduje błędu w runtime, akcja po prostu nie była obsługiwana przez reducer. Rozwiązaniem jest definiowanie types jako nazwanych stałych w dedykowanym pliku.

Korzyści są wielorakie. Po pierwsze, jeśli zrobimy literówkę w nazwie stałej (TODO_ADDDED zamiast TODO_ADDED), JavaScript od razu rzuci ReferenceError, podczas gdy literówka w stringu po cichu zerwie cały flow. Po drugie, IDE oferują autocomplete dla nazw zmiennych, a nie dla treści stringów. Po trzecie, łatwo znaleźć wszystkie miejsca użycia danego action type ("Find usages"). Po czwarte, refaktoryzacja jednej stałej automatycznie aktualizuje wszystkie miejsca importu.

Konwencja Redux używa SCREAMING_SNAKE_CASE dla stałych (ADD_TODO, USER_LOGGED_IN), choć Redux Style Guide rekomenduje wartości w formacie domain/event ('todos/todoAdded'). Te dwie konwencje można łączyć: stała TODO_ADDED z wartością 'todos/todoAdded'.

W Redux Toolkit problem definiowania stałych w zasadzie znika. createSlice automatycznie generuje action creators wraz z odpowiednimi typami w formacie nazwaSlice/nazwaReducera. Każdy action creator wyeksponuje swój typ jako property .type, więc gdy potrzebujemy stringa (np. w extraReducers lub middleware), używamy mySlice.actions.someAction.type zamiast jakichkolwiek ręcznie definiowanych stałych.

Przykład kodu:

// === Klasyczne Redux: actionTypes.js ===
export const TODO_ADDED = 'todos/todoAdded';
export const TODO_TOGGLED = 'todos/todoToggled';
export const TODO_REMOVED = 'todos/todoRemoved';

// === actions.js ===
import { TODO_ADDED, TODO_TOGGLED } from './actionTypes';

export const addTodo = (text) => ({
  type: TODO_ADDED,           // Stała - IDE wykryje literówkę
  payload: { id: Date.now(), text }
});

// === reducer.js ===
import { TODO_ADDED, TODO_TOGGLED } from './actionTypes';

export function todosReducer(state = [], action) {
  switch (action.type) {
    case TODO_ADDED:          // Bez stringa - bezpieczniej
      return [...state, action.payload];
    case TODO_TOGGLED:
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    default:
      return state;
  }
}

// === Redux Toolkit: problem rozwiązany automatycznie ===
import { createSlice } from '@reduxjs/toolkit';

const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    todoAdded: (state, action) => {
      state.push(action.payload);  // Immer pozwala na mutację
    },
    todoToggled: (state, action) => {
      const todo = state.find(t => t.id === action.payload);
      if (todo) todo.completed = !todo.completed;
    }
  }
});

// RTK automatycznie generuje:
// todosSlice.actions.todoAdded - action creator
// todosSlice.actions.todoAdded.type === 'todos/todoAdded' - stała typu
export const { todoAdded, todoToggled } = todosSlice.actions;
export default todosSlice.reducer;

Materiały

Czym są Flux Standard Actions (FSA) i jakie mają zalety?

Odpowiedź w 30 sekund: Flux Standard Action (FSA) to konwencja standaryzująca strukturę obiektów action zaproponowana przez społeczność Redux. Action zgodny z FSA musi mieć pole type, a opcjonalnie może zawierać payload, error (boolean) i meta. Inne pola na najwyższym poziomie są niedozwolone. Ujednolicony format ułatwia pisanie generycznego middleware i bibliotek.

Odpowiedź w 2 minuty: Flux Standard Action to specyfikacja stworzona w repozytorium redux-utilities/flux-standard-action, mająca na celu rozwiązanie problemu niespójności w strukturze akcji w różnych projektach Redux. Każdy zespół wymyślał własną konwencję — niektórzy używali data, inni body, jeszcze inni rozsiewali pola po najwyższym poziomie obiektu. To utrudniało reużywanie middleware i tworzenie bibliotek pomocniczych.

FSA definiuje ścisły kontrakt. Action MUSI być plain JavaScript object i MUSI mieć pole type. MOŻE mieć pola payload (dane), error (boolean wskazujący błąd) i meta (dodatkowe informacje niebędące częścią payloadu, np. timestamp, ID requesta). Action NIE MOŻE mieć innych pól na najwyższym poziomie. Gdy error jest true, payload POWINIEN być instancją Error (lub obiektem podobnym do Error). To pozwala middleware jednolicie rozpoznawać akcje błędu, niezależnie od ich type.

Zalety FSA są praktyczne. Generyczne middleware (np. do logowania, retry, error handlingu) może działać na dowolnej akcji bez znajomości jej specyfiki — wie, że dane znajdzie w payload, błąd rozpozna po error: true, a metadane w meta. Biblioteki takie jak redux-promise, redux-actions czy starsze wersje redux-observable opierały się na FSA. Redux Toolkit również w pełni przestrzega FSA — createAction generuje action creators zgodne z tym standardem, a prepare callback pozwala wygodnie ustawić payload, error i meta.

W praktyce większość nowoczesnego kodu Redux (zwłaszcza z RTK) jest zgodna z FSA bez świadomego wysiłku, ponieważ to po prostu rozsądna konwencja. Znajomość pełnej struktury jest jednak przydatna przy pisaniu middleware lub integracji z bibliotekami zewnętrznymi.

Przykład kodu:

// === Pełna struktura FSA ===
const fsaAction = {
  type: 'users/fetchSucceeded',   // WYMAGANE - string identyfikator
  payload: { id: 1, name: 'Jan' }, // OPCJONALNE - dane akcji
  error: false,                    // OPCJONALNE - boolean flagi błędu
  meta: {                          // OPCJONALNE - metadane (timestamp, requestId, itp.)
    requestId: 'abc-123',
    timestamp: 1716894000000
  }
};

// === Action błędu FSA ===
const errorAction = {
  type: 'users/fetchFailed',
  payload: new Error('Network timeout'), // Gdy error: true, payload to Error
  error: true,                            // Flaga rozpoznawalna przez middleware
  meta: { requestId: 'abc-123' }
};

// === NIEPOPRAWNE - dodatkowe pole na top-level ===
const invalidAction = {
  type: 'users/fetched',
  payload: { id: 1 },
  userId: 1  // BŁĄD - to powinno być w payload lub meta
};

// === RTK createAction z prepare callback - generuje akcje zgodne z FSA ===
import { createAction, nanoid } from '@reduxjs/toolkit';

const fetchUserSucceeded = createAction(
  'users/fetchSucceeded',
  (user, requestId) => ({
    payload: user,
    meta: { requestId, timestamp: Date.now() }
  })
);

const fetchUserFailed = createAction(
  'users/fetchFailed',
  (error, requestId) => ({
    payload: error,
    error: true,
    meta: { requestId }
  })
);

// === Generyczne middleware wykorzystujące FSA ===
const errorLoggerMiddleware = (store) => (next) => (action) => {
  // Działa dla KAŻDEJ akcji błędu - nie musimy znać jej type
  if (action.error === true) {
    console.error(`[${action.type}]`, action.payload, action.meta);
  }
  return next(action);
};

// === Walidacja FSA ===
import { isFSA } from 'flux-standard-action';
console.log(isFSA(fsaAction));     // true
console.log(isFSA(invalidAction)); // false

Materiały

Reducers

Czym jest Reducer i jakie są zasady jego tworzenia?

Odpowiedź w 30 sekund: Reducer to czysta funkcja o sygnaturze (state, action) => newState, która opisuje, jak stan aplikacji zmienia się w odpowiedzi na akcje. Musi być deterministyczna, nie może mutować stanu ani wykonywać efektów ubocznych, a w przypadku nieznanej akcji powinna zwrócić aktualny stan bez zmian.

Odpowiedź w 2 minuty: Reducer w Redux jest sercem zarządzania stanem. Otrzymuje dwa argumenty: bieżący stan oraz akcję opisującą, co się wydarzyło, a zwraca nowy stan. Nazwa "reducer" pochodzi od funkcji Array.prototype.reduce - tak samo jak ona, redukuje sekwencję akcji do pojedynczej wartości stanu.

Podstawowe zasady tworzenia reducerów są ścisłe: muszą być czystymi funkcjami (te same wejścia zawsze dają te same wyjścia), nie mogą mutować przekazanego stanu, nie mogą wykonywać efektów ubocznych (zapytania API, losowanie wartości, odczyt daty), oraz muszą zwrócić nowy obiekt stanu, jeśli coś się zmieniło, lub ten sam stan, jeśli akcja ich nie dotyczy.

Reducer powinien też mieć zdefiniowany stan początkowy - najczęściej przez parametr domyślny state = initialState. Dzięki temu Redux może zainicjalizować store przez wywołanie reducera z akcją typu @@INIT. Dobrym wzorcem jest również użycie instrukcji switch po action.type lub mapy obiektowej, aby obsłużyć różne rodzaje akcji.

W nowoczesnym Redux Toolkit (RTK) używamy createSlice, które generuje reducery automatycznie i pozwala pisać kod, który "wygląda jak mutacja", ale pod spodem używa biblioteki Immer do tworzenia niezmiennych kopii.

Przykład kodu:

// Klasyczny reducer Redux
const initialState = { count: 0, items: [] };

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'counter/incremented':
      // Zwracamy nowy obiekt, nie mutujemy istniejącego
      return { ...state, count: state.count + 1 };
    case 'counter/decremented':
      return { ...state, count: state.count - 1 };
    case 'items/added':
      // Nowa tablica zamiast push()
      return { ...state, items: [...state.items, action.payload] };
    default:
      // Nieznana akcja - zwracamy stan bez zmian
      return state;
  }
}

// Nowoczesny wariant z Redux Toolkit (createSlice + Immer)
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    incremented(state) {
      // "Wygląda jak mutacja" - Immer tworzy nową kopię pod spodem
      state.count += 1;
    },
    itemAdded(state, action) {
      state.items.push(action.payload); // Bezpieczne dzięki Immer
    },
  },
});

Materiały

Dlaczego Reducery muszą być czystymi funkcjami (pure functions)?

Odpowiedź w 30 sekund: Czyste funkcje gwarantują przewidywalność stanu - te same akcje zawsze produkują ten sam wynik, niezależnie od kontekstu. To fundament, na którym działają narzędzia Redux DevTools: time-travel debugging, hot reloading, rejestrowanie i odtwarzanie akcji oraz testowalność bez mockowania.

Odpowiedź w 2 minuty: Czysta funkcja to taka, która spełnia trzy warunki: zwraca ten sam wynik dla tych samych argumentów, nie zależy od żadnego zewnętrznego stanu (zmienne globalne, czas, losowość) i nie wywołuje żadnych efektów ubocznych (zapis do bazy, mutacja argumentów, zapytania sieciowe). Reducery muszą być czyste, ponieważ Redux opiera się na tej własności.

Przewidywalność i debugowanie: Skoro reducer jest deterministyczny, możemy odtworzyć dowolny stan aplikacji, mając tylko stan początkowy i listę akcji. To właśnie umożliwia time-travel debugging w Redux DevTools - cofamy się i wracamy do przodu w czasie, a aplikacja przechodzi przez kolejne stany jak film. Gdyby reducer odczytywał Date.now() lub robił fetch, ta gwarancja by przepadła.

Testowalność: Czyste reducery można testować bez mockowania - wystarczy podać stan i akcję, aby sprawdzić wynik. Nie potrzebujemy żadnych setupów, fake'ów ani jest.mock.

Hot reloading i replikacja: Czystość pozwala bezpiecznie wymieniać kod reducerów bez utraty stanu (hot module replacement) oraz replikować akcje między klientem a serwerem (na przykład w testach E2E lub raportowaniu błędów - "wyślij ostatnie 50 akcji, odtworzę problem u siebie").

Pamięciowo-referencyjne porównania: React-Redux używa porównań przez referencję (===) do decydowania, czy komponenty mają się przerenderować. Mutacja stanu łamie te porównania, bo referencja pozostaje ta sama, mimo że dane się zmieniły - prowadzi to do brakujących re-renderów i błędów typu "UI nie reaguje na zmianę".

Przykład kodu:

// ZŁY reducer - nieczysta funkcja
function badReducer(state, action) {
  if (action.type === 'todo/added') {
    state.todos.push({
      id: Math.random(),           // Niedeterministyczne!
      text: action.payload,
      createdAt: Date.now(),       // Zależność od czasu!
    });
    fetch('/api/log', { method: 'POST' }); // Efekt uboczny!
    return state;                  // Mutacja!
  }
  return state;
}

// DOBRY reducer - czysta funkcja
function goodReducer(state, action) {
  if (action.type === 'todo/added') {
    // ID i timestamp powinny być w action.payload (utworzone wcześniej)
    return {
      ...state,
      todos: [...state.todos, action.payload],
    };
  }
  return state;
}

// Generowanie ID i czasu robimy w action creatorze lub middleware
const addTodo = (text) => ({
  type: 'todo/added',
  payload: {
    id: crypto.randomUUID(),
    text,
    createdAt: Date.now(),
  },
});

// Dzięki czystości test jest trywialny:
test('dodaje todo', () => {
  const stan = { todos: [] };
  const akcja = { type: 'todo/added', payload: { id: '1', text: 'Test' } };
  expect(goodReducer(stan, akcja)).toEqual({ todos: [{ id: '1', text: 'Test' }] });
});

Materiały

Jak obsługiwać nieznane action types w Reducerze?

Odpowiedź w 30 sekund: Reducer musi zawsze zwrócić bieżący stan dla nieznanej akcji - nigdy undefined. W klasycznym Redux używamy gałęzi default: w switch, a w Redux Toolkit createSlice robi to automatycznie. Dzięki temu wiele reducerów może współistnieć w combineReducers, ignorując akcje, które do nich nie należą.

Odpowiedź w 2 minuty: W Redux każda zdyspaczowana akcja przechodzi przez wszystkie reducery w drzewie stanu. Reducer obsługujący todos zobaczy też akcje typu user/loggedIn, posts/fetched, itd. Dlatego absolutnie kluczowe jest, aby reducer obsłużył przypadek "nie znam tej akcji" i zwrócił niezmieniony stan.

Zwracanie state, nie undefined: Jeśli reducer zwróci undefined, Redux rzuci błąd: Given action "X", reducer "Y" returned undefined. To zabezpieczenie chroni przed częstym błędem polegającym na zapomnieniu default: w switch. Stan początkowy obsługujemy przez parametr domyślny state = initialState, co pozwala Reduxowi zainicjalizować store przy starcie poprzez akcję @@INIT.

Akcje cross-cutting: Czasem chcemy zareagować w wielu reducerach na tę samą akcję - na przykład akcja user/loggedOut powinna wyczyścić zarówno cart jak i preferences. To jest poprawne i pożądane: każdy slice obsługuje swoją część stanu w odpowiedzi na tę samą akcję. W Redux Toolkit do tego służy extraReducers lub builder.addCase / builder.addMatcher.

Konwencja nazewnicza akcji: Zalecaną konwencją jest domain/eventName (np. cart/itemAdded, auth/userLoggedIn). To stylistycznie odróżnia akcje od poszczególnych slice'ów i ułatwia śledzenie ich w DevTools.

Redux Toolkit zarządza tym automatycznie: createSlice z RTK generuje wewnętrznie poprawnego reducera, który zwraca stan dla nieobsłużonych akcji. Nie musimy pisać default ręcznie - to znacznie zmniejsza powierzchnię błędów.

Przykład kodu:

// Klasyczny reducer - obowiązkowy default
function todosReducer(state = [], action) {
  switch (action.type) {
    case 'todos/added':
      return [...state, action.payload];
    case 'todos/removed':
      return state.filter(t => t.id !== action.payload);
    default:
      // KRYTYCZNE - zwracamy stan dla nieznanych akcji
      return state;
  }
}

// Nasłuchiwanie cross-cutting action w wielu slice'ach
function cartReducer(state = { items: [] }, action) {
  switch (action.type) {
    case 'cart/itemAdded':
      return { ...state, items: [...state.items, action.payload] };
    case 'auth/userLoggedOut':
      // Reagujemy na akcję z innego slice'a
      return { items: [] };
    default:
      return state;
  }
}

// Redux Toolkit - extraReducers dla akcji spoza slice'a
import { createSlice, createAction } from '@reduxjs/toolkit';

const userLoggedOut = createAction('auth/userLoggedOut');

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] },
  reducers: {
    itemAdded(state, action) {
      state.items.push(action.payload);
    },
  },
  extraReducers: (builder) => {
    builder.addCase(userLoggedOut, (state) => {
      state.items = []; // Czyścimy koszyk po wylogowaniu
    });
  },
});

Materiały

Czym jest combineReducers i jak dzielić stan na slice'y?

Odpowiedź w 30 sekund: combineReducers to funkcja pomocnicza Redux, która łączy wiele "slice reducerów" w jeden root reducer. Każdy slice zarządza swoją gałęzią stanu niezależnie. To pozwala dzielić logikę dużej aplikacji na małe, testowalne moduły, zamiast pisać jeden monolityczny reducer.

Odpowiedź w 2 minuty: W miarę wzrostu aplikacji utrzymywanie całej logiki w jednym reducerze staje się niepraktyczne. Redux promuje wzorzec "reducer composition", gdzie stan dzielimy na logiczne fragmenty (slice'y), a każdy ma własnego reducera obsługującego tylko swoją część stanu.

Jak działa combineReducers: Funkcja przyjmuje obiekt mapujący nazwy slice'ów na ich reducery i zwraca jeden root reducer. Gdy akcja zostaje zdyspaczowana, root reducer wywołuje każdy slice reducer, przekazując mu odpowiadającą gałąź stanu. Wyniki łączy w jeden obiekt - kluczem jest nazwa slice'a.

Granice slice'ów: Slice'y powinny odpowiadać logicznym domenom aplikacji, nie typom akcji. Przykładowo: user, cart, products, ui. Każdy slice nic nie wie o innych - jego reducer otrzymuje tylko swoją gałąź stanu. To wymusza dobre rozdzielenie odpowiedzialności.

Stan globalny w selectorach: Skoro slice nie widzi reszty stanu, kalkulacje wymagające danych z wielu slice'ów (np. "wartość koszyka po zniżce użytkownika") robimy w selektorach, nie w reducerach. To utrzymuje reducery proste i niezależne.

Redux Toolkit i configureStore: W RTK używamy configureStore({ reducer: { cart: cartReducer, user: userReducer } }), co wewnętrznie wywołuje combineReducers. Każdy createSlice produkuje reducer gotowy do podłączenia. Dla bardziej złożonych przypadków (zagnieżdżone slice'y, dynamiczne reducery) możemy nadal jawnie wywołać combineReducers.

Przykład kodu:

// Klasyczne podejście Redux
import { combineReducers, createStore } from 'redux';

function userReducer(state = { name: null }, action) {
  switch (action.type) {
    case 'user/loggedIn':
      return { name: action.payload };
    default:
      return state;
  }
}

function cartReducer(state = { items: [] }, action) {
  switch (action.type) {
    case 'cart/itemAdded':
      return { ...state, items: [...state.items, action.payload] };
    default:
      return state;
  }
}

// Łączymy slice'y - wynikowy stan: { user: {...}, cart: {...} }
const rootReducer = combineReducers({
  user: userReducer,
  cart: cartReducer,
});

const store = createStore(rootReducer);
console.log(store.getState());
// { user: { name: null }, cart: { items: [] } }

// Redux Toolkit - tworzenie slice'ów i store'a
import { createSlice, configureStore } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: { name: null },
  reducers: {
    loggedIn(state, action) { state.name = action.payload; },
  },
});

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] },
  reducers: {
    itemAdded(state, action) { state.items.push(action.payload); },
  },
});

// configureStore wywołuje combineReducers pod spodem
const store = configureStore({
  reducer: {
    user: userSlice.reducer,
    cart: cartSlice.reducer,
  },
});

// Selektor łączący dane z wielu slice'ów
const selectCartItemCount = (state) => state.cart.items.length;
const selectUserName = (state) => state.user.name;

Materiały

Jak unikać mutacji stanu w Reducerach?

Odpowiedź w 30 sekund: Nigdy nie używamy metod mutujących jak push, pop, splice, sort, ani bezpośredniego przypisania (state.x = y). Zamiast tego tworzymy nowe obiekty/tablice używając operatora rozszerzenia ..., oraz nie-mutujących metod jak map, filter, concat. W Redux Toolkit createSlice używa Immera - możemy pisać "mutujący" kod, ale Immer pod spodem tworzy niezmienne kopie.

Odpowiedź w 2 minuty: Mutacja stanu to najczęstszy błąd w Redux. Stan musi być niezmienny, ponieważ React-Redux porównuje referencje (===) do decydowania o re-renderze. Mutacja zachowuje tę samą referencję, więc komponenty nie zauważą zmiany, a DevTools time-travel przestanie działać poprawnie.

Typowe pułapki mutacji w tablicach:

  • state.items.push(x) - dodaje element w miejscu
  • state.items.pop() / shift() - usuwa element w miejscu
  • state.items.splice(i, 1) - usuwa/wstawia w miejscu
  • state.items.sort() / reverse() - sortuje/odwraca w miejscu
  • state.items[0] = x - bezpośrednie przypisanie do indeksu

Typowe pułapki w obiektach:

  • state.user.name = 'X' - bezpośrednie przypisanie do pola
  • delete state.user - usunięcie pola
  • Object.assign(state, ...) - mutuje pierwszy argument!

Zalecane wzorce (niezmienne):

  • Dodawanie do tablicy: [...state.items, newItem]
  • Usuwanie: state.items.filter(x => x.id !== id)
  • Aktualizacja elementu: state.items.map(x => x.id === id ? { ...x, ...changes } : x)
  • Wstawianie: [...state.items.slice(0, i), newItem, ...state.items.slice(i)]
  • Sortowanie: [...state.items].sort() (kopiujemy przed sortowaniem)
  • Aktualizacja obiektu: { ...state, user: { ...state.user, name: 'X' } }

Redux Toolkit i Immer - rozwiązanie problemu: RTK używa biblioteki Immer, która pozwala pisać kod, który "wygląda jak mutacja", ale działa niezmiennie. Immer śledzi modyfikacje na specjalnym obiekcie proxy i na końcu tworzy nową, niezmienną kopię tylko dla zmienionych ścieżek. To znacząco upraszcza kod i eliminuje ryzyko błędów.

Wykrywanie mutacji: RTK ma wbudowane middleware serializableMiddleware oraz immutableMiddleware, które w trybie deweloperskim wykrywają mutacje i wyrzucają błąd. To bezpiecznik chroniący przed trudnymi do wykrycia błędami.

Przykład kodu:

// === TYPOWE PUŁAPKI - NIGDY TAK NIE RÓB ===
function badReducer(state, action) {
  switch (action.type) {
    case 'todos/added':
      state.todos.push(action.payload);      // BŁĄD: push mutuje
      return state;
    case 'todos/removed':
      state.todos.splice(action.payload, 1); // BŁĄD: splice mutuje
      return state;
    case 'todos/sorted':
      state.todos.sort();                    // BŁĄD: sort mutuje
      return state;
    case 'user/renamed':
      state.user.name = action.payload;      // BŁĄD: przypisanie mutuje
      return state;
    case 'user/cleared':
      delete state.user;                     // BŁĄD: delete mutuje
      return state;
  }
}

// === ZALECANE WZORCE - NIEZMIENNE ===
function goodReducer(state, action) {
  switch (action.type) {
    case 'todos/added':
      // Spread tworzy nową tablicę
      return { ...state, todos: [...state.todos, action.payload] };

    case 'todos/removed':
      // filter zwraca nową tablicę
      return {
        ...state,
        todos: state.todos.filter(t => t.id !== action.payload),
      };

    case 'todos/updated':
      // map tworzy nową tablicę, spread - nowy obiekt elementu
      return {
        ...state,
        todos: state.todos.map(t =>
          t.id === action.payload.id ? { ...t, ...action.payload.changes } : t
        ),
      };

    case 'todos/sorted':
      // Kopia przed sortowaniem
      return { ...state, todos: [...state.todos].sort() };

    case 'user/renamed':
      // Zagnieżdżony spread
      return { ...state, user: { ...state.user, name: action.payload } };

    case 'user/cleared': {
      // Destrukturyzacja zamiast delete
      const { user, ...rest } = state;
      return rest;
    }

    default:
      return state;
  }
}

// === REDUX TOOLKIT Z IMMER - "wygląda jak mutacja, ale nie jest" ===
import { createSlice } from '@reduxjs/toolkit';

const todosSlice = createSlice({
  name: 'todos',
  initialState: { items: [], user: { name: '' } },
  reducers: {
    // Pod spodem Immer tworzy niezmienne kopie!
    added(state, action) {
      state.items.push(action.payload);        // OK z Immerem
    },
    removed(state, action) {
      const i = state.items.findIndex(t => t.id === action.payload);
      state.items.splice(i, 1);                // OK z Immerem
    },
    renamed(state, action) {
      state.user.name = action.payload;        // OK z Immerem
    },
    // Jeśli ZWRACAMY wartość, Immer użyje jej zamiast śledzić mutacje
    reset() {
      return { items: [], user: { name: '' } };
    },
  },
});

Materiały

Redux Toolkit (RTK)

21. Czym jest Redux Toolkit i dlaczego jest rekomendowany?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
22. Jak używać createSlice do definiowania reducerów i akcji?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
23. Czym jest Immer i jak upraszcza pisanie reducerów w RTK?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
24. Jak skonfigurować Store za pomocą configureStore z RTK?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
25. Czym jest createAsyncThunk i jak obsługuje operacje asynchroniczne?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
26. Jak używać createEntityAdapter do normalizacji danych?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

React-Redux

27. Czym jest React-Redux i jaką rolę pełni Provider?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
28. Jak działa hook useSelector i jakie są zasady jego używania?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
29. Czym jest useDispatch i jak wysyłać akcje z komponentów?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
30. Jaka jest różnica między useSelector a connect (HOC)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
31. Jak unikać niepotrzebnych renderowań przy użyciu useSelector?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
32. Czym jest shallowEqual i kiedy go używać?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Middleware

33. Czym jest Middleware w Redux i jak działa?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
34. Jak napisać własne middleware w Redux?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
35. Czym jest Redux Thunk i jak obsługuje akcje asynchroniczne?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
36. Jaka jest różnica między Redux Thunk a Redux Saga?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
37. Kiedy warto użyć Redux Saga zamiast Thunk?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
38. Jak działa applyMiddleware i w jakiej kolejności wykonują się middleware?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Selektory i Reselect

39. Czym są selektory i dlaczego warto je stosować?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
40. Jak działa createSelector z biblioteki Reselect?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
41. Czym jest memoizacja selektorów i jakie daje korzyści wydajnościowe?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
42. Jak komponować selektory (selector composition)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
43. Kiedy selektor traci memoizację i jak temu zapobiegać?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Testowanie Redux

44. Jak testować Reducery w izolacji?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
45. Jak testować Action Creators i asynchroniczne akcje (Thunks)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 25 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Chcesz poznać wszystkie odpowiedzi?

Uzyskaj pełny dostęp do 45 pytań rekrutacyjnych z Redux oraz pozostałych technologii.

Zobacz plany cenowe