Как сохранить состояние дочерних компонентов, когда они фильтруются через родительский компонент? - PullRequest
2 голосов
/ 08 апреля 2020

Я создаю небольшое приложение, использующее приложение для создания реагирования, чтобы улучшить свои знания о реакции, но теперь застрял с управлением состоянием.

Приложения отображают данные JSON в родительском компоненте и печатают 6 «карт изображений» как дочерние компоненты с массивом «тегов» для его описания и другими данными (url, заголовки и т. д. c ..), передаваемыми как реквизиты.

У каждой карты есть вход, в который можно добавить дополнительные теги в существующий список.

В родительском компоненте есть вход, который можно использовать для фильтрации карт по тегам. (фильтрует только теги по умолчанию, а не новые, добавленные на карту).

Я пытаюсь добиться сохранения состояния каждой карты при ее фильтрации. В настоящее время, что происходит, если я добавляю новые теги на карточки и фильтрую их с использованием нескольких тегов, только начальные отфильтрованные карточки содержат новые теги, остальные перерисовываются с их тегами по умолчанию. Может кто-нибудь сказать мне, где я иду не так, спасибо.

Мой проект также может быть клонирован, если он облегчит задачу https://github.com/sai-re/assets_tag

данные. json пример

{
    "assets": [
        {
            "url": "https://images.unsplash.com/photo-1583450119183-66febdb2f409?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=200&ixlib=rb-1.2.1&q=80&w=200",
            "title": "Car",
            "tags": [
                { "id": "USA", "text": "USA" },
                { "id": "Car", "text": "Car" }
            ],
            "suggestions": [
                { "id": "Colour", "text": "Colour" },
                { "id": "Motor", "text": "Motor" },
                { "id": "Engineering", "text": "Engineering" }
            ]
        },
        {
            "url": "https://images.unsplash.com/photo-1582996269871-dad1e4adbbc7?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=200&ixlib=rb-1.2.1&q=80&w=200",
            "title": "Plate",
            "tags": [
                { "id": "Art", "text": "Art" },
                { "id": "Wood", "text": "Wood" },
                { "id": "Spoon", "text": "Spoon" }
            ],
            "suggestions": [
                { "id": "Cutlery", "text": "Cutlery" },
                { "id": "Serenity", "text": "Serenity" }
            ]
        }
    ]
}

Родительский компонент

import React, {useState} from 'react';
import Item from './Item'
import data from '../../data.json';

import './Assets.scss'

function Assets() {
    const [state, updateMethod] = useState({tag: "", tags: []});

    const printList = () => {
        //if tag in filter has been added        
        if (state.tags.length > 0) {
            return data.assets.map(elem => {
                //extract ids from obj into array
                const dataArr = elem.tags.map(item => item.id);
                const stateArr = state.tags.map(item => item.id);

                //check if tag is found in asset
                const doesTagExist = stateArr.some(item => dataArr.includes(item));
                //if found, return asset 
                if (doesTagExist) return <Item key={elem.title} data={elem} />;
            })
        } else {
            return data.assets.map(elem => (<Item key={elem.title} data={elem} /> ));
        }
    };

    const handleClick = () => {
        const newTag = {id: state.tag, text: state.tag};
        const copy = [...state.tags, newTag];

        if (state.tag !== "") updateMethod({tag: "", tags: copy});
    }

    const handleChange = e => updateMethod({tag: e.target.value, tags: state.tags});

    const handleDelete = i => {
        const copy = [...state.tags];
        let removed = copy.filter((elem, indx) => indx !== i);

        updateMethod({tag: state.tag, tags: removed});
    }

    return (
        <div className="assets">
            <div className="asset__filter">
                <h3>Add tags to filter</h3>
                <ul className="asset__tag-list">
                    {state.tags.map((elem, i) => (
                        <li className="asset__tag" key={`${elem.id}_${i}`} >
                            {elem.text}

                            <button className="asset__tag-del" onClick={() => handleDelete(i)}>x</button>
                        </li>
                    ))}
                </ul>

                <input 
                    type="text" 
                    value={state.tag}
                    onChange={handleChange} 
                    placeholder="Enter new tag" 
                    className="asset__tag-input"
                />

                <button className="asset__btn" onClick={handleClick}>Add</button>
            </div>

            <div className="item__list-holder">
                {printList()}
            </div>
        </div>
    );  
}

