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.
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.
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.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.
0 Comentarios