feat: add 'back/front end' in curriculum (#42596)
* 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>
This commit is contained in:
@ -0,0 +1,156 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036147
|
||||
title: Conecta Redux a React
|
||||
challengeType: 6
|
||||
forumTopicId: 301426
|
||||
dashedName: connect-redux-to-react
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Ahora que has escrito tanto la función `mapStateToProps()` como la función `mapDispatchToProps()`, puedes usarlos para asignar `state` y `dispatch` a las `props` de uno de tus componentes React. El método `connect` de React Redux puede manejar esta tarea. Este método toma dos argumentos opcionales, `mapStateToProps()` y `mapDispatchToProps()`. Son opcionales porque puedes tener un componente que solo necesita acceso al `state` pero no necesita enviar ninguna acción, o viceversa.
|
||||
|
||||
Para utilizar este método, pasa las funciones como argumentos, y llama inmediatamente al resultado con tu componente. Esta sintaxis es un poco inusual y se ve así:
|
||||
|
||||
```js
|
||||
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
|
||||
```
|
||||
|
||||
**Nota:** Si deseas omitir uno de los argumentos del método `connect`, pasa `null` en su lugar.
|
||||
|
||||
# --instructions--
|
||||
|
||||
El editor de código tiene las funciones `mapStateToProps()` y `mapDispatchToProps()` y un nuevo componente React llamado `Presentational`. Conecta este componente a Redux con el método `connect` del objeto global `ReactRedux`, y llámalo inmediatamente en el componente `Presentational`. Asigna el resultado a una nueva `const` llamada `ConnectedComponent` que representa el componente conectado. Eso es todo, ¡ahora estás conectado a Redux! Intenta cambiar cualquiera de los argumentos de `connect` a `null` y observa los resultados de la prueba.
|
||||
|
||||
# --hints--
|
||||
|
||||
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 recibir una prop `messages` a través de `connect`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const props = mockedComponent.find('Presentational').props();
|
||||
return props.messages === '__INITIAL__STATE__';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El componente `Presentational` debe recibir una prop `submitNewMessage` a través de `connect`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const props = mockedComponent.find('Presentational').props();
|
||||
return typeof props.submitNewMessage === 'function';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
const store = Redux.createStore(
|
||||
(state = '__INITIAL__STATE__', action) => state
|
||||
);
|
||||
class AppWrapper extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ReactRedux.Provider store = {store}>
|
||||
<ConnectedComponent/>
|
||||
</ReactRedux.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
messages: state
|
||||
}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (message) => {
|
||||
dispatch(addMessage(message));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <h3>This is a Presentational Component</h3>
|
||||
}
|
||||
};
|
||||
|
||||
const connect = ReactRedux.connect;
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
messages: state
|
||||
}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (message) => {
|
||||
dispatch(addMessage(message));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <h3>This is a Presentational Component</h3>
|
||||
}
|
||||
};
|
||||
|
||||
const connect = ReactRedux.connect;
|
||||
// Change code below this line
|
||||
|
||||
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
```
|
@ -0,0 +1,300 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036148
|
||||
title: Conecta Redux a la aplicación de mensajes
|
||||
challengeType: 6
|
||||
forumTopicId: 301427
|
||||
dashedName: connect-redux-to-the-messages-app
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Ahora que entiendes cómo usar `connect` para conectar React a Redux, puedes aplicar lo que has aprendido a tu componente React que maneja mensajes.
|
||||
|
||||
En la última lección, el componente que conectaste a Redux se llamó `Presentational`, y esto no fue arbitrario. Este término *generalmente* se refiere a componentes React que no están directamente conectados a Redux. Ellos simplemente son responsables de la presentación de la interfaz de usuario y lo hacen en función de las props que reciben. Por el contrario, los contenedores componentes están conectados a Redux. Estos son típicamente responsables de enviar acciones al store y a menudo pasan el estado del store a componentes secundarios como props.
|
||||
|
||||
# --instructions--
|
||||
|
||||
El editor tiene todo el código que has escrito hasta ahora en esta sección. El único cambio es que el componente React se renombra a `Presentational`. Crea un nuevo componente en una constante llamada `Container` que usa `connect` para conectar el componente `Presentational` a Redux. Luego, en el `AppWrapper`, renderiza el componente `Provider` de React Redux. Pasa a `Provider` el `store` Redux como una prop y renderiza `Container` como un hijo. Una vez que todo esté configurado, verás que los mensajes de la aplicación son visualizados nuevamente en la página.
|
||||
|
||||
# --hints--
|
||||
|
||||
El `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: un `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 store Redux como una 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);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El componente `Presentational` debe recibir la acción `submitMessage` como una 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';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --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:
|
||||
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) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// React-Redux:
|
||||
const mapStateToProps = (state) => {
|
||||
return { messages: state }
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (newMessage) => {
|
||||
dispatch(addMessage(newMessage))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
// Define the Container component here:
|
||||
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
// Complete the return statement:
|
||||
return (null);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --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:
|
||||
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) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// React-Redux:
|
||||
const mapStateToProps = (state) => {
|
||||
return { messages: state }
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (newMessage) => {
|
||||
dispatch(addMessage(newMessage))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Container/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,397 @@
|
||||
---
|
||||
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>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,115 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036143
|
||||
title: Extrae la lógica de estado a Redux
|
||||
challengeType: 6
|
||||
forumTopicId: 301429
|
||||
dashedName: extract-state-logic-to-redux
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Ahora que has terminado el componente React, necesitas mover la lógica que está realizando localmente en su `state` hacia Redux. Este es el primer paso para conectar la aplicación simple de React a Redux. La única funcionalidad que tiene tu aplicación es añadir nuevos mensajes del usuario a una lista desordenada. El ejemplo es simple para demostrar cómo React y Redux trabajan juntos.
|
||||
|
||||
# --instructions--
|
||||
|
||||
En primer lugar, define un tipo de acción `ADD` y asígnalo a una const `ADD`. A continuación, define un creador de acciones `addMessage()` que crea la acción para añadir un mensaje. Tendrás que pasar un `message` a este creador de acciones e incluir el mensaje en la `action` devuelta.
|
||||
|
||||
Luego crea un reductor llamado `messageReducer()` que maneja el estado de los mensajes. El estado inicial debe ser igual a un arreglo vacío. Este reductor debe añadir un mensaje al arreglo de mensajes mantenido en el estado, o devolver el estado actual. Finalmente, crea tu almacén Redux y pásale el reductor.
|
||||
|
||||
# --hints--
|
||||
|
||||
La const `ADD` debe existir y mantener un valor igual a la cadena `ADD`
|
||||
|
||||
```js
|
||||
assert(ADD === 'ADD');
|
||||
```
|
||||
|
||||
El creador de acción `addMessage` debe devolver un objeto con `type` igual a `ADD` y `message` igual al mensaje que se pasa.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const addAction = addMessage('__TEST__MESSAGE__');
|
||||
return addAction.type === ADD && addAction.message === '__TEST__MESSAGE__';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`messageReducer` debe ser una función.
|
||||
|
||||
```js
|
||||
assert(typeof messageReducer === 'function');
|
||||
```
|
||||
|
||||
El almacén debe existir y tener un estado inicial establecido a un arreglo vacío.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
return typeof store === 'object' && initialState.length === 0;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El envío de `addMessage` contra el almacén debe añadir un nuevo mensaje de forma inmutable al arreglo de mensajes mantenido en el estado.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const initialState = store.getState();
|
||||
const isFrozen = DeepFreeze(initialState);
|
||||
store.dispatch(addMessage('__A__TEST__MESSAGE'));
|
||||
const addState = store.getState();
|
||||
return isFrozen && addState[0] === '__A__TEST__MESSAGE';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El `messageReducer` debe devolver el estado actual si se llama con cualquier otra acción.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const addState = store.getState();
|
||||
store.dispatch({ type: 'FAKE_ACTION' });
|
||||
const testState = store.getState();
|
||||
return addState === testState;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
// Define ADD, addMessage(), messageReducer(), and store here:
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
```
|
@ -0,0 +1,102 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036141
|
||||
title: Introducción a React Redux
|
||||
challengeType: 6
|
||||
forumTopicId: 301430
|
||||
dashedName: getting-started-with-react-redux
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Esta serie de desafíos presenta cómo utilizar Redux con React. En primer lugar, repasamos algunos de los principios clave de cada tecnología. React es una librería de vistas a la que le proporcionas datos y luego renderiza la vista de forma eficiente y predecible. Redux es un framework de gestión de estados que puedes utilizar para simplificar la gestión del estado de tu aplicación. Por lo general, en una aplicación React Redux, se crea un único almacén Redux que gestiona el estado de toda la aplicación. Tus componentes de React se suscriben sólo a las piezas de datos del almacén que son relevantes para su función. Luego, se envían acciones directamente desde los componentes de React, que luego activan las actualizaciones del almacén.
|
||||
|
||||
Aunque los componentes de React pueden gestionar su propio estado localmente, cuando se tiene una aplicación compleja, generalmente es mejor mantener el estado de la aplicación en una sola ubicación con Redux. Hay excepciones cuando los componentes individuales pueden tener un estado local específico sólo para ellos. Por último, debido a que Redux no está diseñado para trabajar con React de fábrica, es necesario utilizar el paquete `react-redux`. Proporciona una forma para pasar Redux `state` y `dispatch` a tus componentes React como `props`.
|
||||
|
||||
A lo largo de los siguientes desafíos, primero crearás un simple componente React que te permita introducir nuevos mensajes de texto. Estos se añaden a un arreglo que se muestra en la vista. Esto debería ser un buen repaso de lo aprendido en las lecciones de React. A continuación, crearás un almacén Redux y acciones que gestionen el estado del arreglo de mensajes. Por último, utilizarás `react-redux` para conectar el almacén Redux con tu componente, extrayendo así el estado local en el almacén Redux.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Comienza con un componente `DisplayMessages`. Añade un constructor a este componente e inicialízalo con un estado que tenga dos propiedades: `input`, que se establece como una cadena vacía, y `messages`, que se establece como un arreglo vacío.
|
||||
|
||||
# --hints--
|
||||
|
||||
El componente `DisplayMessages` debe mostrar un elemento `div` vacío.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
return mockedComponent.find('div').text() === '';
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El constructor `DisplayMessages` debe ser llamado correctamente con `super`, pasando `props`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index'));
|
||||
return (
|
||||
noWhiteSpace.includes('constructor(props)') &&
|
||||
noWhiteSpace.includes('super(props')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El componente `DisplayMessages` debe tener un estado inicial igual a `{input: "", messages: []}`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const initialState = mockedComponent.state();
|
||||
return (
|
||||
typeof initialState === 'object' &&
|
||||
initialState.input === '' &&
|
||||
Array.isArray(initialState.messages) &&
|
||||
initialState.messages.length === 0
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<DisplayMessages />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class DisplayMessages extends React.Component {
|
||||
// Change code below this line
|
||||
|
||||
// Change code above this line
|
||||
render() {
|
||||
return <div />
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return <div/>
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,260 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036142
|
||||
title: Gestiona el estado localmente primero
|
||||
challengeType: 6
|
||||
forumTopicId: 301431
|
||||
dashedName: manage-state-locally-first
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Aquí terminarás de crear el componente `DisplayMessages`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
En primer lugar, en el método `render()`, haz que el componente renderice un elemento `input`, un elemento `button` y un elemento `ul`. Cuando el elemento `input` cambia, debe activar un método `handleChange()`. Además, el elemento `input` debe renderizar el valor de `input` que está en el estado del componente. El elemento `button` debe activar un método `submitMessage()` cuando se hace clic en él.
|
||||
|
||||
En segundo lugar, escribe estos dos métodos. El método `handleChange()` debe actualizar el `input` con lo que el usuario está escribiendo. El método `submitMessage()` debe concatenar el mensaje actual (almacenado en `input`) al arreglo `messages` del estado local, y borrar el valor de `input`.
|
||||
|
||||
Por último, utiliza el `ul` para asignar el arreglo de `messages` y renderizarlo en la pantalla como una lista de elementos `li`.
|
||||
|
||||
# --hints--
|
||||
|
||||
El componente `DisplayMessages` debe inicializarse con un estado igual a `{ input: "", messages: [] }`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const initialState = mockedComponent.state();
|
||||
return (
|
||||
typeof initialState === 'object' &&
|
||||
initialState.input === '' &&
|
||||
initialState.messages.length === 0
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El componente `DisplayMessages` debe renderizar un `div` que contenga un elemento `h2`, un elemento `button`, un elemento `ul` y elementos `li` como hijos.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const state = () => {
|
||||
mockedComponent.setState({ messages: ['__TEST__MESSAGE'] });
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await state();
|
||||
assert(
|
||||
updated.find('div').length === 1 &&
|
||||
updated.find('h2').length === 1 &&
|
||||
updated.find('button').length === 1 &&
|
||||
updated.find('ul').length === 1 &&
|
||||
updated.find('li').length > 0
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`.map` debe usarse en el arreglo `messages`.
|
||||
|
||||
```js
|
||||
assert(code.match(/this\.state\.messages\.map/g));
|
||||
```
|
||||
|
||||
El elemento `input` debe renderizar el valor de `input` en estado local.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const testValue = '__TEST__EVENT__INPUT';
|
||||
const changed = () => {
|
||||
causeChange(mockedComponent, testValue);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const updated = await changed();
|
||||
assert(updated.find('input').props().value === testValue);
|
||||
};
|
||||
```
|
||||
|
||||
La llamada al método `handleChange` debe actualizar el valor de `input` en estado a la entrada actual.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const initialState = mockedComponent.state();
|
||||
const testMessage = '__TEST__EVENT__MESSAGE__';
|
||||
const changed = () => {
|
||||
causeChange(mockedComponent, testMessage);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterInput = await changed();
|
||||
assert(
|
||||
initialState.input === '' &&
|
||||
afterInput.state().input === '__TEST__EVENT__MESSAGE__'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Al hacer clic en el botón `Add message` se debe llamar al método `submitMessage` que debe añadir el `input` actual al arreglo `messages` del estado.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const initialState = mockedComponent.state();
|
||||
const testMessage_1 = '__FIRST__MESSAGE__';
|
||||
const firstChange = () => {
|
||||
causeChange(mockedComponent, testMessage_1);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const firstResult = await firstChange();
|
||||
const firstSubmit = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterSubmit_1 = await firstSubmit();
|
||||
const submitState_1 = afterSubmit_1.state();
|
||||
const testMessage_2 = '__SECOND__MESSAGE__';
|
||||
const secondChange = () => {
|
||||
causeChange(mockedComponent, testMessage_2);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const secondResult = await secondChange();
|
||||
const secondSubmit = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterSubmit_2 = await secondSubmit();
|
||||
const submitState_2 = afterSubmit_2.state();
|
||||
assert(
|
||||
initialState.messages.length === 0 &&
|
||||
submitState_1.messages.length === 1 &&
|
||||
submitState_2.messages.length === 2 &&
|
||||
submitState_2.messages[1] === testMessage_2
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
El método `submitMessage` debe borrar la entrada actual.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
const initialState = mockedComponent.state();
|
||||
const testMessage = '__FIRST__MESSAGE__';
|
||||
const firstChange = () => {
|
||||
causeChange(mockedComponent, testMessage);
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const firstResult = await firstChange();
|
||||
const firstState = firstResult.state();
|
||||
const firstSubmit = () => {
|
||||
mockedComponent.find('button').simulate('click');
|
||||
return waitForIt(() => mockedComponent);
|
||||
};
|
||||
const afterSubmit = await firstSubmit();
|
||||
const submitState = afterSubmit.state();
|
||||
assert(firstState.input === testMessage && submitState.input === '');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<DisplayMessages />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
}
|
||||
// Add handleChange() and submitMessage() methods here
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
{ /* Render an input, button, and ul below this line */ }
|
||||
|
||||
{ /* Change code above this line */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
class DisplayMessages 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) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,107 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036146
|
||||
title: Asigna el envío a props
|
||||
challengeType: 6
|
||||
forumTopicId: 301432
|
||||
dashedName: map-dispatch-to-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
La función `mapDispatchToProps()` se utiliza para proporcionar creadores de acción específicos a tus componentes React para que puedan enviar acciones contra el almacén Redux. Su estructura es similar a la función `mapStateToProps()` que escribiste en el último desafío. Devuelve un objeto que asigna acciones de envío a nombres de propiedades, que se convierten en `props` del componente. Sin embargo, en lugar de devolver una pieza de `state`, cada propiedad devuelve una función que llama a `dispatch` con un creador de acciones y cualquier dato relevante de la acción. Tienes acceso a este `dispatch` porque se pasa a `mapDispatchToProps()` como parámetro cuando defines la función, igual que pasaste `state` a `mapStateToProps()`. Tras bambalinas, React Redux utiliza `store.dispatch()` para realizar estos envíos con `mapDispatchToProps()`. Esto es similar a cómo se utiliza `store.subscribe()` para los componentes que se asignan a `state`.
|
||||
|
||||
Por ejemplo, tienes un creador de acción `loginUser()` que toma un `username` como carga útil de acción. El objeto devuelto por `mapDispatchToProps()` para este creador de acción se vería algo como:
|
||||
|
||||
```jsx
|
||||
{
|
||||
submitLoginUser: function(username) {
|
||||
dispatch(loginUser(username));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
El editor de código proporciona un creador de acción llamado `addMessage()`. Escribe la función `mapDispatchToProps()` que toma `dispatch` como argumento y devuelve un objeto. El objeto debe tener una propiedad `submitNewMessage` establecida en la función de envío, que toma un parámetro para el nuevo mensaje a añadir cuando envía `addMessage()`.
|
||||
|
||||
# --hints--
|
||||
|
||||
`addMessage` debe devolver un objeto con las claves `type` y `message`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const addMessageTest = addMessage();
|
||||
return (
|
||||
addMessageTest.hasOwnProperty('type') &&
|
||||
addMessageTest.hasOwnProperty('message')
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`mapDispatchToProps` debe ser una función.
|
||||
|
||||
```js
|
||||
assert(typeof mapDispatchToProps === 'function');
|
||||
```
|
||||
|
||||
`mapDispatchToProps` debe devolver un objeto.
|
||||
|
||||
```js
|
||||
assert(typeof mapDispatchToProps() === 'object');
|
||||
```
|
||||
|
||||
El envío de `addMessage` con `submitNewMessage` desde `mapDispatchToProps` debe devolver un mensaje a la función de envío.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
let testAction;
|
||||
const dispatch = (fn) => {
|
||||
testAction = fn;
|
||||
};
|
||||
let dispatchFn = mapDispatchToProps(dispatch);
|
||||
dispatchFn.submitNewMessage('__TEST__MESSAGE__');
|
||||
return (
|
||||
testAction.type === 'ADD' && testAction.message === '__TEST__MESSAGE__'
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
// Change code below this line
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: function(message) {
|
||||
dispatch(addMessage(message));
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,69 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036145
|
||||
title: Asigna el estado a props
|
||||
challengeType: 6
|
||||
forumTopicId: 301433
|
||||
dashedName: map-state-to-props
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
El componente `Provider` te permite proporcionar `state` y `dispatch` a tus componentes React, pero debes especificar exactamente qué estado y acciones quieres. De esta manera, te aseguras de que cada componente sólo tiene acceso al estado que necesita. Esto se consigue creando dos funciones: `mapStateToProps()` y `mapDispatchToProps()`.
|
||||
|
||||
En estas funciones, declaras a qué partes del estado quieres tener acceso y qué creadores de acción necesitas poder enviar. Una vez que estas funciones están en su lugar, verás cómo usar el método React Redux `connect` para conectarlos a tus componentes en otro desafío.
|
||||
|
||||
**Nota:** Tras bambalinas, React Redux utiliza el método `store.subscribe()` para implementar `mapStateToProps()`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Crea una función `mapStateToProps()`. Esta función debe tomar `state` como argumento, y luego devolver un objeto que asigna ese estado a nombres de propiedades específicas. Estas propiedades serán accesibles a tu componente a través de `props`. Dado que este ejemplo mantiene todo el estado de la aplicación en un solo arreglo, puedes pasar todo ese estado a tu componente. Crea una propiedad `messages` en el objeto que se devuelve, y establécela como `state`.
|
||||
|
||||
# --hints--
|
||||
|
||||
La const `state` debe ser un arreglo vacío.
|
||||
|
||||
```js
|
||||
assert(Array.isArray(state) && state.length === 0);
|
||||
```
|
||||
|
||||
`mapStateToProps` debe ser una función.
|
||||
|
||||
```js
|
||||
assert(typeof mapStateToProps === 'function');
|
||||
```
|
||||
|
||||
`mapStateToProps` debe devolver un objeto.
|
||||
|
||||
```js
|
||||
assert(typeof mapStateToProps() === 'object');
|
||||
```
|
||||
|
||||
Pasar un arreglo como estado a `mapStateToProps` debe devolver este arreglo asignado a una clave de `messages`.
|
||||
|
||||
```js
|
||||
assert(mapStateToProps(['messages']).messages.pop() === 'messages');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
const state = [];
|
||||
|
||||
// Change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
const state = [];
|
||||
|
||||
// Change code below this line
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
messages: state
|
||||
}
|
||||
};
|
||||
```
|
@ -0,0 +1,69 @@
|
||||
---
|
||||
id: 5a24c314108439a4d403614a
|
||||
title: Avanzando desde aquí
|
||||
challengeType: 6
|
||||
forumTopicId: 301434
|
||||
dashedName: moving-forward-from-here
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
¡Felicidades! Has terminado las lecciones sobre React y Redux. Hay un último punto que vale la pena señalar antes de seguir adelante. Por lo general, no escribirás aplicaciones React en un editor de código como este. Este desafío te da una idea de cómo es la sintaxis si trabajas con npm y un sistema de archivos en tu propia máquina. El código debería ser similar, excepto por el uso de las sentencias `import` (éstas traen todas las dependencias que se te han proporcionado en los desafíos). La sección "Gestión de paquetes con npm" cubre npm con más detalle.
|
||||
|
||||
Por último, escribir código React y Redux generalmente requiere cierta configuración. Esto puede complicarse rápidamente. Si te interesa experimentar en tu propia máquina, <a href="https://github.com/facebookincubator/create-react-app" target="_blank" rel="nofollow">Create React App</a> viene configurado y listo para funcionar.
|
||||
|
||||
Alternativamente, puedes habilitar Babel como un preprocesador de JavaScript en CodePen, añadir React y ReactDOM como recursos externos de JavaScript, y trabajar allí también.
|
||||
|
||||
# --instructions--
|
||||
|
||||
Imprime el mensaje `'Now I know React and Redux!'` en la consola.
|
||||
|
||||
# --hints--
|
||||
|
||||
El mensaje `Now I know React and Redux!` debe imprimirse en la consola.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
/console\s*\.\s*log\s*\(\s*('|"|`)Now I know React and Redux!\1\s*\)/.test(
|
||||
getUserInput('index')
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```jsx
|
||||
/*
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider, connect } from 'react-redux'
|
||||
import { createStore, combineReducers, applyMiddleware } from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
|
||||
import rootReducer from './redux/reducers'
|
||||
import App from './components/App'
|
||||
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
applyMiddleware(thunk)
|
||||
);
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
*/
|
||||
|
||||
// Only change code below this line
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
console.log('Now I know React and Redux!');
|
||||
```
|
@ -0,0 +1,263 @@
|
||||
---
|
||||
id: 5a24c314108439a4d4036144
|
||||
title: Usa "Provider" para conectar Redux a React
|
||||
challengeType: 6
|
||||
forumTopicId: 301435
|
||||
dashedName: use-provider-to-connect-redux-to-react
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
En el último desafío, creaste un almacén Redux para manejar el arreglo de mensajes y creaste una acción para añadir nuevos mensajes. El siguiente paso es proporcionar a React acceso al almacén Redux y a las acciones que necesita para enviar las actualizaciones. React Redux proporciona su paquete `react-redux` para ayudar a realizar estas tareas.
|
||||
|
||||
React Redux ofrece si API con dos características clave: `Provider` y `connect`. Otro desafío cubre `connect`. El `Provider` es un componente envolvente de React Redux que envuelve tu aplicación React. Esta envoltura te permite acceder a las funciones Redux `store` y `dispatch` a través de tu árbol de componentes. `Provider` toma dos props, el almacén Redux y los componentes hijos de tu aplicación. La definición del `Provider` para un componente de la App podría ser así:
|
||||
|
||||
```jsx
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
</Provider>
|
||||
```
|
||||
|
||||
# --instructions--
|
||||
|
||||
El editor de código ahora muestra todo tu código Redux y React de los últimos desafíos. Incluye el almacén Redux, las acciones y el componente `DisplayMessages`. La única pieza nueva es el componente `AppWrapper` de la parte inferior. Usa este componente de nivel superior para renderizar el `Provider` de `ReactRedux`, y pasa el almacén Redux como prop. Luego, renderiza el componente `DisplayMessages` como hijo. Una vez que hayas terminado, deberías ver tu componente React renderizado en la página.
|
||||
|
||||
**Nota:** React Redux está disponible como una variable global aquí, por lo que puedes acceder al "Provider" con notación de puntos. El código del editor aprovecha esto y lo establece en una constante `Provider` para que lo uses en el método de renderizado `AppWrapper`.
|
||||
|
||||
# --hints--
|
||||
|
||||
El `AppWrapper` debe renderizarse.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return mockedComponent.find('AppWrapper').length === 1;
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El componente envolvente `Provider` debe tener una prop de `store` pasada, igual al almacén de Redux.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return __helpers
|
||||
.removeWhiteSpace(getUserInput('index'))
|
||||
.includes('<Providerstore={store}>');
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
`DisplayMessages` debe renderizarse como hijo de `AppWrapper`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return (
|
||||
mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
El componente `DisplayMessages` debe renderizar un elemento `h2`, `input`, `button` y `ul`.
|
||||
|
||||
```js
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return (
|
||||
mockedComponent.find('div').length === 1 &&
|
||||
mockedComponent.find('h2').length === 1 &&
|
||||
mockedComponent.find('button').length === 1 &&
|
||||
mockedComponent.find('ul').length === 1
|
||||
);
|
||||
})()
|
||||
);
|
||||
```
|
||||
|
||||
# --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
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
|
||||
class DisplayMessages 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) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
// Render the Provider below this line
|
||||
|
||||
// Change code above this line
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```jsx
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
|
||||
class DisplayMessages 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) => {
|
||||
const currentMessage = state.input;
|
||||
return {
|
||||
input: '',
|
||||
messages: state.messages.concat(currentMessage)
|
||||
};
|
||||
});
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
// Change code below this line
|
||||
render() {
|
||||
return (
|
||||
<Provider store = {store}>
|
||||
<DisplayMessages/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
// Change code above this line
|
||||
};
|
||||
```
|
Reference in New Issue
Block a user