Ejemplo completo de CRUD con Redux y React

Ejemplo completo de CRUD con Redux y React
by Janeth Kent Date: 23-08-2018 javascript node redux react

El objetivo de este artículo es explicar los conceptos básicos de Redux mediante una aplicación CRUD. La mayoría de las aplicaciones son aplicaciones CRUD. Si no sabes lo que CRUD realmente es, es la abreviatura de Create, Read, Update y Delete.

Así que esta entrada de blog te mostrará los pasos para crear una aplicación donde el usuario puede crear entradas, leer entradas, editar entradas y borrar entradas usando React y Redux, así que vamos a empezar. Creo que tienes Nodejs instalado en tu sistema.

Primero vamos a girar un nuevo proyecto, así que vamos a un directorio donde se almacenará este proyecto y escriba lo siguiente en el terminal-

create-react-app crud-redux

El comando anterior utiliza la herramienta CLI create-react-app para generar un proyecto de placa de calderas de reacción para que no tengamos que configurar ninguna herramienta. Si ese comando falla o da un error, asegúrese de tener create-react-app instalado en su máquina. Si no, haga lo siguiente y luego ejecute el comando anterior.

npm install -g create-react-app

El comando anterior instalará create-react-app globalmente en su sistema para que pueda usarlo en cualquier lugar que desee.

Una vez que el comando termine su trabajo tendrás un nuevo directorio llamado'crud-redux'. Cambie a este nuevo directorio. Ahora vamos a limpiar esto un poco ya que no necesitamos algunas de las cosas para este proyecto.

Escriba lo siguiente

cd src
rm App.css App.test.js logo.svg registerServiceWorker.js

Por último, vuelva al directorio raíz del proyecto y ábralo en su editor de texto favorito.

Ahora vamos a instalar Redux y es React bindings. Así que de vuelta en el tipo de terminal lo siguiente-

npm install --save redux react-redux

Redux es una librería de administración de estado que le da acceso al estado en cualquier parte de sus componentes sin la necesidad de pasar accesorios. Por lo tanto, puede usarse con cualquier librería de front-end como Angular y React, pero funciona mejor con React. react-redux' es la biblioteca oficial que conecta ambos.

Ya que hemos borrado algunos de esos archivos antes, necesitamos hacer algunos cambios en index.js y App.js que son los siguientes-

Ir a crud-redux/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';


ReactDOM.render(
<App />,document.getElementById('root'));

y crud-redux/src/App.js

npm start

Saldrá "Hello React!"

Si estás obteniendo errores por favor asegúrate de que has hecho los cambios que he mostrado arriba en index.js y App.js.

Cada vez que hago una aplicación de React siempre trato de hacer la versión básica de la misma y luego le agrego interactividad. Así que con eso en mente vamos a crear algunos componentes. En crud-redux/App.js haz lo siguiente.

 

import React, { Component } from 'react';
import PostForm from './PostForm';
import AllPost from './AllPost';


class App extends Component {
  render() {
    return (
    <div className="App">
        <PostForm />
        <AllPost />
    </div>
    );
    }
  }
export default App;

Aquí he creado dos componentes. El componente PostForm contendrá los elementos del formulario para crear un puesto y el componente AllPost contendrá todos los puestos. Así que vamos a crear los archivos para cada uno de estos componentes. En la carpeta src cree dos archivos llamados'PostForm.js' y'AllPost.js'.

Dentro de PostForm.js agregue el siguiente código -

import React, { Component } from 'react';

class PostForm extends Component {
render() {
return (
<div>
  <h1>Create Post</h1>
  <form>
   <input required type="text" placeholder="Enter Post Title" /><br /><br />
   <textarea required rows="5" cols="28" placeholder="Enter Post" /><br /><br />
   <button>Post</button>
  </form>
</div>
);
}
}
export default PostForm;

Añadiremos estilos más tarde, así que sigamos con el otro componente. Dentro de crud-redux/src/AllPost.js agregue las siguientes líneas-

import React, { Component } from 'react';

class AllPost extends Component {
  render() {
    return (
    <div>
      <h1>All Posts</h1>
    </div>
    );
   }
}

export default AllPost;

Con esto tenemos la siguiente salida en el navegador.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import { createStore } from 'redux';

const store = createStore();


ReactDOM.render(<App />, document.getElementById('root'));

