I’m facing an issue in my React component where the input fields are re-rendering on every keystroke. I’ve tried optimizing it, but the problem persists. Here’s a breakdown of what I’ve tried and the code I’m working with:
I have several input forms in a component where the user can edit their names & addresses. The input fields update on every keystroke, which causes unnecessary re-renders of the entire component. I’m using React’s useState and useCallback to manage state and avoid unnecessary re-renders, but it doesn’t seem to work as expected.
import React, { useState, useEffect, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import './Account.css'
const Account = ({ user, setUser }) => {
const [orders, setOrders] = useState([])
const [addresses, setAddresses] = useState([])
const [defaultAddressId, setDefaultAddressId] = useState(null)
const [showAddModal, setShowAddModal] = useState(false)
const [showEditModal, setShowEditModal] = useState(false)
const [currentAddress, setCurrentAddress] = useState(null)
const [selectedMainImages, setSelectedMainImages] = useState({})
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [addressToDelete, setAddressToDelete] = useState(null)
const [showEditNameModal, setShowEditNameModal] = useState(false)
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [address, setAddress] = useState({
street: '',
city: '',
state: '',
zip: '',
country: '',
phone: '',
isDefault: false,
})
const navigate = useNavigate()
const fetchAddresses = useCallback(async () => {
const accessToken = localStorage.getItem('accessToken')
const response = await fetch(
'http://localhost:8000/api/shopify/get-addresses',
{
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
)
if (response.ok) {
const data = await response.json()
setAddresses(data.addresses)
if (data.defaultAddressId) {
setDefaultAddressId(data.defaultAddressId)
} else if (data.addresses.length > 0) {
setDefaultAddressId(data.addresses[0].id)
}
} else {
console.error('Failed to fetch addresses')
}
}, [])
const fetchOrders = useCallback(async () => {
const accessToken = localStorage.getItem('accessToken')
const response = await fetch(
'http://localhost:8000/api/shopify/get-orders',
{
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
)
if (response.ok) {
const data = await response.json()
setOrders(data.orders)
} else {
console.error('Failed to fetch orders')
}
}, [])
const setDefaultAddress = async (addressId) => {
const accessToken = localStorage.getItem('accessToken')
const response = await fetch(
'http://localhost:8000/api/shopify/set-default-address',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ addressId }),
}
)
if (response.ok) {
setDefaultAddressId(addressId)
alert('Default address updated successfully!')
} else {
const errorData = await response.json()
alert(
`Failed to update default address: ${
errorData.error || 'Unknown error'
}`
)
}
}
useEffect(() => {
if (user) {
fetchOrders()
fetchAddresses()
}
}, [user, fetchAddresses, fetchOrders])
const deleteAddress = async () => {
if (!addressToDelete) return
const accessToken = localStorage.getItem('accessToken')
const isDeletingDefault = addressToDelete.id === defaultAddressId
const response = await fetch(
'http://localhost:8000/api/shopify/delete-address',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ addressId: addressToDelete.id }),
}
)
if (response.ok) {
await fetchAddresses()
if (isDeletingDefault) {
const remainingAddresses = addresses.filter(
(addr) => addr.id !== addressToDelete.id
)
if (remainingAddresses.length > 0) {
await setDefaultAddress(remainingAddresses[0].id)
}
}
setShowDeleteModal(false)
setAddressToDelete(null)
alert('Address deleted successfully!')
} else {
const errorData = await response.json()
alert(`Failed to delete address: ${errorData.error || 'Unknown error'}`)
}
}
const handleDeleteClick = (addr) => {
if (addresses.length === 1) {
alert('You must have at least one address.')
return
}
setAddressToDelete(addr)
setShowDeleteModal(true)
}
const handleAddressChange = (e) => {
if (showEditModal) {
setCurrentAddress({ ...currentAddress, [e.target.name]: e.target.value })
} else {
setAddress({ ...address, [e.target.name]: e.target.value })
}
}
const handleThumbnailClick = (orderId, lineItemId, imageSrc) => {
setSelectedMainImages((prev) => ({
...prev,
[`${orderId}-${lineItemId}`]: imageSrc,
}))
}
const addAddress = async () => {
const accessToken = localStorage.getItem('accessToken')
const response = await fetch(
'http://localhost:8000/api/shopify/add-address',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify(address),
}
)
if (response.ok) {
const result = await response.json()
if (result && result.addressId) {
if (address.isDefault || addresses.length === 0) {
setDefaultAddressId(result.addressId)
}
}
await fetchAddresses()
setAddress({
street: '',
city: '',
state: '',
zip: '',
country: '',
phone: '',
isDefault: false,
})
setShowAddModal(false)
alert('Address added!')
} else {
const errorData = await response.json()
alert(`Failed to add address: ${errorData.message || 'Unknown error'}`)
}
}
const editAddress = async () => {
const accessToken = localStorage.getItem('accessToken')
const response = await fetch(
'http://localhost:8000/api/shopify/edit-address',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
addressId: currentAddress.id,
street: currentAddress.street,
city: currentAddress.city,
state: currentAddress.state,
zip: currentAddress.zip,
country: currentAddress.country,
phone: currentAddress.phone,
}),
}
)
if (response.ok) {
await fetchAddresses()
if (currentAddress.isDefault) {
await setDefaultAddress(currentAddress.id)
}
setShowEditModal(false)
alert('Address updated successfully!')
} else {
const errorData = await response.json()
alert(`Failed to update address: ${errorData.error || 'Unknown error'}`)
}
}
const handleEditClick = (addr) => {
setCurrentAddress({
...addr,
isDefault: addr.id === defaultAddressId,
})
setShowEditModal(true)
}
const updateName = async () => {
const accessToken = localStorage.getItem('accessToken')
if (!firstName && !lastName) {
alert('Please enter at least one name field.')
return
}
const response = await fetch(
'http://localhost:8000/api/shopify/edit-name',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ firstName, lastName }),
}
)
if (response.ok) {
const data = await response.json()
setUser((prev) => ({
...prev,
customer: {
...prev.customer,
firstName: data.customer.firstName,
lastName: data.customer.lastName,
},
}))
alert('Name updated successfully!')
setShowEditNameModal(false)
} else {
alert('Failed to update name.')
}
}
const handleEditNameClick = () => {
setFirstName(user?.customer.firstName || '')
setLastName(user?.customer.lastName || '')
setShowEditNameModal(true)
}
const logout = () => {
localStorage.removeItem('accessToken')
setUser(null)
navigate('/login')
}
const Modal = ({ show, onClose, title, children }) => {
if (!show) return null
return (
<div className="modal-backdrop">
<div className="modal-content">
<div className="modal-header">
<h2>{title}</h2>
<button onClick={onClose} className="close-button">
×
</button>
</div>
<div className="modal-body">{children}</div>
</div>
</div>
)
}
if (!user) return <p>Loading...</p>
return (
<div className="account-container">
<div className="account-header">
<h1>Welcome, {user?.customer.firstName}!</h1>
<button onClick={logout} className="logout-button">
Logout
</button>
</div>
<h1>Account Details</h1>
<div className="account-details">
<div className="name-container">
<p>
Name: {user?.customer.firstName} {user?.customer.lastName}
</p>
<p>Email: {user?.customer.email}</p>
</div>
<div className="edit-name-container">
<button onClick={handleEditNameClick} className="edit-name-btn">
Edit
</button>
</div>
</div>
<Modal
show={showEditNameModal}
onClose={() => setShowEditNameModal(false)}
title="Edit Name"
>
<div className="name-edit-form">
<div className="name-inputs">
<label>
First Name:{' '}
<input
type="text"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
</label>
<label>
Last Name:{' '}
<input
type="text"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</label>
</div>
<div className="modal-actions">
<button onClick={updateName} className="confirm-btn">
Save Changes
</button>
<button
onClick={() => setShowEditNameModal(false)}
className="cancel-btn"
>
Cancel
</button>
</div>
</div>
</Modal>
<h2>Your Addresses</h2>
<div className="address-list">
{addresses.length === 0 ? (
<p>No addresses found.</p>
) : (
addresses.map((addr) => (
<div className="address-item" key={addr.id}>
<div className="address-details">
<p>
{addr.street}, {addr.city}, {addr.state}, {addr.zip},{' '}
{addr.country}
{addr.phone && ` • ${addr.phone}`}
</p>
{addr.id === defaultAddressId ? (
<span className="default-label">Default</span>
) : (
<span className="not-default-label">Not default</span>
)}
</div>
<div className="address-actions">
<button
className="edit-btn"
onClick={() => handleEditClick(addr)}
>
Edit
</button>
<button
className="delete-btn"
onClick={() => handleDeleteClick(addr)}
>
Delete
</button>
</div>
</div>
))
)}
</div>
<Modal
className="delete-address-modal"
show={showDeleteModal}
onClose={() => setShowDeleteModal(false)}
title="Confirm Delete"
>
<div className="delete-confirmation">
<p>Are you sure you want to delete this address?</p>
{addressToDelete && (
<div className="address-to-delete">
<p>
{addressToDelete.street}, {addressToDelete.city},{' '}
{addressToDelete.state}, {addressToDelete.zip},{' '}
{addressToDelete.country}
</p>
</div>
)}
<div className="modal-actions">
<button onClick={deleteAddress} className="confirm-delete-btn">
Yes, Delete
</button>
<button
onClick={() => setShowDeleteModal(false)}
className="cancel-delete-btn"
>
Cancel
</button>
</div>
</div>
</Modal>
<div className="address-actions">
<button
className="add-address-btn"
onClick={() => setShowAddModal(true)}
>
+ Add New Address
</button>
</div>
<Modal
show={showAddModal}
onClose={() => setShowAddModal(false)}
title="Add New Address"
>
<div className="address-form">
<div className="address-input">
Street:{' '}
<input
name="street"
placeholder="Street"
value={address.street}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
City:{' '}
<input
name="city"
placeholder="City"
value={address.city}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
State:{' '}
<input
name="state"
placeholder="State"
value={address.state}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
ZIP Code:{' '}
<input
name="zip"
placeholder="ZIP Code"
value={address.zip}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
Country:{' '}
<input
name="country"
placeholder="Country"
value={address.country}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
Phone Number:{' '}
<input
name="phone"
placeholder="Phone Number"
value={address.phone}
onChange={handleAddressChange}
/>
</div>
<label className="checkbox-label">
Set as Default
<input
type="checkbox"
name="isDefault"
checked={address.isDefault}
onChange={(e) =>
setAddress({ ...address, isDefault: e.target.checked })
}
/>
</label>
<div className="modal-actions">
<button onClick={addAddress} className="confirm-btn">
Add Address
</button>
<button
onClick={() => setShowAddModal(false)}
className="cancel-btn"
>
Cancel
</button>
</div>
</div>
</Modal>
<Modal
className="edit-address-modal"
show={showEditModal}
onClose={() => setShowEditModal(false)}
title="Edit Address"
>
{currentAddress && (
<div className="address-form">
<div className="address-input">
Street:{' '}
<input
name="street"
placeholder="Street"
value={currentAddress.street}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
City:{' '}
<input
name="city"
placeholder="City"
value={currentAddress.city}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
State:{' '}
<input
name="state"
placeholder="State"
value={currentAddress.state}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
ZIP Code:{' '}
<input
name="zip"
placeholder="ZIP Code"
value={currentAddress.zip}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
Country:{' '}
<input
name="country"
placeholder="Country"
value={currentAddress.country}
onChange={handleAddressChange}
required
/>
</div>
<div className="address-input">
Phone Number:{' '}
<input
name="phone"
placeholder="Phone Number"
value={currentAddress.phone}
onChange={handleAddressChange}
/>
</div>
<label className="checkbox-label">
Set as Default
<input
type="checkbox"
name="isDefault"
checked={currentAddress.isDefault}
onChange={(e) =>
setCurrentAddress({
...currentAddress,
isDefault: e.target.checked,
})
}
/>
</label>
<div className="modal-actions">
<button onClick={editAddress} className="confirm-btn">
Save Changes
</button>
<button
onClick={() => setShowEditModal(false)}
className="cancel-btn"
>
Cancel
</button>
</div>
</div>
)}
</Modal>
<h2>Your Orders</h2>
{orders.length === 0 ? (
<p>No orders found.</p>
) : (
<div className="order-items">
{orders.map((order) => (
<div key={order.id} className="order-item">
<div className="order-item-image">
{order.lineItems.length > 0 && (
<>
<img
src={
selectedMainImages[
`${order.id}-${order.lineItems[0].id}`
] ||
order.lineItems[0].variant?.images[0] ||
'placeholder-image-url'
}
alt={order.lineItems[0].title}
className="order-main-image"
/>
<div className="thumbnail-container">
{order.lineItems[0].variant?.images.map(
(imgSrc, index) => (
<img
key={index}
src={imgSrc}
alt={`Thumbnail ${index + 1}`}
className={`thumbnail ${
imgSrc ===
selectedMainImages[
`${order.id}-${order.lineItems[0].id}`
]
? 'active'
: ''
}`}
onClick={() =>
handleThumbnailClick(
order.id,
order.lineItems[0].id,
imgSrc
)
}
/>
)
)}
</div>
</>
)}
</div>
<div className="order-item-info">
<div className="order-item-details">
<h3>Order: {order.name}</h3>
<p className="price">
Total: ${order.totalPrice} {order.currency}
</p>
<p>
Created At: {new Date(order.createdAt).toLocaleDateString()}
</p>
<p>Status: {order.fulfillmentStatus}</p>
<div className="order-line-items">
{order.lineItems.map((item, index) => (
<p key={index}>
{item.title} x {item.quantity}
{item.variant?.title ? ` - ${item.variant.title}` : ''}
</p>
))}
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
)
}
export default Account