Sei sulla pagina 1di 44

How to (maybe) structure a

React application

#🍦
@keyserfaty

💡 Me llamo Karen

🎒 Software Engineer @ Mad Mobile


🍦 (Vanilla) Javascript

👫 Co-organizo Meetup.js

📚 Ciencias de la Educación & Ingeniería Electrónica

#🍦
¿De qué vamos a hablar?

1. ¿Qué es la arquitectura de aplicaciones?


2. ¿Cuál sería la arquitectura ideal?
3. Tres problemas que tenés con React:
. Primer problema: qué state usar para qué cosa
. Segundo problema: cómo `queriar` el state
. Tercer problema: cómo manejar la data async
4. Propuesta
5. Problemas de la propuesta
6. Conclusiones

#🍦
¿Qué es la arquitectura
de aplicaciones?

#🍦
#🍦
Firmitas
Utilitas
Venustas

#🍦
Firmitas
Durabilidad. Tiene que se robusto
y poder mantenerse en buenas
condiciones a lo largo del tiempo

#🍦
Utilitas
Utilidad. Tiene que poder servir
para el propósito para el que fue
creado

#🍦
Venustas
Belleza. Tiene usar cosas como
proporción, repetición y escala
para ser útil y agradable a la vista

#🍦
¿Cuál sería la
arquitectura ideal?

#🍦
🚨 Disclaimer

#🍦
Tres problemas que
tenés con React

#🍦
Primer problema:
qué state usar para qué cosa

#🍦
State de React,
Redux, ¿otros?

#🍦
import React from 'react';

import * as actions from './actions';


const mapStateToProps = state => ({
class TodoListContainer extends React.Component { todos: state.todos
initialState = { })
todos: []
} const mapDispatchToProps = dispatch => ({
fetchAll: () => dispatch(actions.fetchAll()),
constructor (props) { saveTodo: () => dispatch(actions.saveTodo()),
super(props) removeTodo: () => dispatch(actions.removeTodo()),
this.state = this.initialState })
}
export default (
componentWillMount () { mapStateToProps,
// populate the state initially mapDispatchToProps
const { fetchAll } = this.props )(TodoListContainer)
fetchAll()
}

onChange (id, text, status) {


const { todos } = this.state
const currentTodo = todos.filter(todo => todo.id === id)
this.setState({
todos: [
...todos,
{
...currentTodo,
text,
status
}
]
})
}

createTodo () {
const { todos } = this.state
const todo = {
id: 1,
text: '',
status: 'available'
}

this.setState({
todos: todos.push(todo)
})
}

componentWillUnmount() {
// clean state
this.setState(this.initialState)
}

render () {
const { todos } = this.props;
return (
<div>
<TodoList items={todos} />
</div>

#🍦
)
}
}
Container
import TodoList from './components/TodoList'

import * as actions from './actions'

const mapStateToProps = state => ({


todos: state.todos.list
})

const mapDispatchToProps = dispatch => ({


fetchAll: () => dispatch(actions.fetchAll()),
saveTodo: () => dispatch(actions.saveTodo())
deleteTodo: () => dispatch(actions.deleteTodo())
})

export default (
mapStateToProps,
mapDispatchToProps
)(withHooks(TodoList))

#🍦
Container
import TodoList from './components/TodoList'

import * as actions from './actions'

const mapStateToProps = state => ({


todos: state.todos.list
})

const mapDispatchToProps = dispatch => ({


fetchAll: () => dispatch(actions.fetchAll()),
saveTodo: () => dispatch(actions.saveTodo())
deleteTodo: () => dispatch(actions.deleteTodo())
})

export default (
mapStateToProps,
mapDispatchToProps
)(TodoList)

#🍦
Ventajas

- Evitamos las clases


- Separamos la data layer de la UI

#🍦
¿Cuándo state usar para qué cosa?

React
- Forms
- Components autocontenidos

Redux
- Manejar la data (sync/async)
- Cambios en la UI (excepto que sean parte de
un componente autocontenido) 🤔❓

¿Otros?
- Si no va a cambiar no pertenece al state =>
puede ser una constante

#🍦
Segundo problema:
cómo `queriar` el state

#🍦
¿Dónde poner mapStateToProps?
Components/Container

¿Qué pasa cuando la data del state tiene que


sufrir cambios para matchear con la UI?

#🍦
1. ¿Dónde poner mapStateToProps?
Components/Container

- mapStateToProps en Containers

