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:
Shaun Hamilton
2021-08-14 03:57:13 +01:00
committed by GitHub
parent 4df2a0c542
commit c2a11ad00d
1215 changed files with 790 additions and 449 deletions

View File

@ -0,0 +1,156 @@
---
id: 5a24c314108439a4d4036147
title: Conectar o Redux ao React
challengeType: 6
forumTopicId: 301426
dashedName: connect-redux-to-react
---
# --description--
Agora que você escreveu ambas as funções `mapStateToProps()` e `mapDispatchToProps()`, você pode usá-las para mapear `state` e `dispatch` para `props` de um de seus componentes React. O método `connect` do Redux React pode lidar com essa tarefa. Esse método recebe dois argumentos opcionais, `mapStateToProps()` e `mapDispatchToProps()`. Eles são opcionais porque você pode ter um componente que precisa apenas acessar o `state`, mas não precisa despachar nenhuma ação, ou vice-versa.
Para usar esse método, passe nas funções como argumentos, e imediatamente chame o resultado com seu componente. Essa sintaxe é um pouco incomum e se parece com:
```js
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
```
**Observação:** se você quiser omitir um dos argumentos do método `connect`, você passa `null` no lugar.
# --instructions--
O editor de código tem as funções `mapStateToProps()` e `mapDispatchToProps()` e um novo componente React chamado `Presentational`. Conecte esse componente ao Redux com o método `connect` do objeto global `ReactRedux`, e o chame imediatamente no componente `Presentational`. Atribua o resultado a uma nova `const` chamada `ConnectedComponent` que representa o componente conectado. É isso, agora você está conectado ao Redux! Tente alterar qualquer um dos argumentos de `connect` para `null` e observe os resultados do teste.
# --hints--
O componente `Presentational` deve renderizar.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
return mockedComponent.find('Presentational').length === 1;
})()
);
```
O componente `Presentational` deve receber uma prop `messages` via `connect`.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
const props = mockedComponent.find('Presentational').props();
return props.messages === '__INITIAL__STATE__';
})()
);
```
O componente `Presentational` deve receber uma prop `submitNewMessage` via `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);
```

View File

@ -0,0 +1,300 @@
---
id: 5a24c314108439a4d4036148
title: Conectar o Redux ao app de mensagens
challengeType: 6
forumTopicId: 301427
dashedName: connect-redux-to-the-messages-app
---
# --description--
Agora que você entendeu como usar `connect` para conectar o React ao Redux, você pode aplicar o que aprendeu ao seus componentes React que lidam com mensagens.
Na última lição, o componente que você conectou ao Redux foi nomeado `Presentational`, e isso não foi arbitrário. Esse termo *geralmente* refere-se a componentes React que não estão conectados diretamente ao Redux. Eles simplesmente são responsáveis pela apresentação da interface do usuário e fazem isso como uma função das props que recebem. Em contraste, os componentes de contêiner são conectados ao Redux. Estes são tipicamente responsáveis por despachar ações ao store e, frequentemente, passam o state do store para componentes filhos como props.
# --instructions--
O editor de código tem todo o código que você escreveu nesta seção até agora. A única alteração é que o componente React é renomeado para `Presentational`. Crie um novo componente mantido em uma constante chamada `Container` que usa `connect` para conectar o componente `Presentational` ao Redux. Em seguida, no `AppWrapper`, renderize o componente `Provider` do Redux React. Passe `Provider` ao `store` do Redux como prop e renderize `Container` como filho. Assim que tudo estiver pronto, você verá as mensagens do app renderizadas na página novamente.
# --hints--
O `AppWrapper` deve ser renderizardo na página.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
return mockedComponent.find('AppWrapper').length === 1;
})()
);
```
O componente `Presentational` deve ser renderizado na página.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
return mockedComponent.find('Presentational').length === 1;
})()
);
```
O componente `Presentational` deve renderizar os elementos `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
);
})()
);
```
O componente `Presentational` deve receber `messages` do store Redux como 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);
})()
);
```
O componente `Presentational` deve receber o criador de ação `submitMessage` como 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>
);
}
};
```

View File

