Was ist Redux und warum ist das für React so wichtig?
In React gibt es drei verschiedene Arten von einem State. Ein State representiert Daten, die sich bei der Änderungen auf die Benutzeroberfläche auswirken sollen.
- Local State
- gilt in einer einzelnen Komponente
- Bsp: toggle für mehr Details, user Inputs
- useState() /useReducer()
- Cross-component State
- gilt in verschiedenen Komponenten
- Öffnen/Schließen eines Modals (Pop ups)
- requires “prop drilling” or react context/redux
- App-wide State
- gilt in der kompletten App
- Bsp: User Authentifizierung oder Theme
- requires “prop drilling” or react context/redux
Redux hilft ein State über verschiedene Komponenten zu nutzen.
Warum Redux wenn React Context API existiert?
Es gibt verschiedene Nachteile der React Context API:
-
Verschachtelte Provider führen zu hohen Wartungsaufwand
-
Komplexe Provider
Wie funktioniert Redux?

- Central Data (State) Store für die komplette Anwendung
- Speicherung des States
- Components
- holen Daten (Subscription) aus data store
- Änderungen erfolgen durch actions ans reducer Funktionen, welche Daten im store manipulieren
//redux-demo.js
const redux = require('redux');
const counterReducer = (state = { counter: 0 }, action) => {
if (action.type === 'increment') {
return {
counter: state.counter + 1
};
}
if (action.type === 'decrement') {
return {
counter: state.counter - 1
};
}
return state;
};
const store = redux.createStore(counterReducer);
console.log(store.getState());
const counterSubscriber = () => {
const latestState = store.getState();
console.log(latestState);
};
store.subscribe(counterSubscriber);
store.dispatch({ type: 'increment'});
store.dispatch({ type: 'decrement' });
//log results
// {counter: 0}
// {counter: 1}
// {counter: 0}
-
Reducer Function
- ist eine reine Funktion
- Input
- Old state + dispachted action
- Output
- new state object
-
npm install redux react-redux (React Spezifisch)
-
react store erstellen
//store/index.js
import {createStore} from 'redux';
const counterReducer = (state = {counter: 0}, action) => {
switch (action.type) {
case 'increment':
return {counter: state.counter + 1,}
case 'decrement':
return {counter: state.counter - 1,}
default:
return state;
}
}
const store = createStore(counterReducer);
export default store;
- react store in React App einbinden
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import {Provider} from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}>
<App/>
</Provider>);
- Zugriff in einer React Komponente auf Redux store
- useSelector()
//Counter.js
import classes from './Counter.module.css';
import {useSelector} from "react-redux";
const Counter = () => {
const counter = useSelector(state => state.counter);
const toggleCounterHandler = () => {
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counter}</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
- Manipulation des Stores
- useDispatch()
//Counter.js
import classes from './Counter.module.css';
import {useDispatch, useSelector} from "react-redux";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
const incrementHandler = () => {
dispatch({type: 'increment'});
}
const decrementHandler = () => {
dispatch({type: 'decrement'});
}
const toggleCounterHandler = () => {
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counter}</div>
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
Wie kann ich Variablen an den Store übergeben, wenn beispielsweise der Counter um X erhöht werden soll?
//store/index.js
const counterReducer = (state = {counter: 0}, action) => {
switch (action.type) {
case 'increment':
return {counter: state.counter + 1,}
case 'decrement':
return {counter: state.counter - 1,}
case 'increase':
return {counter: state.counter + action.amount,}
default:
return state;
}
}
//Counter.js
const increaseHandler = () => {
dispatch({type: 'increase', amount: 5});
}
- redux cache verwenden um Elemente ein/-aus zu blenden
//store.index.js
import {createStore} from 'redux';
const initialState = {
counter: 0,
showCounter: true
}
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'increment':
return {
counter: state.counter + 1,
showCounter: state.showCounter
}
case 'decrement':
return {
counter: state.counter - 1,
showCounter: state.showCounter
}
case 'increase':
return {
counter: state.counter + action.payload,
showCounter: state.showCounter
}
case 'toggle':
return {
counter: state.counter,
showCounter: !state.showCounter
}
default:
return state;
}
}
const store = createStore(counterReducer);
export default store;
//Counter.js
import classes from './Counter.module.css';
import {useDispatch, useSelector} from "react-redux";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
const show = useSelector(state => state.showCounter);
const incrementHandler = () => {
dispatch({type: 'increment'});
}
const increaseHandler = () => {
dispatch({type: 'increase', payload: 5});
}
const decrementHandler = () => {
dispatch({type: 'decrement'});
}
const toggleCounterHandler = () => {
dispatch({type: 'toggle'});
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{show && <div className={classes.value}>{counter}</div> }
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={increaseHandler}>Increse by 5</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
- der Rückgabewerte eines Reducer überschreibt den ursprünglichen Wert
- das soll nicht gemacht werden:
//store/index.js
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'increment':
//geht aber soll nicht gemacht werden
state.counter++;
return state;
default:
return state;
}
}
-
immer ein neues Objekt oder Array zurückgeben
-
potenzielle Probleme
- Schreibfehler bei Action types -> CONST
- je mehr states hinzugefügt werden, desto größer wird das zurückgegebene Objekt/Array eines Reducer
- dafür sorgen, dass immer ein neues Objekt/Array zurückgegeben wird
-
Lösung: Redux Toolkit
Wie strukturiere ich die States mit Redux?
- pro State ein Slice
- in einem Slice wird mithilfe von Immer ein neues Objekt/Array zurückgegeben
- action wird nur benötigt wenn variablen mitgegeben werden sollen
//store/index.js
createSlice({
name: "counter",
initialState: initialState,
reducers: {
increment: (state) => {
state.counter++;
},
decrement: (state) => {
state.counter--;
},
increase: (state, action) => {
state.counter = state.counter + action.amount;
},
toggleCounter: (state) => {
state.showCounter = !state.showCounter;
}
}
});
- um die Slice zu nutzen wird configureStore verwendet, wenn es mehrere Slices verwendet werden, kann ein Objekt definiert werden und übergeben werden
//store/index.js
import {createStore} from 'redux';
import {createSlice, configureStore} from "@reduxjs/toolkit";
const initialState = {
counter: 0,
showCounter: true
}
const counterSlice = createSlice({
name: "counter",
initialState: initialState,
reducers: {
increment: (state) => {
state.counter++;
},
decrement: (state) => {
state.counter--;
},
increase: (state, action) => {
state.counter = state.counter + action.amount;
},
toggleCounter: (state) => {
state.showCounter = !state.showCounter;
}
}
});
const store = configureStore({
reducer: counterSlice.reducer,
});
export default store;
Wie kann der State mit Redux Toolkit geändert werden?
//Counter.js
import classes from './Counter.module.css';
import {useDispatch, useSelector} from "react-redux";
import {counterActions} from "../store";
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
const show = useSelector(state => state.showCounter);
const incrementHandler = () => {
dispatch(counterActions.increment());
}
const increaseHandler = () => {
dispatch(counterActions.increase(5));
}
const decrementHandler = () => {
dispatch(counterActions.decrement());
}
const toggleCounterHandler = () => {
dispatch(counterActions.toggleCounter());
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{show && <div className={classes.value}>{counter}</div> }
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={increaseHandler}>Increse by 5</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
Wie können mehrere Slices definiert werden? Beispiel anhand Login /Logout
//store/index.js
import {configureStore} from "@reduxjs/toolkit";
import counterSlice from "./counter";
import authSlice from "./auth";
const store = configureStore({
reducer: {counter: counterSlice, auth: authSlice},
});
export default store;
//auth.js
import {createSlice} from "@reduxjs/toolkit";
const initialAuthState = {
isAuthenticated: false,
};
const authSlice = createSlice({
name: "authentication",
initialState: initialAuthState,
reducers: {
login(state) {
state.isAuthenticated = true;
},
logout(state) {
state.isAuthenticated = false;
}
}
});
export const authActions = authSlice.actions;
export default authSlice.reducer;
//App.js
import Counter from './components/Counter';
import {Fragment} from "react";
import Header from "./components/Header";
import Auth from "./components/Auth";
import {useSelector} from "react-redux";
import UserProfile from "./components/UserProfile";
function App() {
const isAuth = useSelector(state => state.auth.isAuthenticated);
return (
<Fragment>
<Header/>
{!isAuth && <Auth/>}
{isAuth && <UserProfile/>}
<Counter/>
</Fragment>
);
}
export default App;
//Auth.js
import classes from './Auth.module.css';
import {useDispatch} from "react-redux";
import {authActions} from "../store/auth";
const Auth = () => {
const dispatch = useDispatch();
function loginHandler(event) {
event.preventDefault();
dispatch(authActions.login())
}
return (
<main className={classes.auth}>
<section>
<form onSubmit={loginHandler}>
<div className={classes.control}>
<label htmlFor='email'>Email</label>
<input type='email' id='email'/>
</div>
<div className={classes.control}>
<label htmlFor='password'>Password</label>
<input type='password' id='password'/>
</div>
<button>Login</button>
</form>
</section>
</main>
);
};
export default Auth;
//Header.js
import classes from './Header.module.css';
import {useDispatch, useSelector} from "react-redux";
import {authActions} from "../store/auth";
const Header = () => {
const dispatch = useDispatch();
const isAuth = useSelector(state => state.auth.isAuthenticated);
function handleLogout() {
dispatch(authActions.logout());
}
return (
<header className={classes.header}>
<h1>Redux Auth</h1>
{isAuth && (
<nav>
<ul>
<li>
<a href='/'>My Products</a>
</li>
<li>
<a href='/'>My Sales</a>
</li>
<li>
<button onClick={handleLogout}>Logout</button>
</li>
</ul>
</nav>
)}
</header>
);
};
export default Header;
Projek Webshop mithilfe Redux
- Kernfunktionalitäten
- Redux um Warenkorb anzeigen/ausblenden
- Redux um Produkte zu einem Warenkorb hinzuzufügen/entfernen