#🍦
const mapStateToProps = state => ({
status: selectors.getStatus(state),
error: selectors.getError(state),
endpoints: selectors.getEndpoints(state),
searchValue: selectors.getSearchValue(state),
isValidating: selectors.getValidationsViewStatus(state),
exportCandidate: selectors.getExportCandidate(state),
importCandidate: selectors.getImportCandidate(state),
validationsErrors: selectors.getValidationErrorsWithRenamingOfStatus(state),
//* Static content
steps,
popoverContent
});

const mapDispatchToProps = dispatch => ({


onMount: () => {
dispatch(endpointsActions.fetchEndpoints());
dispatch(connectionActions.fetchAll());
},
onUnmount: () => dispatch(endpointsActions.clearTestEndpoint()),

handleValidate: () => dispatch(endpointsActions.validateEndpoint()),


handleToggleValidationsView: () => dispatch(endpointsActions.toggleValidationsView()),
handleOnChange: e => dispatch(endpointsActions.onChange({ name: e.target.name, value: e.target.value })),
handleJsonOnChange: e => dispatch(endpointsActions.onJsonChange({ name: e.target.name, value: e.target.value })),
handleSelectEndpoint: endpoint => dispatch(endpointsActions.selectExportEndpointCandidate({ endpoint })),

handleUploadInit: name => dispatch(endpointsActions.uploadInit({ name })),


handleUploadError: error => dispatch(endpointsActions.uploadError({ error })),
handleUploadReady: file => dispatch(endpointsActions.uploadReady({ file })),
handleUploadRemove: () => dispatch(endpointsActions.uploadRemove())
});

#🍦
El único lugar por el que un component puede
recibir data es por props desde el container

#🍦
Los components no tienen que importar
nada que no sea otros components

#🍦
2. ¿Qué pasa cuando la data del state tiene
que sufrir cambios para matchear con la UI?

#🍦
selectors 🌟🎉

#🍦
import * as selectors from './selectors'

const mapStateToProps = state => ({


todos: selectors.getTodos(state)
})

export const getTodos = state => state.todos.list

#🍦
import { createSelector } from 'reselect'

export const getTodos = state => state.todos.list


export const getAuthors = state => state.author

export const getTodosWithAuthor = createSelector(


getTodos,
getAuthor,
(todos, author) => {
const todoList = todos
.reduce((res, todo) => {
res.todo = todo
res.author = author
return res
}, {})

return todoList
}
)

import * as selectors from './selectors'

const mapStateToProps = state => ({


todos: selectors.getTodosWithAuthor(state)
})

#🍦
El state no tiene por qué saber de los
datos que necesita tu UI

#🍦
Ventajas:
1- Mi state está intacto
2- Mis components están intactos

#🍦
#🍦
import { createSelector } from ‘reselect'
import { pipe } from 'ramda'

export const getParsedList = createSelector(


getParsedListBySearch,
getActivePage,
getSearchValue,
getSorting,
(items, activePage, searchValue, { column, asc }) =>
pipe(
byColumn(column, asc), // sort by column && asc/desc
byFuzzyName(searchValue)(items), // filter by search
getVisibleItems(activePage, itemsPerPage), // filter items in the view
)(items)
)

#🍦
Tercer problema:
cómo manejar la data async

#🍦
¿Qué pasa cuando la data tiene que sufrir
cambios antes de poder mandarla en el request?

#🍦
sagas 🌟🎉
+
selectors🌟🎉

#🍦
export function * saveThingWorker (action) {
yield put(actions.saveThing.start());

const formData = yield select(selectors.getFormDataForTheThing);

const { agent, error } = yield call(api.createAgent, formData);

if (error) {
return yield put(actions.saveThing.failure({ error }));
}

yield put(actions.saveThing.success({ thing }));


}

export function * saveThingWatcher () {


yield takeEvery(actions.saveThing.type, saveThingWorker);
}

#🍦
Ventajas:
1- Mis funciones async solo hacen eso y
no hacen handling de la data
2- Mis components están intactos

#🍦
Una propuesta

#🍦
Actions

¿Async? ¿Sync? Component

UI Layer
Data Layer

Container Component

Sagas Selectors Component

Selectors

Models State

#🍦
Algunos problemas de la
estructura

#🍦
1- La complejidad del stack
2- Sobrecargar el state de redux
3- ¿Otros?

#🍦
Recursos:

https://github.com/reactjs/reselect
https://github.com/redux-saga/redux-saga
https://jaysoo.ca/2016/02/28/applying-code-organization-
rules-to-concrete-redux-code/#model
https://vimeo.com/166790294

#🍦
¡Gracias! 😬
🤔❓

@keyserfaty
#🍦

Potrebbero piacerti anche