child组件通过parent组件过滤后如何保持状态?
How to maintain state of child components when they are filtered through the parent component?
我正在使用 create react app 构建一个小应用程序来提高我的反应知识,但现在坚持使用状态管理。
应用程序通过 JSON 数据映射到 parent 组件,并将 6 "image cards" 打印为 child 组件,并使用 "tags" 数组来描述它和其他数据(url、标题等)作为道具传递。
每张卡片都有一个输入,您可以将更多标签添加到现有列表中。
在 parent 组件上有一个输入,可用于通过标签过滤卡片。
(只过滤默认标签,不过滤新添加到卡片的标签)。
我想要实现的是在过滤时保持每张卡片的状态。目前发生的情况是,如果我向卡片添加新标签并使用多个标签进行过滤,只有初始过滤的卡片包含新标签,其余卡片使用默认标签获得 re-rendered 。谁能告诉我哪里出错了,谢谢。
我的项目也可以克隆,如果它使事情变得更容易的话
https://github.com/sai-re/assets_tag
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分量
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分量
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;
您面临的问题是消失的卡未安装,这意味着它们的状态已丢失。最好的解决方案是将您添加到卡片的新自定义标签保留在父组件中,因此无论卡片是否安装,它都是持久的。以下是修改后的文件:
父组件
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;
希望这对您有所帮助,如果有任何不清楚的地方,我可以添加一些评论:)
渲染所有项目,即使它们被过滤掉,只隐藏使用 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
属性使用 style
属性或 css class 隐藏具有 CSS 的项目:
return (
<div className="item" style={{ display: props.hide ? 'none' : 'block' }}>
您还可以通过始终创建集合来进一步简化 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} />
);
})
};
我正在使用 create react app 构建一个小应用程序来提高我的反应知识,但现在坚持使用状态管理。
应用程序通过 JSON 数据映射到 parent 组件,并将 6 "image cards" 打印为 child 组件,并使用 "tags" 数组来描述它和其他数据(url、标题等)作为道具传递。
每张卡片都有一个输入,您可以将更多标签添加到现有列表中。
在 parent 组件上有一个输入,可用于通过标签过滤卡片。 (只过滤默认标签,不过滤新添加到卡片的标签)。
我想要实现的是在过滤时保持每张卡片的状态。目前发生的情况是,如果我向卡片添加新标签并使用多个标签进行过滤,只有初始过滤的卡片包含新标签,其余卡片使用默认标签获得 re-rendered 。谁能告诉我哪里出错了,谢谢。
我的项目也可以克隆,如果它使事情变得更容易的话 https://github.com/sai-re/assets_tag
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分量
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分量
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;
您面临的问题是消失的卡未安装,这意味着它们的状态已丢失。最好的解决方案是将您添加到卡片的新自定义标签保留在父组件中,因此无论卡片是否安装,它都是持久的。以下是修改后的文件:
父组件
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;
希望这对您有所帮助,如果有任何不清楚的地方,我可以添加一些评论:)
渲染所有项目,即使它们被过滤掉,只隐藏使用 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
属性使用 style
属性或 css class 隐藏具有 CSS 的项目:
return (
<div className="item" style={{ display: props.hide ? 'none' : 'block' }}>
您还可以通过始终创建集合来进一步简化 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} />
);
})
};