Una vez tienes perfectamente conformada la arquitectura de Redux solo queda hacer las adaptaciones necesarias para tener acceso a elementos como el store
y a las actions
en nuestra aplicación de React y así unir ambas arquitecturas.
En el artítulo anterior de esta temática estuvimos creando los Reducers en Redux que nos permitían, como ya explicamos, filtrar cada una de las acciones y cambiar el estado atendiendo a la acción despachada.
Adaptando al store para trabajar con NextJS: next-redux-wrapper
Para poder cargar el store
de Redux, deberemos de utilizar un wrapper llamado next-redux-wrapper
. Aplicaremos este sobre los componentes principales de las páginas, que por si no lo recuerdas, se encuentran localizados en la carpeta pages/
.
Una vez tengamos el concepto claro, procederemos a la instalación del wrapper para su posterior inclusión en la página principal(pages/index
):
Debes de tener en cuenta que aunque solo vayamos a aplicarlo a la página principal como ejemplo, tú debes de hacerlo en cada una de las páginas que tengan peticiones que querramos cambiar por acciones de Redux o que tengan componentes que hagan esto.
$ npm install next-redux-wrapper
Una vez instalado, procederemos a su importación en la página principal. También deberemos de exportar el componente encapsulándolo en este wrapper. Este tipo de encapsulamientos es propio tanto de este wrapper como de la librería react-redux
que veremos en próximos pasos. Por tanto, nuestro código quedaría de la siguiente manera:
import React from 'react'
import Head from 'next/head'
import withRedux from 'next-redux-wrapper'
import Posts from '../components/Posts'
import { configureStore } from '../store'
function FirstPage() {
return(
<div className="App">
<Head>
<link rel="stylesheet" href="/static/app.css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"/>
</Head>
<Posts />
</div>
)
}
export default withRedux(configureStore, null, null)(FirstPage)
Si te fijas, como te estaba comentado, el poder de withRedux
está en la exportación del componente, y para reforzar el concepto, debes saber que la llamada al wrapper tiene la siguiente estructura:
export default withRedux(funcionStore, mapStateToProps, mapDispatchToProps)(componente)
Como habrás apreciado, en el lugar de mapStateToProps
y mapDispatchToProps
no estamos pasando nada pues en nuestro caso en este archivo no tenemos ninguna petición que vayamos a sustituir por una acción de Redux. Si recuerdas, todas las peticiones se hacen desde los componentes pero es obligatorio hacer esta mínima llamada por la página, para que el componente herede el store
.
Accediendo al store en los componentes que tienen que hacer peticiones con react-redux
Configurando react-redux
para el componente Posts
Una vez hayamos configurado NextJS para trabajar con Redux en nuestra página principal, procederemos a configurar también este en el componente hijo, en este caso Posts
.
Para ello deberemos de hacer algo similar a lo anterior, aunque esta vez con el componente react-redux
y teniendo en cuenta que necesitaremos esta vez sí, la configuración de mapStateToProps
y mapDispatchToProps
.
$ npm install react-redux
Una vez instalado, aplicamos lo mencionado al componente:
import React from 'react'
import moment from 'moment'
import { connect } from 'react-redux'
import Header from './Header'
class Posts extends React.Component {
constructor(props) {
super(props)
this.state = {
posts: []
}
this.deletePost = this.deletePost.bind(this)
}
deletePost(ev) {
let el = ev.target
let id = el.dataset.id
let index = el.dataset.index
fetch(`https://owcrud-api.now.sh/api/posts/${id}`, {
method: 'DELETE'
})
.catch(err => console.error(err))
.then(() => {
let posts = this.state.posts
posts.splice(index, 1)
this.setState({ posts })
})
}
componentDidMount() {
fetch('https://owcrud-api.now.sh/api/posts')
.catch(err => console.error(err))
.then(res => res.json())
.then(posts => this.setState({ posts }))
}
render() {
if(this.state.posts.length > 0) {
return(
<div className="App">
<Header />
<div className="Posts">
{
this.state.posts.reverse().map((post, index) => (
<div className="Posts-Item" key={index}>
<div className="PhotoSegment">
<img src={post.image} alt={post.title}/>
</div>
<div className="DetailsSegment">
<a href={`/post/${post._id}`} className="Post-Link">
<h2 className="Post-Title">{post.title}</h2>
</a>
<h3 className="Post-Date">{moment(post.releaseDate).fromNow()}</h3>
<span
className="fa fa-trash"
onClick={this.deletePost}
data-id={post._id}
data-index={index}
/>
</div>
</div>
))
}
</div>
</div>
)
} else {
return(
<h3>Cargando...</h3>
)
}
}
}
const mapStateToProps = {}
const mapDispatchToProps = {}
export default connect(mapStateToProps, mapDispatchToProps)(Posts)
Añadiendo mapStateToProps
mapStateToProps
, al igual que mapDispatchToProps
pueden ser definidos como unos transpiladores entre React y Redux y son los que harán que podamos tocar el state desde React.
En el caso particular de mapStateToProps
no será mas que una función, que siempre retornará un objeto en el que, partiendo del parámetro state
de la función colocaremos las partes del estado de Redux a las que querramos tener acceso y estas, estarán disponibles desde this.props
:
const mapStateToProps = state => {
return {
posts: state.posts
}
}
Sólo he procedido a añadir a posts
ya que currentPosts
no será necesario en este componente. Si recuerdas, esta estructura fue definida en el reducer y más concretamente en la variable INTIAL_STATE
Añadiendo mapDispatchToProps
En el caso de mapDispachToProps
será otra función la cual colocará en las props funciones que, al llamarlas ejecutarán las diferentes acciones de Redux. Para ello traeremos como parámetro dispatcher
que es el encargado de despachar las funciones.
Otro elemento importante será el importar las funciones desde el archivo en el que las definimos.
import { getPosts, deletePost } from '../redux/actions/index'
[...]
const mapDispatchToProps = dispatch => {
return {
getPosts: () => {
dispatch(getPosts())
},
deletePost: (id, index) => {
dispatch(deletePost(id, index))
}
}
}
Llamando a las acciones necesarias dentro de los componentes
Una vez tenemos acceso mediante las props
de ReactJS a las funciones que necesitamos y al estado de Redux vamos a proceder a cambiar las peticiones que hay en los componentes por las llamadas a las acciones de Redux.
En el caso particular del componente Posts
, nos encontramos que en componentDidMount
estamos cargando todos los posts, que habría que sustituirlo por la función getPosts
y en el método deletePost
la acción del mismo nombre.
async componentDidMount() {
await this.props.getPosts()
}
deletePost(ev) {
let el = ev.target
let id = el.dataset.id
let index = el.dataset.index
this.props.deletePost(id, index)
}
Adaptando los componentes para trabajar con el estado de Redux
Una vez hemos sustituido las peticiones tal cual las trabajamos en el CRUD por las acciones de Redux vamos a cambiar algunos detalles de nuestro componente.
Por ejemplo, el state
dejaremos de tenerlo y con este habrá que modificar elementos como el if
del método render()
.
Los cambios resultarían en la clase que constituye el componente dejándolo de la siguiente manera:
class Posts extends React.Component {
constructor(props) {
super(props)
this.deletePost = this.deletePost.bind(this)
}
deletePost(ev) {
let el = ev.target
let id = el.dataset.id
let index = el.dataset.index
this.props.deletePost(id, index)
}
async componentDidMount() {
await this.props.getPosts()
}
render() {
if(!this.props.posts.loading) {
return(
<div className="App">
<Header />
<div className="Posts">
{
this.props.posts.data.reverse().map((post, index) => (
<div className="Posts-Item" key={index}>
<div className="PhotoSegment">
<img src={post.image} alt={post.title}/>
</div>
<div className="DetailsSegment">
<a href={`/post/${post._id}`} className="Post-Link">
<h2 className="Post-Title">{post.title}</h2>
</a>
<h3 className="Post-Date">{moment(post.releaseDate).fromNow()}</h3>
<span
className="fa fa-trash"
onClick={this.deletePost}
data-id={post._id}
data-index={index}
/>
</div>
</div>
))
}
</div>
</div>
)
} else {
return(
<h3>Cargando...</h3>
)
}
}
}
Conclusiones
Hemos conseguido conectar todas las implementaciones que hemos realizado en cuanto a Redux y las hemos aplicado a nuestras vistas en ReactJS.
Por tu parte quedaría, aplicar los pasos que hemos aprendido hoy para este y el resto de los componentes que lo necesitan.
En el siguiente artículo, concluiremos la guía de Redux y veremos aspectos interesantes del mismo como la diferenciación de los pasos que hemos visto el día de hoy si usáramos react-router
en vez de Next.JS y el código de los cambios que hemos estado realizando entre otras aportaciones que complementarán tu aprendizaje sobre Redux.