Programación, Tecnología y Más...

Programación, Tecnología y Más...

Agenda de Contacto - React y LocalStorage

 Tengo un par de semanas practicando y estudiando  react js(ya que en mi trabajo actual se empezará a implementar en algunos proyectos), si sigues mis artículos te darás cuenta que tengo experiencia con vue. Así que mis conocimientos en vue(principalmente en js) me han servido en react, si bien es cierto hay varias diferencias entre react y vue, los conceptos son los mismos(componentes, estado, rutas, props, etc). En artículos posteriores publicare  algunas de las diferencias entre vue y react.

Luego de esa pequeña introducción 😄 empecemos a codificar, la pequeña aplicación que haremos será la siguiente puedes verla desplegada en netlify  phonebook-app  es una pequeña aplicación para administrar contactos que almacena los datos en localstorage.


Lo primero que haremos será crear nuestra aplicación de react y abrirla en nuestro querido vscode.

npx create-react-app phonebook-app
cd phonebook-app
code .
Ya abierto nuestro proyecto usaremos el cdn de bootstrap para darle un poco de diseño a nuestra app, asi que en nuestro index.html agregamos el cdn de bootstrap y de sus iconos
<head>
...
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" /> 
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.6.1/font/bootstrap-icons.css" /> <title>PhoneBook App</title> 
</head>
También instalaremos sweetalert2 para los mensajes que mostraremos al usuario.
npm install sweetalert2
Empezaremos creando algunos componentes así que dentro de src creemos la carpeta components y agreguemos los siguientes componentes 
import React from "react";

export const Navbar = () => {
	return (
		<nav className="navbar navbar-light">
			<div className="container-fluid">
				<span className="navbar-brand">
					<b>PhoneBook-App</b>
				</span>
			</div>
		</nav>

	);
};
import React from "react";

export const Footer = () => {
	return (
		<footer className="navbar fixed-bottom text-black footer">
			<div className="container d-flex justify-content-center">
				<span>
					Cristian Torres <b>{new Date().getFullYear()}</b>
				</span>
			</div>
		</footer>
	);
};

Los 2 componentes anteriores son componentes sencillos para agregar un navbar y un footer.

Ahora agregaremos los componentes que tendrán la funcionalidad de nuestra app.

import React, { useEffect, useState } from "react";

const initialForm = {
	id: null,
	name: "",
	phone: "",
};

export const ContactForm = ({
	addContact,
	updateContact,
	dataToEdit,
	setDataToEdit,
}) => {
	const [form, setForm] = useState(initialForm);

	useEffect(() => {
		if (dataToEdit) {
			setForm(dataToEdit);
		} else {
			setForm(initialForm);
		}
	}, [dataToEdit]);

	const handleChange = e => {
		setForm({
			...form,
			[e.target.name]: e.target.value,
		});
	};

	const handleSubmit = e => {
		e.preventDefault();
		//* validation inputs not null
		if (
			form.name.trim().length > 2 ||
			form.phone.trim().length > 2
		) {
			//* validation add or update
			if (form.id === null) {
				addContact(form);
			} else {
				updateContact(form);
			}
			handleReset();
		}
	};

	const handleReset = () => {
		setForm(initialForm);
		setDataToEdit(null);
	};
	return (
		<div className="col-lg-6 col-md-6 col-sm-12 col-xs-12">
			<form onSubmit={handleSubmit}>
				<div className="form-floating mb-3">
					<input
						type="text"
						className="form-control"
						id="name"
						name="name"
						placeholder="Name"
						autoComplete="off"
						onChange={handleChange}
						value={form.name}
					/>
					<label htmlFor="name">Name</label>
				</div>
				<div className="form-floating mb-3">
					<input
						type="tel"
						className="form-control"
						id="phone"
						name="phone"
						placeholder="Phone"
						autoComplete="off"
						onChange={handleChange}
						value={form.phone}
					/>
					<label htmlFor="phone">Phone</label>
				</div>

				<div className="mb-3 row">
					<div className="col-sm-12">
						{dataToEdit != null ? (
							<button
								type="submit"
								className="btn btn-primary shadow-none"
							>
								<i className="bi bi-pencil"></i> Edit
							</button>
						) : (
							<button
								type="submit"
								className="btn btn-primary shadow-none"
							>
								<i className="bi bi-person-plus"></i> Add
							</button>
						)}
					</div>
				</div>
			</form>
		</div>
	);
};
El componente ContactForm es el encargado de manejar el formulario para agregar o editar un contacto, usamos los hooks de useState para manejar los datos del formulario(inputs) y useEffect para renderizar los datos del contacto cuando el formulario esté en modo edición.

import React from "react";
import { ContactInfo } from "./ContactInfo";

export const ItemContact = ({
	contactData,
	setDataToEdit,
	deleteContact,
}) => {
	return (
		<div className="col-lg-6 col-md-6 col-sm-12 col-xs-12">
			{contactData.length === 0 ? (
				<span className="d-flex justify-content-center">
					Not Contact 📞
				</span>
			) : (
				contactData.map(contact => (
					<ContactInfo
						key={contact.id}
						contact={contact}
						setDataToEdit={setDataToEdit}
						deleteContact={deleteContact}
					/>
				))
			)}
		</div>
	);
};

El componente ContactInfo sera el encargado de renderizar la información de los contactos cuando estos existan, la información de los contactos se imprimen llamando otro componente .
import React from "react";

