# Agenda de Contactos en ReactJS con Redux y Firebase (Parte 1)

## Introducción

A lo largo de este tutorial, aprenderás a utilizar ReactJS para crear una interfaz de usuario dinámica y atractiva que permita agregar, editar y eliminar contactos de manera eficiente. Utilizaremos Redux para gestionar el estado de nuestra aplicación de manera ordenada y predecible, y Firebase para almacenar y sincronizar datos en tiempo real de forma segura en la nube.

## Consideraciones

Para aprovechar al máximo este tutorial se recomienda tener una experiencia básica con ReactJS y una comprensión básica de los conceptos de Redux, como los 'actions', 'reducers', 'store', etc.

## Proyecto

El proyecto consiste en una agenda de contactos, donde el usuario podrá añadir nuevos contactos, actualizarlos y borrarlos. Usaremos Redux toolkit para la gestión del estado de la aplicación de manera organizada y eficaz. Conectaremos la aplicación con Firebase para una persistencia de datos segura.

## Inicialización del proyecto y creación de los componentes

Creamos un nuevo proyecto ReactJS con Vite, para ello corremos el siguiente comando:

```plaintext
npm create vite@latest react-redux-firebase -- --template react
```

Llamé al proyecto `react-redux-firebase` , entra al directorio y abre el fichero index.html. Vamos a añadir la `cdn` de bootstrap para los estilos:

```xml
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
```

Ahora vamos a crea el componente `src/components/NewContactForm.jsx` y añade el siguiente código:

```javascript

export const NewContactForm = () => {
    return (
        <>
            <form action="">
                <div className="row mb-3">
                    <div className="col">
                        <input type="text" name="firstname" className="form-control" placeholder="First name" aria-label="First name"/>
                    </div>
                    <div className="col">
                        <input type="text" name="lastname" className="form-control" placeholder="Last name" aria-label="Last name"/>
                    </div>
                </div>
                <div className="row mb-3">
                    <div className="col">
                        <input type="text" name="phone" className="form-control" placeholder="Phone number" aria-label="Phone number"/>
                    </div>
                    <div className="col">
                        <input type="text" name="email" className="form-control" placeholder="E-mail" aria-label="E-mail"/>
                    </div>
                </div>
                <div className="row">
                    <div className="col text-end">
                        <button className="btn btn-primary">GUARDAR</button>
                    </div>
                </div>
            </form>
        </>
    )
}
```

Tenemos un formulario con cuatro campos: firstname, lastname, phone y email.

Continuamos con el componente `src/components/ContactList.jsx` :

```javascript
import { Contact } from "./Contact"

const data = [{
    id: 1,
    firstname: 'Mark',
    lastname: 'Otto',
    phone: '3323233',
    email: 'info@email.com.ar'
},{
    id: 2,
    firstname: 'Jacob',
    lastname: 'Thornton',
    phone: '3323233',
    email: 'info@email.com.ar'
},{
    id: 3,
    firstname: 'Larry',
    lastname: 'Bird',
    phone: '3323233',
    email: 'info@email.com.ar'
}]

export const ContactList = () => {

    return (
        <table className="table">
            <thead>
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">Firstname</th>
                    <th scope="col">Lastname</th>
                    <th scope="col">Phone number</th>
                    <th scope="col">E-mail</th>
                    <th scope="col">Acciones</th>
                </tr>
            </thead>
            <tbody>
                { data.map(contact => (
                    <Contact key={contact.id} data={contact} />
                )) }
            </tbody>
        </table>
    )
}
```

Este componente consiste en una tabla en el cuál mostramos los datos de los contactos. Por el momento estamos utilizando un array `data` con datos de ejemplo para recorrerlo y mostrarlos en la tabla.

```javascript
{ data.map(contact => (
     <Contact key={contact.id} data={contact} />
)) }
```

El código arriba recorre el array y pasa los contactos al componente `Contact.jsx` que pasamos a crearlo a continuación.

Crea un componente `src/components/Contact.jsx` y agrega el siguiente código.

```javascript
import React, { useState } from "react";

export const Contact = ({ data }) => {
  const [edit, setEdit] = useState(false);

  const [contact, setContact] = useState({
    id: data.id,
    firstname: data.firstname,
    lastname: data.lastname,
    email: data.email,
    phone: data.phone
  });

  const toggleEdit = () => {
    setEdit(!edit);
  };

  let viewMode = {}
  let editMode = {}
  
  if(edit){
    viewMode.display = "none";
  } else {
    editMode.display = "none";
  }

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setContact({ ...contact, [name]: value });
  };

  const handleSave = () => {
    // Aquí puedes realizar la lógica para guardar los cambios
    console.log("Contacto actualizado:", contact);
    toggleEdit(); // Cambiar el modo de edición
  };

  return (
      <>
        <tr style={viewMode}>
          <th scope="row">{contact.id}</th>
          <td>{contact.firstname}</td>
          <td>{contact.lastname}</td>
          <td>{contact.phone}</td>
          <td>{contact.email}</td>
          <td>
            <button className="btn btn-sm btn-success" onClick={toggleEdit}>
              EDITAR
            </button>
            <button className="btn btn-sm btn-danger">BORRAR</button>
          </td>
        </tr>

        <tr style={editMode}>
          <th scope="row">{contact.id}</th>
          <td>
            <input
              type="text"
              name="firstname"
              className="form-control form-control-sm"
              value={contact.firstname}
              onChange={handleInputChange}
            />
          </td>
          <td>
            <input
              type="text"
              name="lastname"
              className="form-control form-control-sm"
              value={contact.lastname}
              onChange={handleInputChange}
            />
          </td>
          <td>
            <input
              type="text"
              name="email"
              className="form-control form-control-sm"
              value={contact.email}
              onChange={handleInputChange}
            />
          </td>
          <td>
            <input
              type="text"
              name="phone"
              className="form-control form-control-sm"
              value={contact.phone}
              onChange={handleInputChange}
            />
          </td>
          <td>
            <button className="btn btn-sm btn-danger" onClick={toggleEdit}>
              CANCELAR
            </button>
            <button className="btn btn-sm btn-primary" onClick={handleSave}>
              GUARDAR
            </button>
          </td>
        </tr>
      </>
  );
};
```

Se define el componente `Contact` como una función que toma un prop llamado `data`, que contiene la información del contacto.

Se utilizan dos estados de componente, `edit` y `contact`, ambos gestionados mediante el hook `useState`. `edit` controla si el contacto se está en modo edición o no, mientras que `contact` almacena los datos del contacto.

La función `toggleEdit` cambia el valor del estado `edit`, alternando entre los modos de visualización y edición cuando se hace clic en el botón "EDITAR" o "CANCELAR".

Los objetos `viewMode` y `editMode` se utilizan para controlar la visibilidad de las filas de la tabla dependiendo del modo (visualización o edición). Cuando se está en modo edición (`edit` es `true`), se establece `display: "none"` en `viewMode`, y viceversa.

La función `handleInputChange` se utiliza para manejar los cambios en los campos de entrada (inputs) del formulario de edición. Actualiza el estado `contact` cuando los usuarios escriben en los campos de entrada.

`handleSave` es una función que se ejecuta cuando se hace clic en el botón "GUARDAR" en el modo de edición. En este ejemplo, se muestra un mensaje en la consola con los datos actualizados y se cambia el modo a visualización llamando a `toggleEdit()`.

Finalmente, el componente `Contact` devuelve dos filas de una tabla. La primera fila muestra los datos del contacto en modo de visualización, mientras que la segunda fila muestra los mismos datos en campos de entrada en modo de edición. Sólo se muestra una fila dependiendo del modo (visualización o edición).

Para terminar, abre el componente `src/App.jsx` y agreguemos los componentes `NewContactForm.jsx` y `ContactList.jsx` :