El método createStore nos permitirá crear la tienda pero aún no hemos terminado. Este método necesita un argumento especial y este argumento recibe un nombre especial llamado el'reductor'. Vamos a crear una carpeta separada llamada reductores. Así que bajo crud-redux/src crea una carpeta llamada `reductores'. Dentro de esa carpeta crear un archivo llamado postReducer.js Añadir el siguiente código por ahora

const postReducer = (state = [], action) => {

}
export default postReducer;

Rellenaremos el contenido de esa función un poco más tarde. Ahora vamos a entender otro concepto importante en Redux llamado acciones. Las acciones no son más que objetos Javascript simples con una propiedad de tipo. Esta propiedad de tipo describe el evento que está teniendo lugar en la aplicación. Este evento puede ser cualquier cosa, desde el incremento de un contador hasta la adición de elementos en una matriz. Estas acciones nos ayudan a seguir los diferentes eventos que están ocurriendo en nuestra aplicación. La estructura de una acción es la siguiente.

{
 type: 'EVENT_NAME'
}

Una acción puede tener cualquier número de propiedades pero debe tener un tipo de propiedad. Así que una acción puede incluir datos como los siguientes

{
  type:'ADD_ITEM',
  name: 'Redux'
}

En este ejemplo el nombre del evento es 'ADD_ITEM' y los datos son la propiedad name con un valor de 'Redux'. Ahora otro término importante que se usa junto a las acciones se llama despacho. Cuando decimos 'enviar una acción' simplemente queremos decir llamar al método de envío que está dentro del objeto de la tienda con una acción. ¿Sigues conmigo?

Echemos un vistazo a la tienda. La tienda que creamos usando el método createStore es un objeto que tiene algunos métodos. Uno de esos métodos se llama despacho. Este método de envío acepta un objeto como argumento y este objeto es lo que llamamos'acción'.

Con eso fuera del camino, finalmente volvamos a esa función que escribimos antes dentro de postReducer.js. Cada vez que enviamos una acción, esta acción con su propiedad tipo es recibida por algo llamado el reductor. ¿Qué diablos es el reductor? Bueno, no es más que una función que toma el estado actual y una acción que fue enviada como parámetros y devuelve el nuevo estado.

Así que la próxima vez que vea el término reductor, recuerde que es sólo una función que le da un nuevo estado a sus componentes.

Ahora la pregunta es cómo hace el reductor para producir el nuevo estado para la aplicación. Bueno, eso es bastante simple, primero comprueba qué tipo de acción fue enviada y en base a ella devuelve el nuevo estado. Bajo crud-redux/src/reducers/postReducer.js agregue las siguientes líneas de código.

 

const postReducer = (state = [], action) => {
  switch(action.type) {
    case 'ADD_POST':
      return state.concat([action.data]);
    default:
      return state;
  }
}
export default postReducer;

Ahora lo que está pasando aquí es que estamos usando una'declaración de cambio' y estamos cambiando basándonos en el valor de action.type. Si el valor es 'ADD_POST' estamos devolviendo una nueva matriz que contiene action.data. Básicamente siempre que ocurre el evento `ADD_POST' queremos introducir algunos datos en la matriz de estado. Bueno, no es nada más que un objeto con nuestro título individual y el mensaje del mensaje. Una cosa a tener en cuenta aquí es que la función reductora espera un valor por defecto para el estado. Aquí estamos usando la sintaxis de parámetros por defecto ES6 para añadir eso. El valor por defecto para el estado aquí es un array vacío. Otra cosa a tener en cuenta es que un reductor siempre debe tener la cláusula por defecto dentro de la declaración del interruptor. En la cláusula por defecto simplemente devolvemos el estado. Esto se hace para que en caso de que ninguno de los valores de action.type coincida con alguno de los casos simplemente devolvamos el estado.

Ahora que tenemos algo de código dentro de postReducer.js, importémoslo en nuestro archivo index.js y pasémoslo a la tienda como argumento.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore } from 'redux';

import postReducer from './reducers/postReducer';

const store = createStore(postReducer);
ReactDOM.render(<App />,document.getElementById('root'));

Ahora que hemos terminado con el reductor. Pasemos esta tienda a nuestros componentes. Para ello usemos el componente Proveedor de la librería 'react-redux'. Cambia crud-redux/src/index.js como sigue-

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import { Provider } from 'react-redux';


import postReducer from './reducers/postReducer';
const store = createStore(postReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));

El componente Proveedor utiliza algo llamado React Context que le permite pasar el objeto de la tienda a cualquier componente que necesite acceder a él sin necesidad de pasar puntales. Aquí estamos envolviendo el componente App que es nuestro componente padre con el componente Provider para que todos los componentes hijo de nuestra aplicación puedan acceder a la tienda. El componente Proveedor toma la tienda como un accesorio.

Volvamos a nuestro componente PostForm y conectémoslo a nuestra tienda para que podamos enviar acciones.

import React, { Component } from 'react';

class PostForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();
    const title = this.getTitle.value;
    const message =  this.getMessage.value;
    const data = {
      id: new Date(),
      title,
      message
    }
  }
render() {
return (
<div>
  <h1>Create Post</h1>
  <form onSubmit={this.handleSubmit}>
   <input required type="text" ref={(input)=>this.getTitle = input} 
    placeholder="Enter Post Title"/>
   <br /><br />
   <textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28" 
    placeholder="Enter Post" />
   <br /><br />
   <button>Post</button>
  </form>
</div>
);
}
}
export default PostForm;

Así que aquí el elemento de formulario ahora acepta un evento onSubmit. Siempre que se produzca este evento se ejecutará la función handleSubmit. La función handleSubmit toma un argumento que es el evento. Llamar a e.preventDefault() evitará que la página se actualice. A continuación tomamos el valor del título y el mensaje de las entradas usando refs y los ponemos dentro de un objeto llamado datos. También tenemos una propiedad id cuyo valor se ajusta a lo que devuelva Date(). Utilizaremos esta propiedad de identificación para realizar operaciones de actualización y eliminación.

