Вопрос или проблема
Изменения данных не обновляют пользовательский интерфейс мгновенно из-за того, что страница не перерисовывается.
Создание приложения с использованием NextJS 14 App Router. Часть приложения — это поток, похожий на Facebook, где пользователи могут оставлять сообщения. Проблема, с которой я столкнулся, заключается в том, что интерфейс не перерисовывается/не обновляется после мутаций данных на стороне клиента, таких как нажатие кнопок «нравится» или добавление комментариев. Всё работает нормально в основном потоке, где можно увидеть все сообщения всех пользователей. Однако при выборе записей по пользователю, переходе на страницу postsbyuser функции, такие как нажатие кнопки «нравится» или добавление комментария, работают только один раз, после этого только при обновлении страницы интерфейс не обновляется/не перезагружается мгновенно. Данные изменяются в базе данных, как и ожидалось, но не отображаются в интерфейсе.
Я пытался обновить роутер, но это сработало только на маршруте /api/posts/, revalidatePath тоже не решило проблему. Оптимистичные обновления могут быть решением, но я хочу этого избежать.
Полагаю, состояние не переинициализируется и не пересчитывается должным образом после отправки мутаций данных на сервер.
Структура папки API
Маршрут публикаций по пользователю
export const GET = async (request, {params}) => {
try {
await connectDB();
const {postId} = params;
if (!postId) {
return null;
}
const posts = await Post.aggregate([
{ $match: { user: ObjectId.createFromHexString(postId) } },
{
$lookup: {
from: "comments",
localField: "_id",
foreignField: "postId",
as: "comments",
},
},
{
$lookup: {
from: "likes",
localField: "_id",
foreignField: "postId",
as: "likes",
},
},
{
$sort: { createdAt: -1 },
},
]);
return new Response(JSON.stringify({posts}), { status: 200 });
} catch (error) {
console.log(error);
}
};
Маршрут для «нравится» для публикации
export const POST = async (request, { params }) => {
await connectDB();
const { postId } = params;
const session = await getSessionUser()
if (!session) {
return new Response(JSON.stringify({message: "Неавторизован"}), { status: 401 });
}
const userId = session.user.id;
try {
const liked = await Like.findOne({ postId, userId });
if(liked){
const deletedLike = await Like.findOneAndDelete({ postId, userId });
if(deletedLike){
await Post.findByIdAndUpdate(postId, { $inc: { likesCount: -1 } });
}
}
if(!liked){
const like = await Like.create({ userId, postId });
await Post.findByIdAndUpdate(postId, { $inc: { likesCount: 1 } });
}
return new Response(JSON.stringify(liked), { status: 201 });
} catch (error) {
return new Response({message:error.message}, { status: 500 });
}
};
Маршрут для «нравится» комментария
export const POST = async (request, { params }) => {
await connectDB();
const { commentId } = params;
const session = await getSessionUser();
const postId = commentId // Вам нужен postId, потому что в схемах «нравится» нет commentId
if (!session) {
return new Response(JSON.stringify({ message: "Неавторизован" }), {
status: 401,
});
}
const userId = session.user.id;
try {
const liked = await Like.findOne({ postId, userId });
if (liked) {
const deletedLike = await Like.findOneAndDelete({ postId, userId });
if (deletedLike) {
await Comment.findByIdAndUpdate({_id:postId}, {$inc: { likesCount: -1 }});
}
}
if (!liked) {
const like = await Like.create({ userId, postId });
if(like){
await Comment.findByIdAndUpdate(postId, { $inc: { likesCount: 1 }});
}
}
return new Response(JSON.stringify(liked), { status: 201 });
} catch (error) {
return new Response({ message: error.message }, { status: 500 });
}
};
Кнопка «нравится» для комментария
const CommentLikeButton = ({com, initialLikesCount }) => {
const router = useRouter();
const toggleLike = async (commentId) => {
console.log("Id:", commentId);
try {
const res = await fetch(`/api/posts/comments/${commentId}/like`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ commentId }),
});
if (!res.ok) {
alert("Сначала войдите в систему!");
}
} catch (error) {
console.error("Ошибка при переключении «нравится»: ", error);
}
router.refresh();
};
}
Ответ или решение
Конечно, давайте разберемся с вашей проблемой, связанной с обновлением пользовательского интерфейса после изменения данных. Это распространенная проблема в разработке с использованием React и Next.js, особенно когда дело доходит до асинхронных операций, таких как мутация данных. Мы обсудим несколько решений, которые могут помочь вам добиться желаемого поведения.
Проблема
Как вы описали, ваше приложение на Next.js не обновляет UI после изменения данных (например, при нажатии на кнопку "лайк" или добавлении комментария). Причина заключается в том, что у вас, возможно, не происходит повторная инициализация состояния или валидация данных на клиенте после мутации.
Решения
-
Обновление состояния локально:
Чтобы избежать перезагрузки страницы, вы можете обновить локальное состояние компонента после успешного выполнения мутации. Вот как это может выглядеть в вашем компоненте.const CommentLikeButton = ({ com, initialLikesCount }) => { const [likesCount, setLikesCount] = useState(initialLikesCount); const router = useRouter(); const toggleLike = async (commentId) => { try { const res = await fetch(`/api/posts/comments/${commentId}/like`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ commentId }), }); if (res.ok) { const liked = await res.json(); // Логика обновления состояния локально if (liked) { setLikesCount(prevCount => prevCount + 1); } else { setLikesCount(prevCount => prevCount - 1); } } else { alert("Login first!"); } } catch (error) { console.error("Error toggling like:", error); } }; return ( <button onClick={() => toggleLike(com._id)}> Лайков: {likesCount} </button> ); };
Здесь
setLikesCount
будет обновлять количество лайков сразу после успешной мутации, что вызовет повторный рендер компонента. -
Использование библиотеки для управления состоянием (Redux, Zustand):
Если ваше приложение становится сложным, можно рассмотреть использование библиотеки управления состоянием, такой как Redux или Zustand. Это поможет вам более эффективно управлять глобальным состоянием ваших данных и обновлениями UI. -
Сторонние решения для оптимистичных обновлений:
Хотя вы хотите избежать оптимистичных обновлений, они на самом деле могут очень упростить управление состоянием. Вы можете временно обновить локальное состояние до завершения мутации и затем компенсировать это, если произошла ошибка. -
Проверка данных после мутации:
После выполнения мутации вы можете снова запросить актуальные данные в вашей компоненте для обновления UI. Это можно сделать с помощьюrouter.refresh()
, как вы уже пробовали, но в сочетании с предыдущими советами вы можете добиться лучшего результата. - Связь с сервером:
Убедитесь, что API обрабатывает запросы как ожидается и возвращает правильные данные. Запросы, которые отправляются при клике на кнопки "лайк" и "комментировать", должны возвращать актуальные данные после их изменения на сервере.
Заключение
При разработке приложения с динамическим контентом важно правильно управлять состоянием компонентов. Внедрение локального состояния обновления после мутации, использование библиотек управления состоянием и обеспечение правильного взаимодействия с сервером помогут вам добиться желаемого поведения. Попробуйте применить вышеперечисленные рекомендации, и это должно решить ваши проблемы с обновлением UI.
Если у вас остались вопросы или требуется дополнительная помощь, не стесняйтесь спрашивать!