--- id: 5a24c314108439a4d4036149 title: Вилучення Local State в Redux challengeType: 6 forumTopicId: 301428 dashedName: extract-local-state-into-redux --- # --description-- Майже готово! Пам'ятайте, що ви написали весь код Redx, так щоб Redux міг контролювати управління станом вашого додатка повідомлень в React. Тепер, коли Redux підключений, вам потрібно витягти управління станами з компонента `Presentational` і перемістити його в Redux. У даний момент, ваш Redux підключений, але ви обробляєте стан локально всередині компонента `Presentational`. # --instructions-- В компоненті `Presentational`, спершу, видаліть значення `messages` в локальному стані `state`. Управління повідомленнями буде здійснюватись в Redux. Далі, змініть метод `submitMessage()`, так щоб він відправляв `submitNewMessage()` з `this.props` і передайте йому поточний ввід повідомлень з локального `state` як аргумент. Через те, що ви видалили `messages` з локального стану, видаліть властивість/значення `messages` з виклику до `this.setState()` також. Нарешті, змініть метод `render()`, так щоб він відображав повідомлення отримані з `props`, а не з `state`. Як тільки ці зміни будуть внесені, застосунок продовжить функціонувати так само, за винятком того, що тепер Redux керує станом. Цей приклад також демонструє, як компонент може мати локальний `state`: ваш компонент все ще відслідковує вхідні дані користувача локально у своєму власному `state`. Ви можете побачити, як Redux надає зручний фреймворк управління станом на основі React. Ви досягли того ж результату використовуючи тільки локальний стан React спершу і це, зазвичай, можливо з простими додатками. Однак, в той час як ваші додатки ставатимуть масштабнішими і складнішими, і ваше управління станом зазнаватиме тих же змін, саме цю проблему вирішуватиме Redux. # --hints-- `AppWrapper` повинен відобразитися на сторінці. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })() ); ``` `Presentational` компонент повинен відобразитися на сторінці. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })() ); ``` Компонент `Presentational` повинен відобразити елементи `h2`, `input`, `button` і `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 ); })() ); ``` Компонент `Presentational` повинен отримувати `messages` зі сховища Redux у якості пропсу. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })() ); ``` Компонент `Presentational` повинен отримувати `submitMessage` виконавця як пропс. ```js assert( (function () { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })() ); ``` Стан компоненту `Presentational` повинен містити одну властивість/значення, `input`, яке ініціалізується на порожній рядок. ```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 ); })() ); ``` Введення в елемент `input` повинно оновити стан компоненту `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__' ); }; ``` Відправлення `submitMessage` на компонент `Presentational` має оновити 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 === '' ); }; ``` Компонент `Presentational` має відображати `messages` зі сховища 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 ( ); } }; ```