I'm working with a React and Next.js application where I have a parent component ProfileDashboard and a child component EditProfileModal. The child is used for editing a user's profile and seems to successfully update the user data on the server side. However, the updated data does not reflect in the parent component's state until I manually refresh the page.
The child component calls an updateUser function which sends a PUT request to my Next.js API route. The backend updates the user in the database and is supposed to return the updated user object. While the database update is successful (verified upon refreshing the page), the state in my React component does not reflect this update immediately.
Here are relevant parts of my components:
Parent Component (ProfileDashboard.tsx):
export default function ProfileDashboard({
seller, // The seller to display on the page
userStatus, // The current user's status
}: {
seller: User;
userStatus: UserStatus;
}) {
const [updatedSeller, setupdatedSeller] = useState(seller);
// This function will be called by the child component after updating the user
const handleUserUpdate = (updatedUserData: User) => {
console.log('Parenent component before update:', updatedSeller);
setupdatedSeller(updatedUserData);
console.log('Parent component after update:', updatedUserData);
};
useEffect(() => {
console.log('Parent user after update useEffect:', seller);
}, [seller]);
return (
<div className={"flex flex-row justify-center items-center gap-8 mt-8"}>
{updatedSeller.profilePic !== "" ? (
<Image
src={updatedSeller.profilePic}
alt=""
width={150}
height={150}
className="rounded-full max-w-[150px] max-h-[150px]"
/>
) : (
<div className="rounded-full w-[150px] h-[150px] bg-yellow-500"></div>
)}
<div className="flex flex-col items-center gap-2 py-2">
<span className="font-semibold self-start">{updatedSeller.username}</span>
<SellerStats seller={updatedSeller} />
{updatedSeller.bio !== "" && (
<ScrollArea className="h-[150px] w-[500px] rounded-md border p-4">
{updatedSeller.bio}
</ScrollArea>
)}
{userStatus === UserStatus.Seller && (
<EditProfileModal onUserUpdate={handleUserUpdate}>
<Button variant="outline" className="text-xs">
<Pencil className="inline-block w-[12px] h-[12px] mr-2" />
Edit profile
</Button>
</EditProfileModal>
)}
</div>
</div>
);
}
Child Component (EditProfileModal.tsx):
export default function EditProfileModal({
children,
onUserUpdate,
}: {
children: React.ReactNode;
onUserUpdate: (updatedUserData: User) => void;
}) {
const [user, setUser] = useState<User | null>(null); // The current user
const [loading, setLoading] = useState<boolean>(false); // Whether the submission is loading
const [success, setSuccess] = useState<boolean>(false); // Whether the listing was successfully created
const [password, setPassword] = useState(""); // The password of the user
const [bio, setBio] = useState(""); // The bio of the user
const [profilePic, setProfilePic] = useState(""); // The profile picture of the user
const [shippingAddress, setShippingAddress] = useState(""); // The shipping address of the user
/*
On page load, get the current user.
*/
useEffect(() => {
const fetchUserData = async () => {
const session = await getSession();
if (session?.user?.name) {
// Assume getUser takes an identifier (e.g., email or ID) and returns user data
const userData = await getUser(session.user.name);
if (userData) {
setUser(userData);
setBio(userData.bio || "");
setProfilePic(userData.profilePic || "");
setShippingAddress(userData.shippingAddress || "");
setPassword(""); // Password might not be fetched or should not be directly editable
}
}
};
fetchUserData();
}, []);
const onSubmit = async () => {
setLoading(true);
try {
const userDataToUpdate = {
...user,
bio,
profilePic,
shippingAddress,
password,
};
if (userDataToUpdate.username === undefined) {
console.error("Username is undefined. Cannot update user.");
return;
}
const updatedUser = await updateUser(userDataToUpdate as Omit<User, "timestamp" | "balance">);
setUser(updatedUser); // Update local user state
console.log("child component after 'setUser(updatedUser)' "+ user);
// Call the callback function passed from the parent to update the parent's state
onUserUpdate(updatedUser);
setSuccess(true);
setTimeout(() => setSuccess(false), 2000); // Reset success state after a delay
} catch (error) {
console.error("Failed to update user:", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
console.log("Child useeffect "+user);
}, [user]);
return (
<Dialog>
<DialogTrigger>{children}</DialogTrigger>
// ... JSX with form inputs and submit button
}
Utility Function (utils.ts):
export async function updateUser(user: Omit<User, "timestamp" | "balance">) {
const response = await fetch(`http://localhost:3000/api/users/${user.username}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user),
});
return await response.json();
}
When I attempt to update the user's bio from "original" to "updated" and submit the form, the console logs in the handleUserUpdate show the following:Console log outputs
"Parent component before update" logs the updatedSeller state, which is correct before the update. "Parent component after update" logs the updatedUserData parameter, which shows the database operation result instead of the updated user object. This discrepancy seems to be why the state doesn't update as expected. However, when I close the modal and refresh the parent page, the bio shows "updated," confirming the backend update was successful.
What could be causing the state update to only reflect after a page refresh, and how can I make the state update immediately with the new data?
Thank you for any insights or suggestions!