Un patrón para modificar un objeto específico en una matriz usando JavaScript.
Primeros pasos
Para este tutorial, usaremos CheatCode Next.js Boilerplate para darnos un punto de partida para nuestro trabajo. Primero, clonemos una copia:
Terminal
git clone https://github.com/cheatcode/nextjs-boilerplate.git
A continuación, instale las dependencias para el repetitivo:
Terminal
cd nextjs-boilerplate && npm install
Finalmente, inicie el servidor de desarrollo:
Terminal
npm run dev
Con eso, estamos listos para comenzar.
Construyendo un componente React para probar
Para contextualizar nuestro trabajo, vamos a construir un componente React simple basado en clases. Esto nos dará una situación en la que usar el patrón que aprenderemos tendrá más sentido.
/páginas/index.js
import React from "react";
import PropTypes from "prop-types";
import usersFixture from "../lib/users";
class Index extends React.Component {
state = {};
render() {
return <div></div>;
}
}
Index.propTypes = {};
export default Index;
Aquí, solo estamos creando el andamio para un componente de clase en React. La parte a la que queremos prestar atención es el nombre del componente Index
y la ruta de ese archivo /pages/index.js
. Debido a que estamos usando Next.js, aquí confiamos en el enrutador Next.js colocando nuestro archivo de componente dentro del marco /pages
directorio.
Los archivos y carpetas aquí se convierten automáticamente en rutas. Porque hemos colocado esto en la raíz de nuestros /pages
carpeta como index.js
, esto se renderizará en la raíz URL de nuestra aplicación, o http://localhost:5000/
.
A continuación, echemos un vistazo rápido a ese usersFixture
archivo que hemos importado arriba:
/lib/usuarios.js
const users = [
{
_id: "f91bbFE72aaDDd8c",
emailAddress: "[email protected]",
name: { first: "Phoebe", last: "Schamberger" },
address: {
streetAddress: "39473 David Mill",
city: "Stammbury",
state: "Michigan",
zipCode: "91802",
},
},
{
_id: "E8c8f6d3fE6761dd",
emailAddress: "[email protected]",
name: { first: "Orin", last: "Balistreri" },
address: {
streetAddress: "27846 Collier Roads",
city: "Schneiderton",
state: "Kansas",
zipCode: "49705-7399",
},
},
{
_id: "Cd9caEcb4fB1D558",
emailAddress: "[email protected]",
name: { first: "Chanelle", last: "Oberbrunner" },
address: {
streetAddress: "638 Fadel Cliffs",
city: "Lake Thorahaven",
state: "West Virginia",
zipCode: "12349-0480",
},
},
{
_id: "BAf1DcEec4b4DBAc",
emailAddress: "[email protected]",
name: { first: "Briana", last: "White" },
address: {
streetAddress: "0540 Brown Meadow",
city: "Port Jerad",
state: "Oklahoma",
zipCode: "14368",
},
},
{
_id: "1c4E8Aa24c37cBFA",
emailAddress: "[email protected]",
name: { first: "Vidal", last: "Stokes" },
address: {
streetAddress: "31028 Marquardt Forest",
city: "North Bethany",
state: "Indiana",
zipCode: "32632",
},
},
];
export default users;
Aquí tenemos una lista estática de "usuarios" (estos se componen de datos falsos). Nuestro objetivo es cargar esta matriz de usuarios en nuestro componente y luego realizar cambios en los objetos de la matriz a través de JavaScript.
/páginas/index.js
import React from "react";
import PropTypes from "prop-types";
import usersFixture from "../lib/users";
class Index extends React.Component {
state = {
users: usersFixture,
};
render() {
const { users } = this.state;
return (
<div>
<header className="page-header">
<h4>Test</h4>
</header>
<div className="responsive-table">
<table className="table align-middle">
<thead>
<tr>
<th>Name</th>
<th>Email Address</th>
<th>Address</th>
<th />
</tr>
</thead>
<tbody>
{users.map(({ _id, name, emailAddress, address }) => {
return (
<tr key={_id}>
<td>
{name?.first} {name?.last}
</td>
<td>{emailAddress}</td>
<td>
{address?.streetAddress} {address?.city}, {address?.state}{" "}
{address?.zipCode}
</td>
<td>
<button
disabled={editingUser}
className="btn btn-primary"
onClick={() => {
this.setState({ editingUser: _id });
}}
>
Edit
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
}
Index.propTypes = {};
export default Index;
De vuelta en nuestro componente, ahora hemos tomado el usersFixture
importamos anteriormente y estamos configurando en el state
de nuestro componente valor como users
. Abajo en el render()
función, hemos devuelto algo de HTML para mostrar nuestra lista de usuarios en una tabla. Aquí, los nombres de clase de CSS que ve se toman del marco Bootstrap CSS. El uso de estas clases aquí no afecta nuestro trabajo real, solo se usan para la presentación.
La parte que nos importa es cuando .map()
sobre el users
valor que colocamos en el estado (nuevamente, esta es nuestra matriz estática de objetos de usuario). Dentro de nuestro render()
método, usamos la desestructuración de JavaScript para "arrancar" users
de this.state
y luego en el marcado HTML devuelto (técnicamente, JSX, que es un lenguaje específico de React que parece HTML) renderizamos un <table></table>
con nuestro users
siendo listado en el cuerpo.
Para el "listado", mantenemos las cosas simples. Aquí, representamos un <tr></tr>
fila de tabla para cada usuario, mostrando su name
, emailAddress
y físico address
. Nuevamente, estos valores son solo datos de prueba para ayudarnos a contextualizar nuestro trabajo modificando objetos en una matriz.
Finalmente, para cada usuario, hemos agregado un <button></button>
que al hacer clic establecerá a ese usuario como el editingUser
en estado. Aquí, pasamos el _id
del usuario (su ID único en nuestra "base de datos") para decir "actualmente estamos editando al usuario con este _id
.
A continuación, conectemos ese proceso de edición.
Edición de un objeto de usuario en una matriz
Con nuestro conjunto de componentes base, ahora, agreguemos la funcionalidad de edición que insinuamos anteriormente:
/páginas/index.js
import React from "react";
import PropTypes from "prop-types";
import usersFixture from "../lib/users";
class Index extends React.Component {
state = {
editingUser: null,
users: usersFixture,
};
renderUserEditor = () => {
const { editingUser, users } = this.state;
const user = users.find(({ _id }) => _id === editingUser);
return (
<div
className="edit-user"
style={{
border: "1px solid #ddd",
padding: "20px",
borderRadius: "3px",
marginTop: "40px",
marginBottom: "40px",
}}
>
<form onSubmit={this.handleUpdateUser}>
<div className="row">
<div className="col-xs-12 col-sm-3">
<div className="mb-3">
<label className="form-label">First Name</label>
<input
type="text"
className="form-control"
defaultValue={user?.name?.first}
name="firstName"
/>
</div>
</div>
<div className="col-xs-12 col-sm-3">
<div className="mb-3">
<label className="form-label">Last Name</label>
<input
type="text"
className="form-control"
defaultValue={user?.name?.last}
name="lastName"
/>
</div>
</div>
<div className="col-xs-12 col-sm-6">
<div className="mb-3">
<label className="form-label">Email Address</label>
<input
type="text"
className="form-control"
defaultValue={user?.emailAddress}
name="emailAddress"
/>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-12 col-sm-5">
<label className="form-label">Street Address</label>
<input
disabled
type="text"
className="form-control"
defaultValue={user?.address?.streetAddress}
name="streetAddress"
/>
</div>
<div className="col-xs-12 col-sm-3">
<label className="form-label">City</label>
<input
disabled
type="text"
className="form-control"
defaultValue={user?.address?.city}
name="city"
/>
</div>
<div className="col-xs-12 col-sm-2">
<label className="form-label">State</label>
<input
disabled
type="text"
className="form-control"
defaultValue={user?.address?.state}
name="state"
/>
</div>
<div className="col-xs-12 col-sm-2">
<label className="form-label">Zip Code</label>
<input
disabled
type="text"
className="form-control"
defaultValue={user?.address?.zipCode}
name="zipCode"
/>
</div>
</div>
<footer className="mt-4">
<button type="submit" className="btn btn-success">
Save
</button>
<button
type="button"
className="btn btn-default"
onClick={() => this.setState({ editingUser: null })}
>
Cancel
</button>
</footer>
</form>
</div>
);
};
render() {
const { editingUser, users } = this.state;
return (
<div>
<header className="page-header">
<h4>Test</h4>
</header>
{editingUser && this.renderUserEditor()}
<div className="responsive-table">
<table className="table align-middle">
<thead>
<tr>
<th>Name</th>
<th>Email Address</th>
<th>Address</th>
<th />
</tr>
</thead>
<tbody>
{users.map(({ _id, name, emailAddress, address }) => { ... })
</tbody>
</table>
</div>
</div>
);
}
}
Index.propTypes = {};
export default Index;
Yendo un poco más allá, ahora hemos agregado editingUser
y establézcalo en null
en nuestro state
predeterminado objeto en la parte superior de nuestra clase de componente. A continuación, en nuestro render()
función, hemos agregado una llamada a this.renderUserEditor()
y han agregado en la función. La idea aquí es que, cuando hacemos clic en el botón "Editar" de un usuario, estableceremos su _id
en estado (tomado de su objeto de usuario en el users
matriz) y luego alternar simultáneamente la representación del editor de usuario y deshabilite todos los botones de edición para los usuarios hasta que se cierre el editor de usuarios (ya sea guardando los cambios o cancelándolos).
Asumiendo que tenemos un editingUser
establecer y renderUserEditor()
ha sido llamado, mirando esa función, la parte que nos importa es la parte superior:
const { editingUser, users } = this.state;
const user = users.find(({ _id }) => _id === editingUser);
Recuerde:estamos lidiando con una estática matriz de usuarios en el estado. En lugar de obtener datos de un servidor, aquí decimos "quitar el editingUser
y users
matriz del estado y luego use un JavaScript .find()
en el users
matriz para encontrar el usuario que tiene un _id
coincidiendo con el editingUser
establecemos en estado". Entonces, cuando hacemos clic en el botón "Editar" de un usuario, se convertirá en el user
que recuperamos aquí.
Una vez recuperado dentro de renderUserEditor()
, renderizamos un formulario que se encargará de permitirnos realizar cambios a ese usuario Aquí, podemos ver que nuestro formulario, nuevamente, usando Bootstrap CSS para limpiar nuestra presentación, enumera cada uno de los campos disponibles en el objeto de usuario como entradas con su defaultValue
establecido en el valor de ese campo en el user
objeto. Para simplificar las cosas, solo permitimos ediciones en el name.first
, name.last
y emailAddress
para el usuario; los demás campos están deshabilitados.
Dos cosas más. Primero, en la parte inferior de renderUserEditor()
, devolvemos un <footer></footer>
con dos botones, un botón "Guardar" y un botón "Cancelar". El botón "Cancelar" aquí es responsable de borrar el editingUser
en el estado cuando se hace clic (recuerde, esto alterna la representación del editor de usuario y el estado deshabilitado de los botones de edición para los usuarios en nuestra lista). El botón más importante, "Guardar", está configurado en type="submit"
, lo que significa que cuando se hace clic en él, se activará el onSubmit
evento para el <form></form>
eso es envolverlo.
Aquí podemos ver que <form></form>
tiene un onSubmit
establecido en una función this.handleUpdateUser
. Conectemos esa función ahora y veamos cómo se modifica nuestra matriz.
/páginas/index.js
import React from "react";
import PropTypes from "prop-types";
import usersFixture from "../lib/users";
class Index extends React.Component {
state = {
editingUser: null,
users: usersFixture,
};
handleUpdateUser = (event) => {
event.preventDefault();
const { editingUser, users } = this.state;
const updatedUsers = [...users];
let userToUpdate = updatedUsers.find(({ _id }) => _id === editingUser);
if (userToUpdate) {
userToUpdate.name = {
first: event.target.firstName.value,
last: event.target.lastName.value,
};
userToUpdate.emailAddress = event.target.emailAddress.value;
}
this.setState({ users: updatedUsers, editingUser: null });
};
renderUserEditor = () => {
const { editingUser, users } = this.state;
const user = users.find(({ _id }) => _id === editingUser);
return (
<div
className="edit-user"
style={{
border: "1px solid #ddd",
padding: "20px",
borderRadius: "3px",
marginTop: "40px",
marginBottom: "40px",
}}
>
<form onSubmit={this.handleUpdateUser}>
...
</form>
</div>
);
};
render() {
const { editingUser, users } = this.state;
return (
<div>
<header className="page-header">
<h4>Test</h4>
</header>
{editingUser && this.renderUserEditor()}
<div className="responsive-table">
<table className="table align-middle">
<thead>
<tr>
<th>Name</th>
<th>Email Address</th>
<th>Address</th>
<th />
</tr>
</thead>
<tbody>
{users.map(({ _id, name, emailAddress, address }) => { ... })}
</tbody>
</table>
</div>
</div>
);
}
}
Index.propTypes = {};
export default Index;
Dentro de nuestro nuevo handleUpdateUser()
función, primero, tomamos el envío event
como argumento e inmediatamente llamar a su .preventDefault()
método. Esto es importante ya que no queremos que nuestro evento de envío active una actualización del navegador; esto lo detiene.
A continuación, vemos algo similar a lo que vimos en renderUserEditor()
. Esta es la parte que nos importa en este tutorial. Aquí, estamos arrancando el editingUser
y users
matriz de this.state
otra vez. Recuerde, nuestro objetivo es editar un objeto que existe en una matriz. Para hacer eso, necesitamos saber dos cosas:
- ¿En qué matriz estamos buscando?
- ¿Cómo encontramos el objeto para actualizar en esa matriz?
Aquí, usaremos el _id
configuramos en editingUser
cuando hicimos clic en el botón "Editar" junto a uno de nuestros usuarios. Ahora, nuevamente, debemos enfatizar que nuestro objetivo es editar un objeto tal como existe en una matriz . En este ejemplo, la matriz es nuestro users
matriz.
Primero, antes de "encontrar" a nuestro usuario, creamos una copia del users
array on state (está más allá del alcance de este tutorial, pero la regla de oro en React es que no desea mutar los valores de estado directamente) con [...users]
. Aquí, en una línea decimos "crear una nueva matriz []
y luego usa el ...
operador de propagación para "desempaquetar" o copiar el contenido de users
en esa nueva matriz". Esto es la matriz que modificaremos.
A continuación, nuevamente usando JavaScript .find()
en nuestro nuevo updatedUsers
matriz, ejecutamos la misma prueba que usamos anteriormente para decir "encuéntrenos un usuario con un _id
que coincide con editingUser
." Suponiendo que hagamos encuentra ese usuario, comenzamos a hacerle cambios. Aquí, estamos configurando el name
objeto y el emailAddress
campo en el userToUpdate
.
Aunque puede que no lo parezca, porque usamos un JavaScript .find()
aquí, en realidad estamos modificando el userToUpdate
coincidente objeto tal como existe en el updatedUsers
matriz en la memoria. Lo que esto significa es que aunque nuestro código está haciendo cambios en userToUpdate
, en última instancia, está realizando cambios en updatedUsers
.
Una vez que estos cambios estén completos (aquí, solo estamos configurando los valores mencionados anteriormente en las entradas correspondientes en nuestro formulario usando event.target.<fieldName>.value
donde <fieldName>
es el name=""
atributo en la entrada), sobrescribimos nuestro users
valor en estado con this.setState()
, pasando nuestro updatedUsers
matriz.
¿El final resulto? Veremos nuestro users
actualización de la lista en nuestra tabla, demostrando con éxito que actualizamos un objeto dentro de una matriz.
Terminando
En este tutorial, aprendimos cómo modificar un objeto en una matriz de JavaScript. Para contextualizar nuestro trabajo, construimos un componente React que modificaba una lista de usuarios en una matriz en estado y luego la volvía a establecer en estado, representando la lista actualizada en una tabla HTML.