Vamos a poner algunos valores en el título y los campos de post y registrarlo en la consola. Esto es para asegurarse de que los datos están siendo capturados. Agregue un console.log() en medio como en el siguiente-

import React, { Component } from 'react';

class PostForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();
    const title = this.getTitle.value;
    const message =  this.getMessage.value;
    const data = {
      id: new Date(),
      title,
      message
    }
    console.log(data)
  }
render() {
return (
<div>
  <h1>Create Post</h1>
  <form onSubmit={this.handleSubmit}>
   <input required type="text" ref={(input)=>this.getTitle = input} 
    placeholder="Enter Post Title"/>
   <br /><br />
   <textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28" 
    placeholder="Enter Post" />
   <br /><br />
   <button>Post</button>
  </form>
</div>
);
}
}
export default PostForm;

Parece que nuestros datos están siendo capturados correctamente. Lo único que queda por hacer es enviar una acción. Para ello utilizaremos la función connect() proporcionada por la librería react-redux.

Ahora bien, aquí es donde las cosas pueden ponerse un poco difíciles, pero intentaré explicarlo lo mejor que pueda. Sabemos que nuestro estado vive dentro de este objeto llamado la tienda y esta tienda tiene su propio conjunto de métodos para obtener el estado actual de nuestra aplicación, actualizar el estado de nuestra aplicación y suscribirse a los cambios.

Ya hemos discutido uno de estos métodos llamado despacho. Necesitamos despachar siempre que queramos pasar alguna acción al reductor para decirle que algún tipo de evento ha ocurrido y entonces el reductor puede decidir qué hacer con el estado. Pero para hacer eso necesitamos acceso al despacho. ¿No sería genial si de alguna manera tuviéramos acceso al método de despacho como un accesorio?

Esto es lo que connect() te permite hacer. connect() devuelve una función que toma tu componente actual como argumento y devuelve un nuevo componente con método de envío como su prop. La idea principal a recordar es que connect finalmente devolverá un nuevo componente que tiene el método de envío como un prop.The sintaxis básica para la escritura de conectar en sus componentes de React es la siguiente -

 

export default connect()(component-name)

Así que usemos eso y agreguémoslo en nuestro PostForm.js. Así que después de eso nuestro componente parecerá tan-

import React, { Component } from 'react';
import {connect} from 'react-redux';
class PostForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();
    const title = this.getTitle.value;
    const message =  this.getMessage.value;
    const data = {
      id: new Date(),
      title,
      message
    }

  }
render() {
return (
<div>
  <h1>Create Post</h1>
  <form onSubmit={this.handleSubmit}>
   <input required type="text" ref={(input)=>this.getTitle = input} 
    placeholder="Enter Post Title"/>
   <br /><br />
   <textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28" 
    placeholder="Enter Post" />
   <br /><br />
   <button>Post</button>
  </form>
</div>
);
}
}
export default connect()(PostForm);

Con eso en su lugar podemos acceder fácilmente al despacho de nuestros componentes, así que vamos a utilizarlo.

import React, { Component } from 'react';
import {connect} from 'react-redux';
class PostForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();
    const title = this.getTitle.value;
    const message =  this.getMessage.value;
    const data = {
      id: new Date(),
      title,
      message
    }
    this.props.dispatch({
      type:'ADD_POST',
      data});
    this.getTitle.value = '';
    this.getMessage.value = '';
  }
render() {
return (
<div>
  <h1>Create Post</h1>
  <form onSubmit={this.handleSubmit}>
   <input required type="text" ref={(input)=>this.getTitle = input} 
    placeholder="Enter Post Title"/>
   <br /><br />
   <textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28" 
    placeholder="Enter Post" />
   <br /><br />
   <button>Post</button>
  </form>
</div>
);
}
}
export default connect()(PostForm);

Recuerda que connect() te da acceso al despacho como un prop. Aquí una vez que hemos capturado los datos del formulario enviamos la acción usando este.props.dispatch() pasando el objeto de datos con un tipo de 'ADD_POST'.

Genial, ahora hemos añadido algunos datos en nuestro estado pero no podemos ver ninguno de esos cambios reflejados en nuestra aplicación así que vamos a arreglarlo. Antes de hacer eso, vamos a entender una cosa más importante acerca de connect(). Esta función especial proporcionada por la librería react-redux le da acceso al envío siempre que lo llame envolviendo el nombre del componente como argumento para la función que se devuelve. Hemos visto esta sintaxis que es la siguiente.

export default connect()(component-name)

Además, connect puede hacer más. Puede darle acceso a su estado que está viviendo dentro de su objeto de la tienda. Para obtener acceso a su estado, necesitamos usar una función especial llamada mapStateToProps. Esta función hace exactamente lo que se llama, mapear el estado del objeto de la tienda al objeto de utilería en sus componentes. Vamos a definir esta función del mapa, StateToProps.

const mapStateToProps = (state) => {
 return {
 posts: state
 }
}