@ -0,0 +1,397 @@
---
id: 5a24c314108439a4d4036149
title: Extrair estado local para dentro do Redux
challengeType: 6
forumTopicId: 301428
dashedName: extract-local-state-into-redux
---
# --description--
Você está quase pronto! Lembre-se de que você escreveu todo o código Redux para que o Redux pudesse controlar o gerenciamento de estado do seu aplicativo de mensagens React. Agora que o Redux está conectado, você precisa extrair o gerenciamento de estado do componente `Presentational` para o Redux. Atualmente, você tem o Redux conectado, mas está manipulando o estado localmente dentro do componente `Presentational`.
# --instructions--
No componente `Presentational`, primeiro, remova a propriedade `messages` no `state` local. Essas mensagens serão gerenciadas pelo Redux. Em seguida, modifique o método `submitMessage()` para que ele despache `submitNewMessage()` de `this.props`, e passe no input de mensagem atual do `state` local como um argumento. Como você removeu `messages` do state local, também remova aqui a propriedade `messages` da chamada para `this.setState()`. Finalmente, modifique o método `render()` para que ele mapeie as mensagens recebidas de `props` ao invés do `state`.
Quando essas alterações forem feitas, o aplicativo continuará a funcionar do mesmo jeito, exceto que o Redux gerencia o estado. Este exemplo também ilustra como um componente pode ter um `state` local: seu componente ainda monitora a entrada do usuário localmente em seu próprio `state`. Você pode ver como o Redux fornece um framework de gerenciamento de estado útil em cima do React. Você alcançou o mesmo resultado usando apenas o state local do React no início, e isso geralmente é possível com aplicativos simples. No entanto, como seus aplicativos se tornam maiores e mais complexos, seu gerenciamento de estado também fica mais complexo, e esse é o problema que o Redux resolve.
# --hints--
O `AppWrapper` deve ser renderizado na página.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
return mockedComponent.find('AppWrapper').length === 1;
})()
);
```
O componente `Presentational` deve ser renderizado na página.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
return mockedComponent.find('Presentational').length === 1;
})()
);
```
O componente `Presentational` deve renderizar os elementos `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
);
})()
);
```
O componente `Presentational` deve receber `messages` do store Redux como 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);
})()
);
```
O componente `Presentational` deve receber o criador de ação `submitMessage` como 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';
})()
);
```
O state do componente `Presentational` deve conter uma propriedade, `input`, a qual é inicializada como uma string vazia.
```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
);
})()
);
```
Digitar no elemento `input` deve atualizar o estado do 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__'
);
};
```
Despachar o `submitMessage` no componente `Presentational` deve atualizar o store Redux e limpar o input no state 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 === ''
);
};
```
O componente `Presentational` deve renderizar `messages` do store 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>
);
}
};
```

View File

@ -0,0 +1,115 @@
---
id: 5a24c314108439a4d4036143
title: Extrair a lógica de state do Redux
challengeType: 6
forumTopicId: 301429
dashedName: extract-state-logic-to-redux
---
# --description--
Agora que você terminou o componente React, você precisa mover a lógica que está sendo executada localmente em seu `state` para o Redux. Este é o primeiro passo para conectar um aplicativo React simples ao Redux. A única funcionalidade que o seu app possui é adicionar novas mensagens do usuário em uma lista não ordenada. O exemplo é simples para demonstrar como o React e Redux trabalham juntos.
# --instructions--
Primeiro, defina um tipo de ação `ADD` e atribua ela a constante `ADD`. Em seguida, defina o criador de ação `addMessage()` o qual cria uma ação para adicionar uma mensagem. Você precisará passar `message` para esse criador de ação e incluir a mensagem na `action` retornada.
Em seguida, crie um reducer chamado `messageReducer()` que lida com o state para as mensagens. O state inicial deve ser igual a um array vazio. Este reducer deve adicionar uma mensagem ao array de mensagens mantidos no state, ou retornar o state atual. Finalmente, crie seu store Redux e passe o reducer.
# --hints--
A const `ADD` deve existir e manter um valor igual a string `ADD`
```js
assert(ADD === 'ADD');
```
O criador de ação `addMessage` deve retornar um objeto com `type` igual a `ADD` e `message` igual à mensagem que foi passada.
```js
assert(
(function () {
const addAction = addMessage('__TEST__MESSAGE__');
return addAction.type === ADD && addAction.message === '__TEST__MESSAGE__';
})()
);
```
`messageReducer` deve ser uma função.
```js
assert(typeof messageReducer === 'function');
```
O store deve existir e ter um estado inicial definido como um array vazio.
```js
assert(
(function () {
const initialState = store.getState();
return typeof store === 'object' && initialState.length === 0;
})()
);
```
Despachar `addMessage` para o store deve imutavelmente adicionar uma nova mensagem ao array de mensagens mantido no state.
```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';
})()
);
```
O `messageReducer` deve retornar o state atual se chamado com qualquer outra ação.
```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);
```

View File

@ -0,0 +1,102 @@
---
id: 5a24c314108439a4d4036141
title: Primeiros passos com React Redux
challengeType: 6
forumTopicId: 301430
dashedName: getting-started-with-react-redux
---
# --description--
Essa série de desafios introduz como usar o Redux com React. Primeiro, aqui está uma revisão de alguns princípios chaves de cada tecnologia. React é uma biblioteca de visualização que você fornece dados, e então ele renderiza a visualização de uma forma eficiente e previsível. Redux é um framework de gerenciamento de estado que você usa para simplificar o gerenciamento do estado da sua aplicação. Tipicamente, em uma aplicação React Redux, você cria um único store Redux que gerencia o estado de todo o seu app. Seus componentes React inscrevem-se apenas aos pedaços de dados no store que são relevantes para suas responsabilidades. Em seguida, você despacha ações diretamente de componentes React, o que então aciona a atualização do store.
Embora componentes React podem gerenciar seus próprios estados localmente, quando você tem um app complexo, geralmente é melhor manter o estado do app em um único lugar com Redux. Existem exceções quando componentes individuais podem ter um estado local específico apenas para eles. Finalmente, como o Redux não é projetado para trabalhar com o React puro, você precisa usar o pacote `react-redux`. Ele fornece uma forma de você passar o `state` e `dispatch` do Redux para seus componentes React como `props`.
Ao longo dos próximos desafios, primeiro, você criará um componente React simples que lhe permite inserir novas mensagens de texto. Estas mensagens são adicionadas a um array que é exibido na exibição. Essa deve ser uma boa revisão do que você aprendeu nas lições do React. Em seguida, você vai criar uma store e ações do Redux que gerenciam o estado do array de mensagens. Finalmente, você usará `react-redux` para conectar a store do Redux com seu componente, deste modo extraindo o state local para a store do Redux.
# --instructions--
Comece com o componente `DisplayMessages`. Adicione um construtor para esse componente e inicialize ele com um state com duas propriedades: `input`, que é definido como uma string vazia, e `messages`, que é definido para um array vazio.
# --hints--
O componente `DisplayMessages` deve renderizar um elemento `div` vazio.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
return mockedComponent.find('div').text() === '';
})()
);
```
O construtor `DisplayMessages` deve ser chamado corretamente com `super`, passando `props`.
```js
(getUserInput) =>
assert(
(function () {
const noWhiteSpace = __helpers.removeWhiteSpace(getUserInput('index'));
return (
noWhiteSpace.includes('constructor(props)') &&
noWhiteSpace.includes('super(props')
);
})()
);
```
O componente `DisplayMessages` deve ter um state 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/>
}
};
```

View File

@ -0,0 +1,260 @@
---
id: 5a24c314108439a4d4036142
title: Gerenciar state localmente primeiro
challengeType: 6
forumTopicId: 301431
dashedName: manage-state-locally-first
---
# --description--
Aqui você terminará de criar o componente `DisplayMessages`.
# --instructions--
Primeiro, no método `render()`, faça o componente renderizar os elementos `input`, `button` e `ul`. Quando o elemento `input` alterar, ele deve acionar o método `handleChange()`. Além disso, o elemento `input` deve renderizar o valor do `input` que está no state do componente. O elemento `button` deve acionar o método `submitMessage()` quando for clicado.
Em segundo lugar, escreva esses dois métodos. O método `handleChange()` deve atualizar o `input` com o que o usuário digitar. O método `submitMessage()` deve concatenar a mensagem atual (armazenada em `input`) para o array `messages` no state local, e limpar o valor do `input`.
Finalmente, use o `ul` para mapear em cima do array `messages` e o renderize na tela como uma lista de elementos `li`.
# --hints--
O componente `DisplayMessages` deve inicializar com o 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
);
})()
);
```
O componente `DisplayMessages` deve renderizar um `div` contendo um elemento `h2`, um elemento `button`, um elemento `ul` e elementos `li` como filhos.
```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` deve ser usado no array de `messages`.
```js
assert(code.match(/this\.state\.messages\.map/g));
```
O elemento `input` deve renderizar o valor de `input` no state 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);
};
```
Chamar o método `handleChange` deve atualizar o valor do `input` no state para o input atual.
```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__'
);
};
```
Clicar o botão `Add message` deve chamar o método `submitMessage` o qual deve adicionar o texto atual no `input` ao array `messages` no state.
```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
);
};
```
O método `submitMessage` deve limpar o input atual.
```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>
);
}
};
```

View File

@ -0,0 +1,107 @@
---
id: 5a24c314108439a4d4036146
title: Mapear dispatch para props
challengeType: 6
forumTopicId: 301432
dashedName: map-dispatch-to-props
---
# --description--
A função `mapDispatchToProps()` é usada para fornecer criadores de ação específicos para seus componentes React, para que eles possam despachar ações à store Redux. É semelhante em estrutura à função `mapStateToProps()` que você escreveu no último desafio. Isso retorna um objeto que mapeia as ações de despache para nomes de propriedades, o que se torna `props` do componente. No entanto, em vez de retornar um pedaço do `state`, cada propriedade retorna uma função que chama `dispatch` com um criador de ação e quaisquer dados de ação relevantes. Você tem acesso a este `dispatch` porque ele foi passado para `mapDispatchToProps()` como um parâmetro quando você define a função, assim como você passou o `state` para `mapStateToProps()`. Nos bastidores, React Redux está usando `store.dispatch()` do Redux para realizar estes despachos com `mapDispatchToProps()`. Isso é semelhante a como ele usa `store.subscribe()` para componentes que são mapeados para o `state`.
Por exemplo, você tem o criador de ação `loginUser()` que recebe um `username` como um payload de ação. O objeto retornado do `mapDispatchToProps()` para este criador da ação pareceria algo como:
```jsx
{
submitLoginUser: function(username) {
dispatch(loginUser(username));
}
}
```
# --instructions--
O editor de código fornece um criador de ação chamado `addMessage()`. Escreva a função `mapDispatchToProps()` que recebe `dispatch` como um argumento, e então retorna um objeto. O objeto deve ter a propriedade `submitNewMessage` definida para a função de despacho, que recebe um parâmetro para a nova mensagem a ser adicionada quando despachar `addMessage()`.
# --hints--
`addMessage` deve retornar um objeto com chaves `type` e `message`.
```js
assert(
(function () {
const addMessageTest = addMessage();
return (
addMessageTest.hasOwnProperty('type') &&
addMessageTest.hasOwnProperty('message')
);
})()
);
```
`mapDispatchToProps` deve ser uma função.
```js
assert(typeof mapDispatchToProps === 'function');
```
`mapDispatchToProps` deve retornar um objeto.
```js
assert(typeof mapDispatchToProps() === 'object');
```
Despachar `addMessage` com `submitNewMessage` do `mapDispatchToProps` deve retornar uma mensagem para a função de envio.
```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));
}
}
};
```

View File

@ -0,0 +1,69 @@
---
id: 5a24c314108439a4d4036145
title: Mapear state para props
challengeType: 6
forumTopicId: 301433
dashedName: map-state-to-props
---
# --description--
O componente `Provider` permite que você forneça `state` e `dispatch` para seus componentes React, mas você deve especificar exatamente que state e actions deseja. Desta forma, você tem certeza que cada componente só tem acesso ao state de que precisa. Você consegue isso criando duas funções: `mapStateToProps()` e `mapDispatchToProps()`.
Nessas funções, você declara quais pedaços do state você quer ter acesso e quais criadores de ação você precisa ser capaz de despachar. Quando essas funções estiverem no lugar, você verá como usar o método `connect` do React Redux para conectá-los aos seus componentes em outro desafio.
**Observação:** nos bastidores, o React Redux usa o método `store.subscribe()` para implementar `mapStateToProps()`.
# --instructions--
Crie a função `mapStateToProps()`. Esta função deve receber `state` como um argumento, em seguida, retornar um objeto que mapeia esse state para nomes de propriedade específicos. Essas propriedades se tornarão acessíveis para o componente via `props`. Uma vez que esse exemplo mantém o estado inteiro do app em um único array, você pode passar todo o state para o seu componente. Cria uma propriedade `messages` no objeto que está sendo retornado e defina-a no `state`.
# --hints--
A const `state` deve ser um array vazio.
```js
assert(Array.isArray(state) && state.length === 0);
```
`mapStateToProps` deve ser uma função.
```js
assert(typeof mapStateToProps === 'function');
```
`mapStateToProps` deve retornar um objeto.
```js
assert(typeof mapStateToProps() === 'object');
```
Passar um array como state para `mapStateToProps` deve retornar esse array atribuído à chave `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
}
};
```

View File

@ -0,0 +1,69 @@
---
id: 5a24c314108439a4d403614a
title: Seguindo adiante a partir daqui
challengeType: 6
forumTopicId: 301434
dashedName: moving-forward-from-here
---
# --description--
Parabéns! Você concluiu as lições sobre React e Redux. Vale a pena destacar um último item antes de continuar. Normalmente, você não escreverá apps React em um editor de código como esse. Esse desafio dá a você uma ideia de como a sintaxe se parece se você está trabalhando com o npm e um sistema de arquivos na sua própria máquina. O código deve ser similar, exceto para o uso de instruções de `import` (essas puxam em todas as dependências fornecidas para você nos desafios). A seção "Gerenciar pacotes com npm" cobre o npm com mais detalhes.
Finalmente, escrever código React e Redux geralmente requer alguma configuração. Isto pode tornar-se complicado rapidamente. Se você estiver interessado em experimentar em sua própria máquina, o <a href="https://github.com/facebookincubator/create-react-app" target="_blank" rel="nofollow">Create React App</a> vem configurado e pronto para ser usado.
Como alternativa, você pode habilitar o Babel como um pré-processador de JavaScript no CodePen, adicionar React e ReactDOM como recursos externos de JavaScript, e trabalhar lá também.
# --instructions--
Exiba a mensagem `'Now I know React and Redux!'` no console.
# --hints--
A mensagem `Now I know React and Redux!` deve ser exibida no console.
```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!');
```

View File

@ -0,0 +1,263 @@
---
id: 5a24c314108439a4d4036144
title: Usar o provider para conectar o Redux ao React
challengeType: 6
forumTopicId: 301435
dashedName: use-provider-to-connect-redux-to-react
---
# --description--
No último desafio, você criou uma store Redux para lidar com o array messages e criou uma ação para adicionar novas mensagens. O próximo passo é fornecer ao React o acesso à store Redux e as ações que ele precise para despachar atualizações. O React Redux fornece seu pacote `react-redux` para ajudar a realizar essas tarefas.
O React Redux fornece uma pequena API com dois principais recursos: `Provider` e `connect`. Outro desafio tratará do `connect`. O `Provider` é um componente encapsulador do React Redux que encapsula seu aplicativo React. Este wrapper permite então que você acesse a `store` Redux e funções de `dispatch` em toda a sua árvore de componentes. `Provider` usa duas props, a store do Redux e os componentes filhos do seu aplicativo. Definir o `Provider` para um componente do aplicativo pode se parecer com isto:
```jsx
<Provider store={store}>
<App/>
</Provider>
```
# --instructions--
O editor de código agora mostra todos os seus códigos Redux e React dos vários desafios passados. Isso inclui a store, actions e o componente `DisplayMessages`. O único novo pedaço é o componente `AppWrapper` na parte inferior. Use este componente de nível superior para renderizar o `Provider` de `ReactRedux`, e passar à store do Redux como uma propriedade. Em seguida, renderize o componente `DisplayMessages` como filho. Quando você terminar, você deve usar seu componente React renderizado para a página.
**Observação:** o React Redux está disponível como uma variável global aqui, então você pode acessar o Provider com notação de ponto. O código no editor aproveita isso e o define para uma constante `Provider` para você usar no método de renderização `AppWrapper`.
# --hints--
O `AppWrapper` deve renderizar.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
return mockedComponent.find('AppWrapper').length === 1;
})()
);
```
O componente wrapper do `Provider` deve ter uma propriedade `store` passada para ele, igual à store do Redux.
```js
(getUserInput) =>
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
return __helpers
.removeWhiteSpace(getUserInput('index'))
.includes('<Providerstore={store}>');
})()
);
```
`DisplayMessages` deve renderizar como filho de `AppWrapper`.
```js
assert(
(function () {
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
return (
mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1
);
})()
);
```
O componente `DisplayMessages` deve renderizar os elementos `h2`, `input`, `button`, e `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
};
```