```javascript
import { ContactList } from "./components/ContactList"
import { NewContactForm } from "./components/NewContactForm"

function App() {

  return (
    <>
      <main className='container-fluid'>
        <div className='row'>
          <div className='col-12 col-lg-8 mx-auto'>
            <h1 className='display-2 text-center'>AGENDA</h1>
            <hr />
          </div>
        </div>
        <div className='row'>
          <div className='col-12 col-lg-8 mx-auto'>
            <h2 className="display-6 text-center">Nuevo contacto</h2>
            <NewContactForm />
            <hr />
          </div>
        </div>
        <div className='row'>
          <div className='col-12 col-xl-8 mx-auto'>
            <h2 className="display-6 text-center">Contactos</h2>
            <ContactList />
            <hr />
          </div>
        </div>
      </main>
    </>
  )
}

export default App
```

Para correr el servidor de desarrollo ejecuta el siguiente comando:

```plaintext
npm run dev
```

En este punto deberíamos tener finalizado la interfaz de nuestra aplicación:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1696277905077/7bab8874-6c3f-434b-97a6-7127090654ee.gif align="center")

Si hacés click en el botón "EDITAR" podés pasar al modo "edición", si hacés click en el botón "CANCELAR" volvés al modo "visualización". Si modificás alguno de los datos, al presionar el botón "GUARDAR", podés ver por consola los datos actualizados.

A continuación seguimos con la instalación de Redux Toolkit y la configuración del store para nuestra app.

## Instalación y configuración de Redux Toolkit

Para añadir el store a nuestra aplicación debemos instalar los siguientes paquetes:

```plaintext
npm install @reduxjs/toolkit react-redux
```

Ahora creamos un fichero `src/store/store.js` y configuramos el store:

```javascript
import { configureStore } from "@reduxjs/toolkit"

export default configureStore({
    reducer: {}
})
```

Con esto ya tienes configurado el store, añadiremos los 'reducers' más adelante cuando pasemos a crear el slice para los contactos, ahora debemos conectarlo a la aplicación, abre el fichero `src/main.jsx` :

```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { Provider } from 'react-redux'
import store from './store/store.js'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
)
```

Mediante el `Provider` que nos proporciona `react-redux` pasamos el `store` a nuestra aplicación. Ahora ya estamos conectados, seguimos con la creación del `slice` .

## Creación del slice contactsSlice

Creamos un fichero `src/features/contacts/contactsSlice.js` y agregamos el siguiente código:

```javascript
import { createEntityAdapter, createSlice } from "@reduxjs/toolkit"

const contactsAdapter = createEntityAdapter()

const initialState = contactsAdapter.getInitialState()

export const contactsSlice = createSlice({
    name: "contacts",
    initialState,
    reducers: {
        addContact: {
            reducer: contactsAdapter.addOne,
            prepare(firstname, lastname, phone, email){
                return {
                    payload: {
                        id: Date.now(),
                        firstname,
                        lastname,
                        phone,
                        email,
                    }
                }
            }
        }
    },
})

export default contactsSlice.reducer

export const { addContact } = contactsSlice.actions

export const { selectAll: selectContacts } = contactsAdapter.getSelectors(
    (state) => state.contacts
)
```

El código comienza importando `createEntityAdapter` y `createSlice` de `@reduxjs/toolkit`.

Se crea un adaptador de entidad llamado `contactsAdapter` utilizando la función `createEntityAdapter`. Este adaptador se utiliza para gestionar la entidad de contactos y proporciona funciones `reducers` predefinidas para agregar, actualizar, eliminar y seleccionar contactos.

Se obtiene el estado inicial del adaptador de entidad utilizando `contactsAdapter.getInitialState()` y lo guardamos en `initialState` .

Se utiliza `createSlice` para crear un slice llamado `contactsSlice`. Un slice en Redux Toolkit es una forma de definir un conjunto de reducers y actions relacionados con una parte específica del estado de la aplicación.

* `name`: Se especifica el nombre del slice como "contacts".
    
* `initialState`: Se utiliza el estado inicial obtenido del adaptador de entidad.
    
* `reducers`: Aquí se define una acción llamada "addContact". Esta acción tiene un `reducer` que apunta a `contactsAdapter.addOne`. El `reducer` se encargará de agregar un nuevo contacto al estado.
    
* `prepare`: Esta es una función que se ejecuta antes de que el `reducer` realice su trabajo. Prepara los datos que se pasarán al `reducer`. En este caso, se espera recibir `firstname`, `lastname`, `phone`, y `email`, y se crea un objeto de `payload` que incluye estos datos junto con un `id` generado usando [`Date.now`](http://Date.now)`()`.
    

Se exporta el `reducer` de `contactsSlice` como `default`. Esto permite importar este reducer en otros lugares de la aplicación.

Se exporta la acción "addContact" directamente de `contactsSlice.actions`.

Se utiliza `contactsAdapter.getSelectors` para crear un selector llamado `selectContacts`. Este selector se utiliza para obtener todos los contactos del estado de Redux.

Ahora que tenemos los 'reducers' definidos en `contactsSlice` podemos añadirlo al `store`. Abrimos el fichero `src/store/store.js` e importamos los 'reducers' como sigue:

```javascript
import { configureStore } from "@reduxjs/toolkit"
import contactsReducer from "../features/contacts/contactsSlice"

export default configureStore({
    reducer: {
        contacts: contactsReducer
    }
})
```

Ahora la configuración del store está completa, continuamos con los 'actions'.

## Utilización del 'action' addContact

Ahora vamos a utilizar el 'actions' `addContact` para añadir un nuevo contacto al store, abrimos el fichero `src/components/NewContactForm.jsx` y lo modificamos para añadir esta funcionalidad.

```javascript
import { useRef } from "react"
import { useDispatch } from "react-redux"
import { addContact } from "../features/contacts/contactsSlice"

export const NewContactForm = () => {
    const dispatch = useDispatch()

    const inputFirstname = useRef(null)
    const inputLastname = useRef(null)
    const inputPhone = useRef(null)
    const inputEmail = useRef(null)

    const handleSave = () => {
        const firstname = inputFirstname.current.value
        const lastname = inputLastname.current.value
        const phone = inputPhone.current.value
        const email = inputEmail.current.value

        dispatch(addContact(firstname, lastname, phone, email))

        inputFirstname.current.value = ""
        inputLastname.current.value = ""
        inputPhone.current.value = ""
        inputEmail.current.value = ""
    }

    return (
        <>
            <form action="">
                <div className="row mb-3">
                    <div className="col">
                        <input type="text"
                                name="firstname"
                                className="form-control"
                                placeholder="First name"
                                aria-label="First name"
                                ref={inputFirstname}
                        />
                    </div>
                    <div className="col">
                        <input type="text"
                                name="lastname"
                                className="form-control"
                                placeholder="Last name"
                                aria-label="Last name"
                                ref={inputLastname}
                        />
                    </div>
                </div>
                <div className="row mb-3">
                    <div className="col">
                        <input type="text"
                                name="phone"
                                className="form-control"
                                placeholder="Phone number"
                                aria-label="Phone number"
                                ref={inputPhone}
                        />
                    </div>
                    <div className="col">
                        <input type="text"
                                name="email"
                                className="form-control"
                                placeholder="E-mail"
                                aria-label="E-mail"
                                ref={inputEmail}
                        />
                    </div>
                </div>
                <div className="row">
                    <div className="col text-end">
                        <button type="button" className="btn btn-primary" onClick={handleSave}>GUARDAR</button>
                    </div>
                </div>
            </form>
        </>
    )
}
```

Se importan las dependencias necesarias para el componente, incluyendo `useRef` de React para crear referencias a los elementos de entrada del formulario, `useDispatch` de `react-redux` para obtener la función `dispatch` de Redux, y `addContact` de `../features/contacts/contactsSlice` para utilizar la acción que agrega un nuevo contacto al estado de Redux.

Se crean cuatro referencias (`inputFirstname`, `inputLastname`, `inputPhone`, `inputEmail`) utilizando `useRef`. Estas referencias se utilizarán para acceder a los valores de los campos de entrada del formulario y borrarlos después de guardar un contacto.

Se define la función `handleSave`, que se ejecuta cuando se hace clic en el botón "GUARDAR". En esta función:

* Se obtienen los valores de los campos de entrada del formulario utilizando las referencias creadas (`inputFirstname.current.value`, `inputLastname.current.value`, etc.).
    
* Se utiliza `dispatch` para llamar a la acción `addContact` importada desde `contactsSlice`. Esta acción agrega un nuevo contacto al estado de Redux utilizando los valores obtenidos de los campos de entrada.
    
* Después de agregar el contacto, se borran los valores de los campos de entrada estableciendo `inputFirstname.current.value`, `inputLastname.current.value`, etc. a una cadena vacía, lo que vacía los campos del formulario.
    

Cada campo de entrada utiliza las referencias creadas (`ref={inputFirstname}`, `ref={inputLastname}`, etc.) para acceder a sus valores.

## Obtenemos los datos desde el store

Abrimos el compoente `src/components/ContactList.jsx` y realizamos las siguientes modificaciones para recuperar los datos desde el store y mostrarlos en la tabla.

```javascript
import { useSelector } from "react-redux"
import { Contact } from "./Contact"
import { selectContacts } from "../features/contacts/contactsSlice"

export const ContactList = () => {
    const data = useSelector(state => selectContacts(state))

    return (
        <table className="table">
            <thead>
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">Firstname</th>
                    <th scope="col">Lastname</th>
                    <th scope="col">Phone number</th>
                    <th scope="col">E-mail</th>
                    <th scope="col">Acciones</th>
                </tr>
            </thead>
            <tbody>
                { data.map(contact => (
                    <Contact key={contact.id} data={contact} />
                )) }
            </tbody>
        </table>
    )
}
```

Se importan las dependencias necesarias, incluyendo `useSelector` de `react-redux` para seleccionar datos del estado de Redux y `selectContacts` de `"../features/contacts/contactsSlice"` para obtener la lista de contactos del estado de Redux.

Borramos el array `data` con los datos de ejemplo. Usamos `useSelector` para seleccionar los datos necesarios del estado de Redux. Por medio de `selectContacts` que definimos en el `contactsSlice.js` obtenemos la lista de contactos desde el estado y lo guardamos en la variable data. Finalmente por medio de `map` recorremos el array `data` y pasamos cada contacto al componente `Contacto` para mostrarlos en pantalla.

Como al principio no tenemos datos en el store, la tabla se mostrará vacía. Cuando añadamos un nuevo contacto por medio del formulario, el componente 'ContactList' detectará el cambio en el store y se actualizará mostrando los datos del nuevo contacto.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1696623992581/9a0d812b-db3a-42f6-baef-7a05799a729d.gif align="center")

