--- id: 5a24c314108439a4d4036149 title: Estrai lo stato locale in Redux challengeType: 6 forumTopicId: 301428 dashedName: extract-local-state-into-redux --- # --description-- Hai quasi finito! Ricorda che hai scritto tutto il codice Redux in modo che Redux possa controllare la gestione dello stato della tua app di messaggi React. Ora che Redux è connesso, è necessario estrarre la gestione dello stato dal componente `Presentational` per metterla in Redux. Attualmente, hai collegato Redux, ma stai gestendo lo stato localmente all'interno del componente `Presentational`. # --instructions-- Nel componente `Presentational`, per prima cosa, rimuovi la proprietà `messages` nello `state` locale. Questi messaggi saranno gestiti da Redux. Successivamente, modifica il metodo `submitMessage()` in modo che invii `submitNewMessage()` da `this.props`, e gli passi l'input del messaggio corrente prendendolo dallo `state` locale come argomento. Poiché hai rimosso `messages` dallo stato locale, rimuovi la proprietà `messages` dalla chiamata a `this.setState()` anche qui. Infine, modifica il metodo `render()` in modo che mappi i messaggi ricevuti da `props` piuttosto che dallo `state`. Una volta che queste modifiche saranno state fatte, l'applicazione continuerà a funzionare ugualmente, solo che sarà Redux a gestire lo stato. Questo esempio illustra anche come un componente può avere uno `state` locale: il tuo componente traccia ancora l'input dell'utente localmente nel proprio `state`. Puoi vedere come Redux fornisce un utile framework di gestione dello stato basato su React. Hai raggiunto inizialmente lo stesso risultato usando solo lo stato locale di React, e questo è di solito possibile con applicazioni semplici. Tuttavia, mano a mano che le applicazioni diventeranno più grandi e più complesse, lo stesso farà lo stato, e questo è il problema risolto da Redux. # --hints-- L' `AppWrapper` dovrebbe essere presentata nella pagina. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })() ); ``` Il componente `Presentational` dovrebbe essere presentato nella pagina. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })() ); ``` Il componente `Presentational` dovrebbe fare il render degli elementi `h2`, `input`, `button` e `ul`. ```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 ); })() ); ``` Il componente `Presentational` dovrebbe ricevere `messages` dallo store Redux come prop. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })() ); ``` Il componente `Presentational` dovrebbe ricevere il creatore dell'azione `submitMessage` come prop. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })() ); ``` Lo stato del componente `Presentational` dovrebbe contenere una proprietà, `input`, che è inizializzata a una stringa vuota. ```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 ); })() ); ``` Digitando nell'elemento `input` si dovrebbe aggiornare lo stato del componente `Presentational`. ```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__' ); }; ``` La spedizione del `submitMessage` nel componente `Presentational` dovrebbe aggiornare lo store di Redux e cancellare l'input nello stato locale. ```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 === '' ); }; ``` Il componente `Presentational` dovrebbe presentare i `messages` dallo store di Redux. ```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 ( ); } }; ```