export default Assets;

Дочерний компонент

import React, {useState, useEffect} from 'react';

function Item(props) {
    const [state, updateMethod] = useState({tag: "", tags: []});

    const handleClick = () => {
        //create new tag from state
        const newTag = {id: state.tag, text: state.tag};
        //create copy of state and add new tag
        const copy = [...state.tags, newTag];
        //if state is not empty update state with new tags
        if (state.tag !== "") updateMethod({tag: "", tags: copy});
    }

    const handleChange = e => updateMethod({tag: e.target.value, tags: state.tags});

    const handleDelete = i => {
        //copy state
        const copy = [...state.tags];
        //filter out tag to be deleted
        let removed = copy.filter((elem, indx) => indx !== i);
        //add updated tags to state
        updateMethod({tag: state.tag, tags: removed});
    }

    useEffect(() => {
        console.log("item rendered");
        //when first rendered, add default tags from json to state
        updateMethod({tag: "", tags: props.data.tags});
    }, [props.data.tags]);

    const assets = props.data;

    return (
        <div className="item">
            <img src={assets.url} alt="assets.title"/>
            <h1 className="item__title">{assets.title}</h1>

            <div className="item__tag-holder">
                <ul className="item__tag-list">
                    {state.tags.map((elem, i) => (
                        <li className="item__tag" key={`${elem.id}_${i}`} >
                            {elem.text}
                            <button className="item__tag-del" onClick={() => handleDelete(i)}>x</button>
                        </li>
                    ))}
                </ul>

                <input 
                    type="text" 
                    value={state.tag} 
                    onChange={handleChange} 
                    placeholder="Enter new tag" 
                    className="item__tag-input"
                />

                <button className="item__btn" onClick={handleClick}>Add</button>
            </div>
        </div>
    );
}

export default Item;

Ответы [ 2 ]

1 голос
/ 08 апреля 2020

Визуализируйте все элементы, даже если они отфильтрованы, и просто скройте элементы, отфильтрованные с помощью CSS (display: none):

const printList = () => {
    //if tag in filter has been added        
    if (state.tags.length > 0) {
        // create a set of tags in state once
        const tagsSet = new Set(state.tags.map(item => item.id));
        return data.assets.map(elem => {
            //hide if no tag is found
            const hideElem = !elem.tags.some(item => tagsSet.has(item.id));

            //if found, return asset 
            return <Item key={elem.title} data={elem} hide={hideElem} />;
        })
    } else {
        return data.assets.map(elem => (<Item key={elem.title} data={elem} /> ));
    }
};

А в самом элементе используйте hide подпишите, чтобы скрыть элемент с помощью CSS, используя атрибут style или класс css:

