简体   繁体   English

通过父组件过滤子组件时如何维护子组件的state?

[英]How to maintain state of child components when they are filtered through the parent component?

I am building a small app using create react app to improve my react knowledge but now stuck with state management.我正在使用 create react app 构建一个小应用程序来提高我的反应知识,但现在坚持使用 state 管理。

The apps maps through JSON data on the parent component and prints 6 "image cards" as child components with an array of "tags" to describe it and other data(url, titles etc..) passed as props.应用程序通过父组件上的 JSON 数据进行映射,并打印 6 个“图像卡”作为子组件,其中包含一组“标签”来描述它以及作为道具传递的其他数据(网址、标题等)。

Each card has an input which you can add more tags to the existing list.每张卡片都有一个输入,您可以将更多标签添加到现有列表中。

On the parent component there is an input which can be used to filter the cards through the tags.在父组件上有一个输入,可用于通过标签过滤卡片。 (only filters through default tags not new ones added to card). (仅过滤默认标签而不是添加到卡片的新标签)。

What I am trying to achieve is maintaining the state of each card when it gets filtered.我想要实现的是在过滤后保持每张卡的 state 。 Currently what happens is if I add new tags to the cards and filter using multiple tags, only the initial filtered cards contain the new tags, the rest get re-rendered with their default tags.目前发生的情况是,如果我向卡片添加新标签并使用多个标签进行过滤,则只有初始过滤的卡片包含新标签,rest 会使用其默认标签重新呈现。 Can someone tell me where I am going wrong, thanks.谁能告诉我哪里出错了,谢谢。

My project can also be cloned if it makes things easier https://github.com/sai-re/assets_tag如果它使事情变得更容易,我的项目也可以被克隆https://github.com/sai-re/assets_tag

data.json example data.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" }
            ]
        }
    ]
}

Parent component父组件

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;

Child component子组件

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;

The problem that you are facing is that the cards that disappear are unmounted, meaning that their state is lost.您面临的问题是消失的卡已卸载,这意味着它们的 state 丢失了。 The best solution is keeping the new custom tags you add to cards in the parent component, so it's persistent, no matter if the card is mounted or not.最好的解决方案是将添加到卡片的新自定义标签保留在父组件中,因此无论卡片是否已安装,它都是持久的。 Here are the modified files:以下是修改后的文件:

Parent component父组件

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;

Child component子组件

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;

Hope this helps, I can add some comments if anything is unclear:)希望这会有所帮助,如果有任何不清楚的地方,我可以添加一些评论:)

Render all items, even if they are filtered out, and just hide the items filtered out using CSS ( display: none ):渲染所有项目,即使它们被过滤掉,只需隐藏使用 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} /> ));
    }
};

And in the Item itself, use the hide prop to hide the item with CSS using the style attribute or a css class:在项目本身中,使用hide道具使用style属性或 css class 隐藏项目:

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

You can also simplify printList() a bit more, by always creating the Set, even if state.tags is empty, and if it's empty hideElem would be false :您还可以通过始终创建 Set 来进一步简化printList() ,即使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} />
    );
  })
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 React 在重新渲染父组件时如何重用子组件/保留子组件的 state? - How does React re-use child components / keep the state of child components when re-rendering the parent component? React - 当父状态组件发生更改时,如何强制子组件重新呈现? - React - How do i force child components to re render when parent state component changes? 如何避免在父组件 state 更新时重新渲染循环中的所有子组件 - How to avoid rerender all child components which in loop when parent component state update 如何让父组件在React中管理子组件的状态? - How to let a parent component manage the state of child components in React? 如何维护Sortable容器的子组件状态? - How to maintain state of child components of Sortable container? 当从父组件过滤时,React 子组件会从另一个子组件超越状态 - React child component overtakes state from another child when filtered from parent 当父状态更新时反应父组件重置子组件状态 - React parent component reseting child components state when parent state updates 如何在 React.js 功能组件中将子组件 state 传递给父组件 state? - How to pass child component state to parent component state in React.js functional components? 父类状态改变时如何重新加载子组件 - How to reload child component when the state of parent class changed 更改父状态后如何重新呈现子组件? - How to re-render child component when Parent state is changed?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM