* chore: rename APIs and Microservices to include "Backend" (#42515) * fix typo * fix typo * undo change * Corrected grammar mistake Corrected a grammar mistake by removing a comma. * change APIs and Microservices cert title * update title * Change APIs and Microservices certi title * Update translations.json * update title * feat(curriculum): rename apis and microservices cert * rename folder structure * rename certificate * rename learn Markdown * apis-and-microservices -> back-end-development-and-apis * update backend meta * update i18n langs and cypress test Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * fix: add development to front-end libraries (#42512) * fix: added-the-word-Development-to-front-end-libraries * fix/added-the-word-Development-to-front-end-libraries * fix/added-word-development-to-front-end-libraries-in-other-related-files * fix/added-the-word-Development-to-front-end-and-all-related-files * fix/removed-typos-from-last-commit-in-index.md * fix/reverted-changes-that-i-made-to-dependecies * fix/removed xvfg * fix/reverted changes that i made to package.json * remove unwanted changes * front-end-development-libraries changes * rename backend certSlug and README * update i18n folder names and keys * test: add legacy path redirect tests This uses serve.json from the client-config repo, since we currently use that in production * fix: create public dir before moving serve.json * fix: add missing script * refactor: collect redirect tests * test: convert to cy.location for stricter tests * rename certificate folder to 00-certificates * change crowdin config to recognise new certificates location * allow translations to be used Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com> * add forwards slashes to path redirects * fix cypress path tests again * plese cypress * fix: test different challenge Okay so I literally have no idea why this one particular challenge fails in Cypress Firefox ONLY. Tom and I paired and spun a full build instance and confirmed in Firefox the page loads and redirects as expected. Changing to another bootstrap challenge passes Cypress firefox locally. Absolutely boggled by this. AAAAAAAAAAAAAAA * fix: separate the test Okay apparently the test does not work unless we separate it into a different `it` statement. >:( >:( >:( >:( Co-authored-by: Sujal Gupta <55016909+heysujal@users.noreply.github.com> Co-authored-by: Noor Fakhry <65724923+NoorFakhry@users.noreply.github.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>
398 lines
11 KiB
Markdown
398 lines
11 KiB
Markdown
---
|
|
id: 5a24c314108439a4d4036149
|
|
title: Extrae el estado local en Redux
|
|
challengeType: 6
|
|
forumTopicId: 301428
|
|
dashedName: extract-local-state-into-redux
|
|
---
|
|
|
|
# --description--
|
|
|
|
¡Ya casi terminas! Recuerda que escribiste todo el código Redux para que Redux pudiera controlar la gestión del estado de tu aplicación de mensajes React. Ahora que Redux está conectado, necesitas extraer la gestión del estado de `Presentational` y añadirlo a Redux. Actualmente, tienes Redux conectado, pero estás manejando el estado localmente dentro del componente `Presentational`.
|
|
|
|
# --instructions--
|
|
|
|
En el componente `Presentational`, primero elimina la propiedad `messages` del local `state`. Estos mensajes serán gestionados por Redux. A continuación, modifica el método `submitMessage()` para que `submitNewMessage()` trabaje desde `this.props` y pase la entrada del mensaje actual desde el `state` local como un argumento. Ya que eliminaste `messages` desde el estado local, elimina también la propiedad `messages` de la llamada a `this.setState()`. Finalmente, modifica el método `render()` para que asigne los mensajes recibidos desde `props` en lugar de `state`.
|
|
|
|
Una vez realizados estos cambios, la aplicación seguirá funcionando igual, salvo que Redux gestiona el estado. Este ejemplo también ilustra cómo un componente puede tener un `state` local: tu componente aún registra la entrada del usuario localmente en su propio `state`. Puedes ver cómo Redux proporciona un framework útil de gestión de estados sobre React. Alcanzaste el mismo resultado usando solo el estado local de React al principio, y esto es generalmente posible con aplicaciones simples. Sin embargo, cuanto más complejas y grandes se vuelve tus aplicaciones, más lo hará la gestión del estado, y esto es el problema que Redux resuelve.
|
|
|
|
# --hints--
|
|
|
|
`AppWrapper` debe renderizarse.
|
|
|
|
```js
|
|
assert(
|
|
(function () {
|
|
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
|
return mockedComponent.find('AppWrapper').length === 1;
|
|
})()
|
|
);
|
|
```
|
|
|
|
El componente `Presentational` debe renderizarse.
|
|
|
|
```js
|
|
assert(
|
|
(function () {
|
|
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
|
return mockedComponent.find('Presentational').length === 1;
|
|
})()
|
|
);
|
|
```
|
|
|
|
El componente `Presentational` debe renderizar los siguientes elementos: `h2`, `input`, `button` y `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
|
|
);
|
|
})()
|
|
);
|
|
```
|
|
|
|
El componente `Presentational` debe recibir `messages` desde el almacenamiento de Redux como una propiedad.
|
|
|
|
```js
|
|
assert(
|
|
(function () {
|
|
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
|
const PresentationalComponent = mockedComponent.find('Presentational');
|
|
const props = PresentationalComponent.props();
|
|
return Array.isArray(props.messages);
|
|
})()
|
|
);
|
|
```
|
|
|
|
El componente `Presentational` debe recibir `submitMessage` como una propiedad.
|
|
|
|
```js
|
|
assert(
|
|
(function () {
|
|
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
|
const PresentationalComponent = mockedComponent.find('Presentational');
|
|
const props = PresentationalComponent.props();
|
|
return typeof props.submitNewMessage === 'function';
|
|
})()
|
|
);
|
|
```
|
|
|
|
El estado del componente `Presentational` debe contener una propiedad, `input`, que está inicializada a una cadena vacía.
|
|
|
|
```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
|
|
);
|
|
})()
|
|
);
|
|
```
|
|
|
|
Escribir el elemento `input` debe actualizar el estado 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__'
|
|
);
|
|
};
|
|
```
|
|
|
|
`submitMessage` del componente `Presentational` debe actualizar el almacenamiento de Redux y vaciar la entrada en el estado local.
|
|
|
|
```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 === ''
|
|
);
|
|
};
|
|
```
|
|
|
|
El componente `Presentational` debe renderizar `messages` desde el almacenamiento de 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(<AppWrapper />, 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 (
|
|
<div>
|
|
<h2>Type in a new Message:</h2>
|
|
<input
|
|
value={this.state.input}
|
|
onChange={this.handleChange}/><br/>
|
|
<button onClick={this.submitMessage}>Submit</button>
|
|
<ul>
|
|
{this.state.messages.map( (message, idx) => {
|
|
return (
|
|
<li key={idx}>{message}</li>
|
|
)
|
|
})
|
|
}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
// 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 (
|
|
<Provider store={store}>
|
|
<Container/>
|
|
</Provider>
|
|
);
|
|
}
|
|
};
|
|
```
|
|
|
|
# --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 (
|
|
<div>
|
|
<h2>Type in a new Message:</h2>
|
|
<input
|
|
value={this.state.input}
|
|
onChange={this.handleChange}/><br/>
|
|
<button onClick={this.submitMessage}>Submit</button>
|
|
<ul>
|
|
{this.props.messages.map( (message, idx) => {
|
|
return (
|
|
<li key={idx}>{message}</li>
|
|
)
|
|
})
|
|
}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
// 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 (
|
|
<Provider store={store}>
|
|
<Container/>
|
|
</Provider>
|
|
);
|
|
}
|
|
};
|
|
```
|