return (
    <div className="item" style={{ display: props.hide ? 'none' : 'block' }}>

Вы также можете упростить printList() немного больше, всегда создавая Set, даже если state.tags пусто, и если оно пусто, hideElem будет false:

const printList = () => {
  const tagsSet = new Set(state.tags.map(item => item.id));

  return data.assets.map(elem => {
    //hide if state.tags is empty or no selected tags
    const hideElem = tagsSet.size > 0 && !elem.tags.some(item => tagsSet.has(item.id));

    //if found, return asset 
    return (
      <Item key={elem.title} data={elem} hide={hideElem} />
    );
  })
};
1 голос
/ 08 апреля 2020

Проблема, с которой вы сталкиваетесь, заключается в том, что исчезающие карты размонтированы, что означает, что их состояние потеряно. Лучшее решение - сохранить новые пользовательские теги, которые вы добавляете в карты в родительском компоненте, чтобы они были постоянными, независимо от того, установлена ​​карта или нет. Вот измененные файлы:

Родительский компонент

import React, {useState} from 'react';
import Item from './Item'
import data from '../../data.json';

import './Assets.scss'

function Assets() {
    const [state, updateMethod] = useState({tag: "", tags: []});

    const [childrenTags, setChildrenTags] = useState(data.assets.map(elem => elem.tags));

    const addChildrenTag = (index) => (tag) => {
        let newTags = Array.from(childrenTags)
        newTags[index] = [...newTags[index], tag]

        setChildrenTags(newTags)
    }

    const removeChildrenTag = (index) => (i) => {
        let newTags = Array.from(childrenTags)
        newTags[index] = newTags[index].filter((elem, indx) => indx !== i)

        setChildrenTags(newTags)
    }

    const printList = () => {
        //if tag in filter has been added        
        if (state.tags.length > 0) {
            return data.assets.map((elem, index) => {
                //extract ids from obj into array
                const dataArr = elem.tags.map(item => item.id);
                const stateArr = state.tags.map(item => item.id);

                //check if tag is found in asset
                const doesTagExist = stateArr.some(item => dataArr.includes(item));
                //if found, return asset 
                if (doesTagExist) 
                    return (
                        <Item 
                            key={elem.title} 
                            data={elem} 
                            customTags={childrenTags[index]} 
                            addCustomTag={addChildrenTag(index)}
                            removeCustomTag={removeChildrenTag(index)}
                        />
                    )
            })
        } else {
            return data.assets.map((elem, index) => (
                <Item 
                    key={elem.title} 
                    data={elem} 
                    customTags={childrenTags[index]} 
                    addCustomTag={addChildrenTag(index)}
                    removeCustomTag={removeChildrenTag(index)}
                />
            ));
        }
    };

    const handleClick = () => {
        const newTag = {id: state.tag, text: state.tag};
        const copy = [...state.tags, newTag];

        if (state.tag !== "") updateMethod({tag: "", tags: copy});
    }

    const handleChange = e => updateMethod({tag: e.target.value, tags: state.tags});

    const handleDelete = i => {
        const copy = [...state.tags];
        let removed = copy.filter((elem, indx) => indx !== i);

        updateMethod({tag: state.tag, tags: removed});
    }

    return (
        <div className="assets">
            <div className="asset__filter">
                <h3>Add tags to filter</h3>
                <ul className="asset__tag-list">
                    {state.tags.map((elem, i) => (
                        <li className="asset__tag" key={`${elem.id}_${i}`} >
                            {elem.text}

                            <button className="asset__tag-del" onClick={() => handleDelete(i)}>x</button>
                        </li>
                    ))}
                </ul>

                <input 
                    type="text" 
                    value={state.tag}
                    onChange={handleChange} 
                    placeholder="Enter new tag" 
                    className="asset__tag-input"
                />

                <button className="asset__btn" onClick={handleClick}>Add</button>
            </div>

            <div className="item__list-holder">
                {printList()}
            </div>
        </div>
    );  
}

export default Assets;

Дочерний компонент

import React, {useState, useEffect} from 'react';

function Item(props) {
    const [state, updateMethod] = useState({tag: ""});
    cosnst tags = props.customTags
    cosnst addCustomTag = props.addCustomTag
    cosnst removeCustomTag = props.removeCustomTag

    const handleClick = () => {
        if (state.tag !== "") addCustomTag(state.tag);
    }

    const handleChange = e => updateMethod({tag: e.target.value});

    const handleDelete = i => {
        removeCustomTag(i);
    }

    const assets = props.data;

    return (
        <div className="item">
            <img src={assets.url} alt="assets.title"/>
            <h1 className="item__title">{assets.title}</h1>

            <div className="item__tag-holder">
                <ul className="item__tag-list">
                    {tags.map((elem, i) => (
                        <li className="item__tag" key={`${elem.id}_${i}`} >
                            {elem.text}
                            <button className="item__tag-del" onClick={() => handleDelete(i)}>x</button>
                        </li>
                    ))}
                </ul>

                <input 
                    type="text" 
                    value={state.tag} 
                    onChange={handleChange} 
                    placeholder="Enter new tag" 
                    className="item__tag-input"
                />

                <button className="item__btn" onClick={handleClick}>Add</button>
            </div>
        </div>
    );
}

export default Item;

Надеюсь, это поможет, Я могу добавить некоторые комментарии, если что-то неясно:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...