@HitGirl

Как правильно рендерить рекурсивный массив используя useMemo?

Здравствуйте!
Пытаюсь выводить вложенные комментарии. Подскажите, пожалуйста, как сделать так чтобы при добавлении нового комментария перерисовывалось минимум старых комментариев. В коде ниже, если убрать useMemo всё работает, но это перерисовывает все комментарии. Если оставить useMemo, то добавление комментария 3-го и выше уровня вложенности не работает, так как приложение не понимает, что состояние изменилось. Как это можно исправить?
import {FC, useCallback, useMemo, useState} from "react";


const AddReply:FC<{onReply:(text:string)=>void}> =({onReply})=>{

    const [text,setText] = useState('')

    const submitHandler = (e:any)=>{
        e.preventDefault();
        onReply(text)
    }

    return <form onSubmit={submitHandler}>
        <input type={"text"} value={text} onChange={(e)=>setText(e.target.value)}/>
        <button type="submit">Отправить</button>
    </form>
}

const Reply:FC<{reply:IReply, level:number, onReplyHandler:(text:string,reply?:IReply)=>void}> = ({reply,onReplyHandler,level})=>{

    const onReply= useCallback((text:string)=>{
        onReplyHandler(text,reply)
    },[])

    return useMemo(()=>(
        <div style={{marginLeft:level*10+"px"}}>
            <p>{reply.text}</p>
            <div style={{marginLeft:(1+level)*10+"px"}}>
                <AddReply onReply={onReply}/>
            </div>
            {
                reply.replies.map(el=><Reply key={el.id} reply={el} level={level+1} onReplyHandler={onReplyHandler}/>)
            }
        </div>
    ),[reply,reply.replies])
}


type IReply = {
    id:string;
    text:string;
    replies:IReply[]
}


const TestPage:FC = ()=>{
    const [replies,setReplies] = useState<IReply[]>([])


    const addReply = useCallback((text:string,reply?:IReply)=>{
        let newReply = {id:new Date().toISOString(),text:text,replies:[]}
        if (reply){
            reply.replies = [newReply,...reply.replies]
            setReplies(rootReplies=>[...rootReplies])
        }
        else{
            setReplies(rootReplies=>[newReply,...rootReplies])
        }
        console.log(replies)
    },[replies])



    return (
        <>
            <AddReply onReply={addReply}/>
            {replies.map(el=><Reply key={el.id} reply={el} level={0} onReplyHandler={addReply}/>)}
        </>
    )
}

export default TestPage;
  • Вопрос задан
  • 122 просмотра
Решения вопроса 2
Aetae
@Aetae Куратор тега TypeScript
Тлен
useMemo - это не магическое заклинание. Оно работает сверяя изменения переданных props не более того.
В твоём случае в useMemomemo надо обернуть сам компонент Replay, а не голый jsx.
Ответ написан
Alexandroppolus
@Alexandroppolus
кодир
reply.replies = [newReply,...reply.replies]
setReplies(rootReplies=>[...rootReplies])

ты неправильно обрабатываешь стейт. По факту здесь меняется содержимое некоего узла, а потом возвращается поверхностная копия всего дерева.

есть 3 варианта:
1) Продолжать использовать дерево в useState. Но менять его надо иммутабельно. То есть если у какого-то узла что-то поменялось, то надо заменить узел и все его паренты, а не только корневой массив.
2) Нормализовать данные в плоскую структуру. Каждому реплаю присваивается уникальный id, все реплаи складываются в объект, где ключем будет id. В массиве replies у каждого объекта будут лежать не сами объекты, а только id, и потом надо будет их соединять во время рендера. Этот вариант канонично используется для redux, но как зайдет для useState, навскидку не совсем понятно.
3) Использовать MobX с этими его observable.deep (или просто observable). Как всегда, самый простой и удобный вариант.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы