--- id: 5a24c314108439a4d4036149 title: Lokalen Zustand in Redux extrahieren challengeType: 6 forumTopicId: 301428 dashedName: extract-local-state-into-redux --- # --description-- Du hast es fast geschafft! Erinnere dich daran, dass du den gesamten Redux-Code geschrieben hast, damit Redux die Zustandsverwaltung deiner React-Messages-App steuern kann. Jetzt, wo Redux angebunden ist, musst du die Zustandsverwaltung aus der Komponente `Presentational` herausnehmen und in Redux integrieren. Im Moment hast du Redux verbunden, aber du handhabst den Zustand lokal innerhalb der Komponente `Presentational`. # --instructions-- Entferne in der Komponente `Presentational` zuerst die Eigenschaft `messages` im lokalen `state`. Diese Nachrichten werden von Redux verwaltet. Als Nächstes änderst du die Methode `submitMessage()` so, dass sie `submitNewMessage()` von `this.props` aus auslöst, und übergibst die aktuelle Nachrichteneingabe vom lokalen `state` als Argument. Da du `messages` aus dem lokalen Zustand entfernt hast, entferne auch hier die Eigenschaft `messages` aus dem Aufruf von `this.setState()`. Schließlich änderst du die `render()`-Methode so, dass sie die Nachrichten von `props` und nicht von `state` übernimmt. Sobald diese Änderungen vorgenommen wurden, funktioniert die App weiterhin wie bisher, nur dass Redux den Zustand verwaltet. Dieses Beispiel zeigt auch, wie eine Komponente einen lokalen `state` haben kann: Deine Komponente verfolgt die Benutzereingaben immer noch lokal in ihrem eigenen `state`. Wie du siehst, bietet Redux ein nützliches Framework zur Zustandsverwaltung auf der Basis von React. Du hast das gleiche Ergebnis erzielt, indem du zunächst nur den lokalen Zustand von React verwendet hast, und das ist bei einfachen Apps normalerweise möglich. Doch je größer und komplexer deine Apps werden, desto schwieriger wird auch die Zustandsverwaltung, und genau dieses Problem löst Redux. # --hints-- Der `AppWrapper` sollte auf der Seite gerendert werden. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })() ); ``` Die Komponente `Presentational` sollte auf der Seite gerendert werden. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })() ); ``` Die Komponente `Presentational` sollte ein `h2`, `input`, `button` und `ul`-Element rendern. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); return ( PresentationalComponent.find('div').length === 1 && PresentationalComponent.find('h2').length === 1 && PresentationalComponent.find('button').length === 1 && PresentationalComponent.find('ul').length === 1 ); })() ); ``` Die Komponente `Presentational` sollte `messages` aus dem Redux-Store als Eigenschaft erhalten. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })() ); ``` Die Komponente `Presentational` sollte den `submitMessage` Action Creator als Eigenschaft erhalten. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })() ); ``` Der Zustand der Komponente `Presentational` sollte eine Eigenschaft `input` erhalten, die mit einem leeren String initialisiert wird. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalState = mockedComponent .find('Presentational') .instance().state; return ( typeof PresentationalState.input === 'string' && Object.keys(PresentationalState).length === 1 ); })() ); ``` Die Eingabe in das `input`-Element sollte den Zustand der Komponente `Presentational` aktualisieren. ```js async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const testValue = '__MOCK__INPUT__'; const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v } }); let initialInput = mockedComponent.find('Presentational').find('input'); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent); }; const updated = await changed(); const updatedInput = updated.find('Presentational').find('input'); assert( initialInput.props().value === '' && updatedInput.props().value === '__MOCK__INPUT__' ); }; ``` Das Senden der `submitMessage` an die Komponente `Presentational` sollte den Redux-Store aktualisieren und die Eingabe im lokalen Zustand löschen. ```js async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); let beforeProps = mockedComponent.find('Presentational').props(); const testValue = '__TEST__EVENT__INPUT__'; const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v } }); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent); }; const clickButton = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent); }; const afterChange = await changed(); const afterChangeInput = afterChange.find('input').props().value; const afterClick = await clickButton(); const afterProps = mockedComponent.find('Presentational').props(); assert( beforeProps.messages.length === 0 && afterChangeInput === testValue && afterProps.messages.pop() === testValue && afterClick.find('input').props().value === '' ); }; ``` Die Komponente `Presentational` soll die `messages` aus dem Redux-Store wiedergeben. ```js async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); let beforeProps = mockedComponent.find('Presentational').props(); const testValue = '__TEST__EVENT__INPUT__'; const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v } }); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent); }; const clickButton = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent); }; const afterChange = await changed(); const afterChangeInput = afterChange.find('input').props().value; const afterClick = await clickButton(); const afterProps = mockedComponent.find('Presentational').props(); assert( beforeProps.messages.length === 0 && afterChangeInput === testValue && afterProps.messages.pop() === testValue && afterClick.find('input').props().value === '' && afterClick.find('ul').childAt(0).text() === testValue ); }; ``` # --seed-- ## --after-user-code-- ```jsx ReactDOM.render(, document.getElementById('root')) ``` ## --seed-contents-- ```jsx // Redux: const ADD = 'ADD'; const addMessage = (message) => { return { type: ADD, message: message } }; const messageReducer = (state = [], action) => { switch (action.type) { case ADD: return [ ...state, action.message ]; default: return state; } }; const store = Redux.createStore(messageReducer); // React: const Provider = ReactRedux.Provider; const connect = ReactRedux.connect; // Change code below this line class Presentational extends React.Component { constructor(props) { super(props); this.state = { input: '', messages: [] } this.handleChange = this.handleChange.bind(this); this.submitMessage = this.submitMessage.bind(this); } handleChange(event) { this.setState({ input: event.target.value }); } submitMessage() { this.setState((state) => ({ input: '', messages: state.messages.concat(state.input) })); } render() { return (

Type in a new Message:


); } }; // Change code above this line const mapStateToProps = (state) => { return {messages: state} }; const mapDispatchToProps = (dispatch) => { return { submitNewMessage: (message) => { dispatch(addMessage(message)) } } }; const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational); class AppWrapper extends React.Component { render() { return ( ); } }; ``` # --solutions-- ```jsx // Redux: const ADD = 'ADD'; const addMessage = (message) => { return { type: ADD, message: message } }; const messageReducer = (state = [], action) => { switch (action.type) { case ADD: return [ ...state, action.message ]; default: return state; } }; const store = Redux.createStore(messageReducer); // React: const Provider = ReactRedux.Provider; const connect = ReactRedux.connect; // Change code below this line class Presentational extends React.Component { constructor(props) { super(props); this.state = { input: '' } this.handleChange = this.handleChange.bind(this); this.submitMessage = this.submitMessage.bind(this); } handleChange(event) { this.setState({ input: event.target.value }); } submitMessage() { this.props.submitNewMessage(this.state.input); this.setState({ input: '' }); } render() { return (

Type in a new Message:


); } }; // Change code above this line const mapStateToProps = (state) => { return {messages: state} }; const mapDispatchToProps = (dispatch) => { return { submitNewMessage: (message) => { dispatch(addMessage(message)) } } }; const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational); class AppWrapper extends React.Component { render() { return ( ); } }; ```