export const ContactInfo = ({
	contact,
	setDataToEdit,
	deleteContact,
}) => {
	let { id, name, phone } = contact;
	return (
		<div className="p-2">
			<div role="alert" className="alert-info ">
				<div className="d-flex justify-content-between align-items-center">
					<div className="pt-2 ps-2 text-white">
						<h5>{name}</h5>
						<h6>{phone}</h6>
					</div>
					<div>
						<button
							type="button"
							className="btn btn-outline-success bt-sm mx-3"
							onClick={() => setDataToEdit(contact)}
						>
							<i className="bi bi-pencil"></i>
						</button>
						<button
							type="button"
							className="btn btn-outline-danger bt-sm mx-3"
							onClick={() => deleteContact(id)}
						>
							<i className="bi bi-trash"></i>
						</button>
					</div>
				</div>
			</div>
		</div>
	);
};


El componente ContactInfo es el que imprime la información de los contactos, también es el encargado de notificar a ContactForm cuando el usuario quiere editar o eliminar un contacto. 

 Estos serian todos los componentes que vamos a utilizar para este ejemplo ahora dentro de src creemos la carpeta pages y creamos el siguiente componente.
import React, { useEffect, useState } from "react";
import { ContactForm } from "../components/ContactForm";
import { ItemContact } from "../components/ItemContact";
import Swal from "sweetalert2";

export const Index = () => {
	const [contactData, setContactData] = useState([]);
	const [dataToEdit, setDataToEdit] = useState(null);

	//* get data local storage
	useEffect(() => {
		let getContacts = localStorage.getItem("contacts");
		if (getContacts != null) {
			setContactData(JSON.parse(getContacts));
		} else {
			setContactData([]);
		}
	}, []);

	//* update data localstorage
	useEffect(() => {
		localStorage.setItem(
			"contacts",
			JSON.stringify(contactData)
		);
	}, [contactData]);

	const addContact = data => {
		//* validation name not exist
		if (
			!contactData.find(
				c =>
					c.name.toLowerCase() === data.name.toLowerCase()
			)
		) {
			data.id = Date.now();
			setContactData([...contactData, data]);
			Swal.fire({
				title: "Contact Save!",
				icon: "success",
				confirmButtonColor: "#9bc59d",
			});
		}
	};

	const updateContact = data => {
		let newContact = contactData.map(c =>
			c.id === data.id ? data : c
		);
		setContactData(newContact);
		Swal.fire({
			title: "Contact Update!",
			icon: "success",
			confirmButtonColor: "#9bc59d",
		});
	};

	const deleteContact = id => {
		//let confirmDelete = window.confirm("Are you sure to delete the contact?")
		Swal.fire({
			title: "Are you sure to delete the contact?",
			icon: "warning",
			showCancelButton: true,
			confirmButtonColor: "#9bc59d",
			cancelButtonColor: "#212738",
			confirmButtonText: "Yes, delete it!",
		}).then(result => {
			if (result.isConfirmed) {
				let newData = contactData.filter(c => c.id !== id);
				setContactData(newData);
			}
		});
	};

	return (
		<div className="container mt-5 mb-5">
			<div className="row">
				<ContactForm
					addContact={addContact}
					updateContact={updateContact}
					dataToEdit={dataToEdit}
					setDataToEdit={setDataToEdit}
				/>
				<ItemContact
					contactData={contactData}
					setDataToEdit={setDataToEdit}
					deleteContact={deleteContact}
				/>
			</div>
		</div>
	);
};

Este componente representa nuestra vista principal (pudimos usar App.js pero prefiero mantener lo más limpio posible App.js). Acá es donde tenemos la lógica de las funciones para poder agregar, editar y eliminar los contactos también manejamos los datos de localStorage.

Por último en App.js tendremos el siguiente código.
import "./App.css";
import { Footer } from "./components/Footer";
import { Navbar } from "./components/Navbar";
import { Index } from "./pages/Index";

function App() {
	return (
		<div className="App">
			<Navbar />
			<Index />
			<Footer />
		</div>
	);
}

export default App;

Estilo de nuestra aplicación index.css.

@import url("https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@300&display=swap");

:root {
	--primary-color: #d0fcb3;
	--secondary-color: #9bc59d;
	--info-color: #212738;
	--black-color: #rgb(31, 30, 30);
	--body-color: #fcf7ff;
}

body {
	margin: 0;
	font-family: "Roboto Slab", "sans-serif";
	background-color: var(--body-color);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

.navbar {
	background-color: var(--primary-color);
}
.form-control:focus {
	border-color: var(--info-color);
	box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
		0 0 8px rgb(33, 39, 56);
}

.alert-info {
	background-color: var(--info-color);
}

.btn-primary {
	background-color: var(--secondary-color);
	border-color: var(--secondary-color);
	color: var(--black-color);
}
.btn-primary:hover,
.btn-primary:focus {
	background-color: var(--primary-color);
	border-color: var(--primary-color);
	color: var(--black-color);
}

.footer {
	background-color: var(--secondary-color);
}

export default App;

Resultado final de nuestra aplicación.


Si lo notastes pasamos las props entre varios componentes pudimos haber usado useContext para centralizar los datos y evitar estar pasando los datos entre componentes, pero como la aplicación es pequeña preferí hacerlo de esta manera para justamente ver cómo podemos pasar props entre componentes. 
puedes descargar el código desde este repositorio


si quieren donarme para una café lo pueden hacer aqui.

Hasta la próxima.

Saludos desde El Salvador...

Publicar un comentario

0 Comentarios