Por medio de la extensión de Redux DevTools de Chrome podemos ver como se guardan en el store los datos ingresados en el formulario.

A continuación seguimos con la funciones de Editar y Borrar contacto.

## Borrar y Actualizar un contacto

Abrimos el fichero `src/features/contacts/contactsSlice.js` y añadimos el siguiente código:

```javascript
export const contactsSlice = createSlice({
    name: "contacts",
    initialState,
    reducers: {
        addContact: {
            reducer: contactsAdapter.addOne,
            prepare(firstname, lastname, phone, email){
                return {
                    payload: {
                        id: Date.now(),
                        firstname,
                        lastname,
                        phone,
                        email,
                    }
                }
            }
        },
        removeContact: contactsAdapter.removeOne,
        updateContact: contactsAdapter.updateOne,
    },
})

//...

export const { addContact, removeContact, updateContact } = contactsSlice.actions
```

Agregamos dos líneas, `removeContact: contactsAdapter.removeOne` y `updateContact: contactsAdapter.updateOne` para borrar y actualizar un contacto respectivamente.

Abrimos el componente `src/components/Contact.jsx` y realizamos las siguientes modificaciones:

```javascript
import React, { useState } from "react"
import { removeContact, updateContact } from "../features/contacts/contactsSlice"
import { useDispatch } from "react-redux"

export const Contact = ({ data }) => {
  const [edit, setEdit] = useState(false)
  const dispatch = useDispatch();

  const [contact, setContact] = useState({
    id: data.id,
    firstname: data.firstname,
    lastname: data.lastname,
    email: data.email,
    phone: data.phone
  });

  const toggleEdit = () => {
    setEdit(!edit);
  };

  let viewMode = {}
  let editMode = {}
  
  if(edit){
    viewMode.display = "none";
  } else {
    editMode.display = "none";
  }

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setContact({ ...contact, [name]: value });
  };

  const handleSave = () => {
    dispatch(updateContact({ id:contact.id, changes: contact}))
    toggleEdit();
  }

  const handleRemove = () => {
    dispatch(removeContact(contact.id));
  }

  return (
      <>
        <tr style={viewMode}>
          <th scope="row">{contact.id}</th>
          <td>{contact.firstname}</td>
          <td>{contact.lastname}</td>
          <td>{contact.phone}</td>
          <td>{contact.email}</td>
          <td>
            <button className="btn btn-sm btn-success" onClick={toggleEdit}>
              EDITAR
            </button>
            <button className="btn btn-sm btn-danger" onClick={handleRemove}>BORRAR</button>
          </td>
        </tr>

        <tr style={editMode}>
          <th scope="row">{contact.id}</th>
          <td>
            <input
              type="text"
              name="firstname"
              className="form-control form-control-sm"
              value={contact.firstname}
              onChange={handleInputChange}
            />
          </td>
          <td>
            <input
              type="text"
              name="lastname"
              className="form-control form-control-sm"
              value={contact.lastname}
              onChange={handleInputChange}
            />
          </td>
          <td>
            <input
              type="text"
              name="email"
              className="form-control form-control-sm"
              value={contact.email}
              onChange={handleInputChange}
            />
          </td>
          <td>
            <input
              type="text"
              name="phone"
              className="form-control form-control-sm"
              value={contact.phone}
              onChange={handleInputChange}
            />
          </td>
          <td>
            <button className="btn btn-sm btn-danger" onClick={toggleEdit}>
              CANCELAR
            </button>
            <button className="btn btn-sm btn-primary" onClick={handleSave}>
              GUARDAR
            </button>
          </td>
        </tr>
      </>
  );
};
```