El argumento para mapStateToProps es nuestro estado de aplicación. Para entender esto mejor, imagine que lo que pase dentro del argumento mapStateToProps es su estado. La siguiente pregunta es ¿cuál es ese estado? ¿Es una matriz o un objeto o algo más? Bueno, eso dependerá de lo que lo hayas definido en tu reductor. Puesto que sólo tenemos un reductor que es el postReductor, sabemos que el estado es un array.

A continuación devolvemos un objeto con una clave posts y el valor es el propio estado. La clave que usamos en mapStateToProps estará disponible para nosotros como puntales dentro del componente.

Con eso en su lugar, agreguemos esta función como un argumento a nuestra conexión. Así que dentro de crud-redux/src/AllPost.js haz los siguientes cambios-


import React, { Component } from 'react';

import { connect } from 'react-redux';

class AllPost extends Component {
    render() {
        return (
            <div>
                <h1>All Posts</h1>
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        posts: state
    }
}
export default connect(mapStateToProps)(AllPost);

Ahora para ver lo que tenemos aquí, podemos registrar estos.props.posts así

import React, { Component } from 'react';

import { connect } from 'react-redux';

class AllPost extends Component {
    render() {
        return (
            <div>
                <h1>All Posts</h1>
                {console.log(this.props.posts)}
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        posts: state
    }
}
export default connect(mapStateToProps)(AllPost);

Para probar esto, introduzca algunos valores en los campos de título y mensaje y compruebe su consola.

Genial, así que tenemos el correo. Todo lo que queda es mostrarlo en el navegador. Para hacer eso vamos a crear otro componente llamado Post. Así que bajo crud-redux/src crea un nuevo archivo y llámalo `Post.js'. Ahora regresa a AllPost.js y haz los siguientes cambios.

import React, { Component } from 'react';

import { connect } from 'react-redux';

import Post from './Post';

class AllPost extends Component {
    render() {
        return (
            <div>
                <h1>All Posts</h1>
                {this.props.posts.map((post) => <Post key={post.id} post={post} />)}
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        posts: state
    }
}
export default connect(mapStateToProps)(AllPost);

Hemos importado el componente Post dentro de AllPost y usado la función Array.prototype.map para enlazar cada uno de los postes individuales dentro de este.props.posts y pasarlo al componente Post con la clave como post.id y al poste mismo. Dentro de crud-redux/src/Post.js agregue lo siguiente-

 


import React, { Component } from 'react';

class Post extends Component {
  render() {
  return (
    <div>
      <h2>{this.props.post.title}</h2>
      <p>{this.props.post.message}</p>
    </div>
  );
 }
}
export default Post;

Con eso en su lugar, introduzca algunos valores en el título y los campos de mensaje y ver si se está mostrando en Todos los mensajes como so -

Si has llegado hasta aquí, es genial que finalmente hayas terminado con la parte C y R de esta aplicación CRUD, ya que ahora podemos crear posts y leerlos también.

Antes de sumergirnos en la actualización y en la parte de eliminar de esta aplicación, recapitulemos.

Todo el estado de nuestra aplicación vive dentro de un objeto llamado tienda. Para actualizar el estado necesitamos enviar una acción. Las acciones no son más que objetos Javascript con una propiedad de tipo que describe el evento que está teniendo lugar. Los eventos pueden ser cualquier cosa, desde la actualización de contadores hasta la adición de mensajes como hemos visto anteriormente. Una vez enviada la acción, el reductor la recibe. El reductor toma el estado actual de la aplicación y la acción enviada y produce el siguiente estado de la aplicación basado en action.type.

Para que nuestra aplicación React utilice la tienda Redux, usamos el componente Provider proporcionado por la librería de react-redux y lo ponemos como la raíz de todos los componentes.

Para acceder a nuestra tienda Redux dentro de nuestros componentes React utilizamos la función especial connect(). Esta función nos da acceso al despacho y cuando pasamos en mapStateToProps nos da acceso al estado. mapStateToProps es una función que toma el estado de nuestra aplicación como parámetro y devuelve un objeto con claves de ese objeto convirtiéndose en los puntales del componente de forma que cada vez que usamos este.props.key_name recuperamos el estado que necesitamos.

Con eso fuera del camino, volvamos al archivo Post.js y agreguemos algunos botones para Borrar y Editar.


import React, { Component } from 'react';

class Post extends Component {
  render() {
  return (
    <div>
      <h2>{this.props.post.title}</h2>
      <p>{this.props.post.message}</p>
      <button>Edit</button>
      <button>Delete</button>
    </div>
  );
 }
}
export default Post;

Vamos a abordar la funcionalidad de eliminar primero, ya que es más fácil. Lo que queremos hacer es que cada vez que el usuario haga clic en el botón de eliminar debe eliminar el mensaje. Ahora para hacer eso necesitamos identificar qué mensaje está borrando el usuario y podemos hacerlo con la propiedad post.id que incluimos cuando añadimos el mensaje anteriormente en el componente PostForm.

Así que necesitamos las siguientes cosas, primero necesitamos un manejador onClick para que cuando el usuario haga clic en el botón de eliminar podamos hacer algo. Entonces lo que necesitamos es enviar una acción del tipo 'DELETE_POST'. Sabemos muy bien cómo ponerlo en marcha y eso es usando connect.dit.

import React, { Component } from 'react';

import {connect} from 'react-redux';

class Post extends Component {
  render() {
  return (
    <div>
      <h2>{this.props.post.title}</h2>
      <p>{this.props.post.message}</p>
      <button>Edit</button>
      <button 
      onClick={()=>this.props.dispatch({type:'DELETE_POST',id:this.props.post.id})}>
      Delete</button>
    </div>
  );
 }
}
export default connect()(Post);

Aquí dentro del manejador onClick tenemos una función de flecha que se invoca cuando el usuario hace clic en el botón de eliminar. Una vez que lo hacen, enviamos una acción de tipo 'DELETE_POST' y también pasamos el id del mensaje dado.

Para que esto funcione necesitamos añadir este evento en nuestros reductores así que volvamos a nuestros reductores bajo crud-redux/src/reductores/postReducer.js y agreguemos lo siguiente-

 


const postReducer = (state = [], action) => {
  switch(action.type) {
    case 'ADD_POST':
      return state.concat([action.data]);
    case 'DELETE_POST':
      return state.filter((post)=>post.id !== action.id);
    default:
      return state;
  }
}
export default postReducer;

Aquí usamos Array.prototype.filter para eliminar el post con el id que coincida con action.id.

Ahora que está en su lugar, vuelva a la aplicación e intente añadir algunos datos y luego haga clic en el botón eliminar. Si el mensaje desaparece, entonces genial, has implementado con éxito la D de esta aplicación CRUD. Lo único que queda es la operación de actualización.

La última y última operación CRUD es la operación Update y ésta es un poco diferente. Todas las demás operaciones que hemos visto son en su mayoría operaciones de un solo clic. Usted hace clic en el post, el post se añade, haga clic en borrar el post se elimina. Este no es el caso de Update. Porque para actualizar una entrada existente, primero el usuario hace clic en editar y luego le proporcionamos una forma en la que puede cambiar el título de la entrada y el mensaje. Por último, cuando envían los cambios, realizamos las actualizaciones necesarias y lo mostramos en el navegador. Por lo tanto, sabemos que la operación de actualización es una operación de dos pasos.

Una forma de hacerlo es usar un booleano en nuestro objeto de datos. Este booleano será falso inicialmente cuando se agreguen los mensajes, pero cuando el usuario hace clic en editar, cambiamos su valor a true. Si el valor de este booleano es verdadero entonces en lugar de renderizar un componente Post normal, renderizamos un EditComponent especial que tendrá un formulario con campos de título y mensaje. Una vez que el usuario ha realizado los cambios necesarios y la actualización de los hits volvemos a renderizar el componente Post pero con el valor actualizado. Así que hagámoslo.

Dentro de crud-redux/src/PostForm.js se hacen los siguientes cambios-

 

import React, { Component } from 'react';
import {connect} from 'react-redux';
class PostForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();
    const title = this.getTitle.value;
    const message =  this.getMessage.value;
    const data = {
      id: new Date(),
      title,
      message,
      editing:false
    }
    this.props.dispatch({
      type:'ADD_POST',
      data});
    this.getTitle.value = '';
    this.getMessage.value = '';
  }
render() {
return (
<div>
  <h1>Create Post</h1>
  <form onSubmit={this.handleSubmit}>
   <input required type="text" ref={(input)=>this.getTitle = input} 
    placeholder="Enter Post Title"/>
   <br /><br />
   <textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28" 
    placeholder="Enter Post" />
   <br /><br />
   <button>Post</button>
  </form>
</div>
);
}
}
export default connect()(PostForm);

Aquí hemos añadido una propiedad extra llamada'editing' y hemos establecido su valor como false. Luego cree un archivo llamado EditComponent.js dentro de la carpeta src. Una vez hecho esto, diríjase a crud-redux/src/AllPost.js y haga los siguientes cambios-

import React, { Component } from 'react';

import { connect } from 'react-redux';

import Post from './Post';

import EditComponent from './EditComponent';

class AllPost extends Component {
    render() {
        return (
            <div>
                <h1>All Posts</h1>
                {this.props.posts.map((post) => (
                    <div key={post.id}>
                        {post.editing ? <EditComponent post={post} key={post.id} /> :
                            <Post key={post.id} post={post} />}
                    </div>
                ))}
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        posts: state
    }
}
export default connect(mapStateToProps)(AllPost);

All that this code is doing is that it is checking the value of editing in each of the posts and if it is true then instead of rendering the Post component it is rendering the EditComponent and passing it the post as a prop.

Before going in and adding the Form in EditComponent, we need to make one more change inside Post.js so go inside crud-redux/src/Post.js and make the following change-

 

import React, { Component } from 'react';

import {connect} from 'react-redux';

class Post extends Component {
  render() {
  return (
    <div>
      <h2>{this.props.post.title}</h2>
      <p>{this.props.post.message}</p>
      <button
       onClick={()=>this.props.dispatch({type:'EDIT_POST',id:this.props.post.id})}>
       Edit</button>
      <button 
      onClick={()=>this.props.dispatch({type:'DELETE_POST',id:this.props.post.id})}>
      Delete</button>
    </div>
  );
 }
}
export default connect()(Post);

Todo lo que estamos haciendo es que cuando el usuario hace clic en el botón de edición estamos enviando una acción del tipo'EDIT_POST' y también pasando el id del mensaje.

Ya que hemos enviado un nuevo evento, necesitamos hacer algunos cambios en postReducer.js así que ve a este archivo y haz los siguientes cambios-

const postReducer = (state = [], action) => {
  switch(action.type) {
    case 'ADD_POST':
      return state.concat([action.data]);
    case 'DELETE_POST':
      return state.filter((post)=>post.id !== action.id);
    case 'EDIT_POST':
      return state.map((post)=>post.id === action.id ? {...post,editing:!post.editing}:post)
    default:
      return state;
  }
}
export default postReducer;

Aquí estamos usando Array.prototype.map para hacer un bucle sobre cada ítem y luego verificar el id del post con el id que fue pasado en la acción. Si hay una coincidencia, devuelva un nuevo objeto pero cambie el valor de edición a true si era false o viceversa. Si no hay coincidencia, simplemente devuelva el objeto tal como está.

Finalmente, vayamos a EditComponent.js y agreguemos lo siguiente.

import React, { Component } from 'react';
import { connect } from 'react-redux';


class EditComponent extends Component {
handleEdit = (e) => {
  e.preventDefault();
  const newTitle = this.getTitle.value;
  const newMessage = this.getMessage.value;
  const data = {
    newTitle,
    newMessage
  }
  this.props.dispatch({ type: 'UPDATE', id: this.props.post.id, data: data })
}
render() {
return (
<div>
  <form onSubmit={this.handleEdit}>
    <input required type="text" ref={(input) => this.getTitle = input}
    defaultValue={this.props.post.title} placeholder="Enter Post Title" /><br /><br />
    <textarea required rows="5" ref={(input) => this.getMessage = input}
    defaultValue={this.props.post.message} cols="28" placeholder="Enter Post" /><br /><br />
    <button>Update</button>
  </form>
</div>
);
}
}
export default connect()(EditComponent);

Aquí estamos creando otro formulario que tiene un handler onSubmit. Cuando se envía el formulario, se invoca esta función.handleEdit. Esta función toma el evento como parámetro. e.preventDefault() detiene la actualización de la página. Luego estamos tomando los datos de las entradas usando refs y poniéndolos dentro de un objeto, finalmente estamos enviando una nueva acción con la propiedad tipo'UPDATE'. También estamos pasando el identificador del mensaje que necesita ser actualizado junto con los datos actualizados. No olvide utilizar la función de conexión cuando envíe acciones.

Ya que hemos añadido un nuevo evento en nuestros archivos, necesitamos hacer algunos cambios en el reductor, así que regresa al reductor y haz los siguientes cambios.

const postReducer = (state = [], action) => {
  switch(action.type) {
    case 'ADD_POST':
      return state.concat([action.data]);
    case 'DELETE_POST':
      return state.filter((post)=>post.id !== action.id);
    case 'EDIT_POST':
      return state.map((post)=>post.id === action.id ? {...post,editing:!post.editing}:post)
    case 'UPDATE':
      return state.map((post)=>{
        if(post.id === action.id) {
          return {
             ...post,
             title:action.data.newTitle,
             message:action.data.newMessage,
             editing: !post.editing
          }
        } else return post;
      })
    default:
      return state;
  }
}
export default postReducer;

Aquí todo lo que estamos haciendo es usar Array.prototype.map y hacer un bucle sobre cada mensaje y el mensaje cuyo id coincide con el id que se pasó en la acción estamos devolviendo un nuevo objeto pero con los valores actualizados para el título y el mensaje.

Con eso hemos terminado, dirígete a la aplicación, añade algunos mensajes y pulsa el botón editar.

Cuando pulsamos el botón editar, el mensaje cambia a un formulario con el título y los campos de mensaje rellenados con los valores actuales. Hagamos un cambio y pongamos al día.

Genial, así que nuestra funcionalidad CRUD es completa. Ahora agreguemos algunos estilos para que se vea bien. He escrito todas las CSS para esta aplicación dentro de index.css sólo para mantener las cosas simples. Aquí está todo el código CSS que necesitas.

body {
margin: 0;
padding: 0;
font-family: 'Work Sans',sans-serif;
background:#f4f4ef;
}


.center {
text-align: center;
}
a {
color:#636363;
text-decoration: none;
transition:all 0.5s ease-in;
}


a:hover {
text-decoration: underline;
}


/* Navigation */


.navbar {
background:#fff;
text-align:center;
padding:1.5rem 0;
width:100%;
display:flex;
justify-content: center;
align-items: center;
box-shadow:0 5px 15px 0 rgba(46, 61, 73, 0.12);
}


.navbar h2 {
margin:0;
font-weight:200;
font-size:30px;
letter-spacing: 4px;
font-family:'Work Sans',sans-serif;
}


/* Post Styles */
.post-container {
background:#fff;
padding:50px;
width:50%;
margin:40px auto;
box-shadow:0 5px 15px 0 rgba(46, 61, 73, 0.12);
font-family:'Work Sans',sans-serif;
}


.post_heading {
text-align:center;
font-weight:400;
font-size:40px;
color:#636363;
}


.form {
display:flex;
flex-direction: column;
justify-content: center;
align-items: center;
}


.form input {
height:44px;
padding-left:15px;
padding-right:15px;
border: 1px solid #dbe2e8;
font-size:14px;
border-radius:2px;
color:#636363;
box-shadow: 0 2px 2px 0 rgba(46, 60, 73, 0.05);
outline:none;
width:80%;
}


.form textarea {
width:80%;
max-width:80%;
padding-left:15px;
padding-right:15px;
padding-top:15px;
border: 1px solid #dbe2e8;
font-size:14px;
border-radius:2px;
color:#636363;
font-family: 'Work Sans,',sans-serif;
box-shadow: 0 2px 2px 0 rgba(46, 60, 73, 0.05);
outline:none;
}


/* All Posts */


.all_post_heading {
text-align:center;
font-weight:400;
font-size:40px;
color:#636363;
word-wrap: break-word;
}


.post {
background:#fff;
width:60%;
margin:0 auto;
padding:30px;
margin-bottom:20px;
box-shadow: 10px 10px 12px 0 rgba(46, 60, 73, 0.05);
}


.form button {
background:#02b3e4;
padding:15px 40px;
text-align: center;
color:#fff;
font-family:'Work Sans',sans-serif;
border-radius:4px;
border:none;
font-size:20px;
}


.post_title {
text-align:center;
font-weight:400;
font-size:40px;
color:#636363;
word-wrap: break-word;
}


.post_message {
font-size:25px;
font-weight: 200;
line-height: 2.5rem;
text-align:center;
word-wrap: break-word
}


.control-buttons {
display:flex;
}


.delete {
background: #ff7777;
border:none;
text-align: center;
font-size:20px;
padding:10px 25px;
border-radius:20px;
cursor:pointer;
outline: none;
color:#fff;
}


.edit {
background:#02b3e4;;
border:none;
text-align: center;
font-size:20px;
padding:10px 25px;
border-radius:20px;
cursor:pointer;
margin-right:auto;
outline: none;
color:#fff;
}

Con todo el código CSS, aquí están los cambios finales para cada uno de los componentes-

App.js

import React, { Component } from 'react';
import PostForm from './PostForm';
import AllPost from './AllPost';


class App extends Component {
render() {
return (
<div className="App">
  <div className="navbar">
    <h2 className="center ">Post It</h2>
    </div>
    <PostForm />
    <AllPost />
</div>
);
}
}
export default App;

PostForm.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
class PostForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
 const title = this.getTitle.value;
 const message = this.getMessage.value;
 const data = {
  id: new Date(),
  title,
  message,
  editing: false
 }
 this.props.dispatch({
 type: 'ADD_POST',
 data
 })
 this.getTitle.value = '';
 this.getMessage.value = '';
}
render() {
return (
<div className="post-container">
  <h1 className="post_heading">Create Post</h1>
  <form className="form" onSubmit={this.handleSubmit} >
   <input required type="text" ref={(input) => this.getTitle = input}
   placeholder="Enter Post Title" /><br /><br />
   <textarea required rows="5" ref={(input) => this.getMessage = input}
   cols="28" placeholder="Enter Post" /><br /><br />
   <button>Post</button>
  </form>
</div>
);
}
}
export default connect()(PostForm);

Post.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
class Post extends Component {
render() {
return (
<div className="post">
  <h2 className="post_title">{this.props.post.title}</h2>
  <p className="post_message">{this.props.post.message}</p>
  <div className="control-buttons">
    <button className="edit"
    onClick={() => this.props.dispatch({ type: 'EDIT_POST', id: this.props.post.id })
    }
    >Edit</button>
    <button className="delete"
    onClick={() => this.props.dispatch({ type: 'DELETE_POST', id: this.props.post.id })}
    >Delete</button>
  </div>
</div>
);
}
}
export default connect()(Post);

AllPost.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import Post from './Post';
import EditComponent from './EditComponent';
class AllPost extends Component {
render() {
return (
<div>
  <h1 className="post_heading">All Posts</h1>
  {this.props.posts.map((post) => (
  <div key={post.id}>
    {post.editing ? <EditComponent post={post} key={post.id} /> : <Post post={post}
    key={post.id} />}
  </div>
))}
</div>
);
}
}

const mapStateToProps = (state) => {
return {
posts: state
}
}
export default connect(mapStateToProps)(AllPost);

EditComponent.js

import React, { Component } from 'react';
import { connect } from 'react-redux';


class EditComponent extends Component {
handleEdit = (e) => {
  e.preventDefault();
  const newTitle = this.getTitle.value;
  const newMessage = this.getMessage.value;
  const data = {
    newTitle,
    newMessage
  }
  this.props.dispatch({ type: 'UPDATE', id: this.props.post.id, data: data })
}
render() {
return (
<div key={this.props.post.id} className="post">
  <form className="form" onSubmit={this.handleEdit}>
    <input required type="text" ref={(input) => this.getTitle = input}
    defaultValue={this.props.post.title} placeholder="Enter Post Title" /><br /><br />
    <textarea required rows="5" ref={(input) => this.getMessage = input}
    defaultValue={this.props.post.message} cols="28" placeholder="Enter Post" /><br /><br />
    <button>Update</button>
  </form>
</div>
);
}
}
export default connect()(EditComponent);

postReducer.js

const postReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_POST':
return state.concat([action.data])
case 'DELETE_POST':
return state.filter((post) => post.id !== action.id)
case 'EDIT_POST':
return state.map((post) => post.id === action.id ? { ...post, editing: !post.editing } : post)
case 'UPDATE':
return state.map((post) => {
if (post.id === action.id) {
return {
...post,
title: action.data.newTitle,
message: action.data.newMessage,
editing: !post.editing
}
} else return post;
})
default:
return state;
}
}
export default postReducer;

 

Aquí he aplicado estilos básicos para que la aplicación se vea decente. Usted puede modificarla de acuerdo a sus necesidades. Así que si usted ha seguido conmigo en este largo blog hasta el final, por favor, felicítese a sí mismo, ya que ahora conoce los fundamentos básicos de Redux.

Hay conceptos más avanzados por los que no pasé, como los creadores de acciones, el middleware, la combinación de múltiples reductores y el manejo de peticiones externas de API sólo para mantener las cosas simples. Como siempre, desde que esta es mi primera entrada en el blog puede haber algunos errores o equivocaciones, así que por favor no dude en señalarlo.

Si usted quiere desafiarse a sí mismo entonces por favor extienda esta aplicación aún más. Intenta añadir una función de comentarios o un sistema de autenticación en el que sólo los usuarios autorizados puedan ver los mensajes y un usuario pueda modificar sólo sus propios mensajes, añadir soporte para los votos ascendentes y descendentes de cada mensaje, tal vez añadir algunas animaciones al crear un mensaje o eliminar un mensaje.

Gracias por leer.

articulo original: https://codeburst.io/redux-a-crud-example-abb834d763c9

 
by Janeth Kent Date: 23-08-2018 javascript node redux react visitas : 45505  
 
Janeth Kent

Janeth Kent

Licenciada en Bellas Artes y programadora por pasión. Cuando tengo un rato retoco fotos, edito vídeos y diseño cosas. El resto del tiempo escribo en MA-NO WEB DESIGN AND DEVELOPMENT.

 
 
 

Artículos relacionados

Crear PDF con Javascript y jsPDF

El formato PDF es muy útil para descargar datos de forma masiva en una aplicación web. Ayuda a los usuarios a descargar contenido dinámico en forma de archivo para que…

Como hacer tu propio cursor personalizado para tu web

Cuando empecé a ojear webs distintas y originales para aprender de ellas, de las primeras cosas que me llamaron la atención fue que algunas de ellas tenían sus propios cursores,…

Explorando la API de CSS Paint: Redondeo de formas parte 1

Añadir bordes a las formas complejas es un auténtico rollo (a veces), pero redondear las esquinas de las formas complejas es un suplicio jejeje. Por suerte, la API de pintura…

Cómo enviar un correo electrónico desde un formulario de contacto HTML

En el artículo de hoy vamos a escribir sobre cómo hacer un formulario que funcione y que al pulsar ese botón de envío sea funcional y envíe el correo electrónico…

Cómo hacer un sitio web multilingüe sin redireccionamiento

Hoy, vamos a hablar de cómo implementar un simple selector de idioma en el sitio web estático o básico, sin necesidad de ningún backend o llamadas a la base de…

Comenzando con Bootstrap-Vue paso a paso

Hoy te mostraremos cómo usar BootstrapVue, describiremos el proceso de instalación y mostraremos la funcionalidad básica. El proyecto está basado en el framework CSS más popular del mundo - Bootstrap, para…

Por qué los desarrolladores de JavaScript deberían preferir Axios a Fetch

Por qué los desarrolladores de JavaScript deberían preferir Axios a Fetch En mi artículo anterior, "Usando la Api Fetch Para Hacer Llamadas Ajax", hablé de los fundamentos de la API Fetch.…

Creación de un sencillo spinner-loader CSS

En el artículo de hoy mostraremos cómo animar un loader básico que gira cuando se define alguna acción predefinida, como cargar una imagen. Eso se puede utilizar en un sitio…

Los mejores selectores de fechas para Bootstrap y tu aplicación

Los selectores de fecha son widgets que permiten a los usuarios elegir una sola fecha o rango de fechas y horas. Es un elemento habitual para todo usuario de Internet,…

Validación de formularios HTML usando BULMA y vanilla JavaScript

Hoy vamos a escribir sobre los formularios de contacto y cómo validarlos usando JavaScript. El formulario de contacto parece ser una de las características principales de toda página web básica. Es…

Cómo usar el efecto Parallax.Js en tu sitio web

Hoy vamos a escribir sobre el efecto de parallax, similar al desplazamiento de parallax, y cómo implementarlo para mejorar su página de aterrizaje. En webdev, dicen que primero el móvil…

Usando la API FETCH para hacer llamadas AJAX - Una promesa cumplida

En este artículo hablamos sobre lo que son las llamadas AJAX y cómo utilizarlas de forma tradicional, utilizando el objeto XMLHttpRequest (XHR). En resumen, gracias a las llamadas AJAX una…

Clicky