Se importan las dependencias necesarias, `removeContact` y `updateContact` de `"../features/contacts/contactsSlice"` para utilizar las acciones relacionadas con contactos definidas en el `contactsSlice.js`, y `useDispatch` de `react-redux` para obtener la función `dispatch` de Redux.

Modificamos la función handleSave para que al hacer clic en el botón "GUARDAR" utilice `dispatch` para llamar a la acción `updateContact`, que actualiza los datos del contacto en el estado global de Redux con los cambios realizados en el estado `contact`. Luego, se llama a `toggleEdit` para cambiar el modo de edición de vuelta a la de visualización. El 'action' `updateContact` recibe un objeto con dos campos: `id` que hace referencia al registro que vamos a actualizar y `changes` con los datos actualizados.

Creamos la función `handleRemove`. Esta función se llama cuando se hace clic en el botón "BORRAR". Utiliza `dispatch` para llamar a la acción `removeContact`, que elimina el contacto del estado global de Redux utilizando su `id`.

Finalmente vínculamos estas funciones con sus correspondientes botones, "GUARDAR" y "BORRAR", para que al hacer click, ejecute la función correspondiente.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1696797855714/9ead608a-d55f-40b1-82cf-c092523cc84e.gif align="center")

## Conclusión

¡Hemos completado la primera parte de este tutorial! En esta etapa, hemos construido con éxito la interfaz frontal de nuestra aplicación. En la segunda parte, exploraremos cómo conectar nuestra aplicación con Firebase y realizaremos las adaptaciones necesarias en el slice para habilitar las peticiones asincrónicas a través de `redux-thunk` de Redux Toolkit.

Espero verte en la siguiente entrega. ¡Saludos y hasta pronto! 👋😊
