With a text field in the parent component, I want to filter through multiple child components where the string is being passed through the props. The child components have been outputted through a map function which is importing data from an API into the props also. I have the user input console logging after being passed as props to the child (searchTerm). My problem is that I can't hide the display of individual components based on the user's input. The trouble I'm having is that when one decides to hide itself - they all do. I've tried indexOf and have found include() to be more useful. I'm worried about my approach to the problem and would appreciate some wisdom from more seasoned react developers.
//parent component
render() {
return (
<React.Fragment>
<input type="text" className = {styles.searchField} id="searchField" placeholder = "Search..." onChange = {this.getUserInput}/>
<div className={styles.container}>
{this.state.importedBooks.map((element, index) => (
<Tile key ={index} searchTerm ={this.state.searchTerm} buy ={element.amazon_product_url}
img ={element.book_image} summary ={element.description} url={element.book_image}
book_author ={element.author} book_title ={element.title} />
))}
</div>
</React.Fragment>);
}
}
//child component
class Tile extends React.Component {
public state:{display:boolean} = {display:true};
public getContainerContent = () => {
const containers: NodeListOf<Element> | null = document.querySelectorAll("#container");
containers.forEach(element => {
if (element.innerHTML.toLocaleLowerCase().includes(this.props.searchTerm) !== false) {
this.setState({display:true});
}
else if (this.props.searchTerm == "") {
this.setState({display:true});
}
else {
this.setState({ display: false });
}
})
};
public componentWillReceiveProps = () => {
this.getContainerContent();
}
render() {
const renderContainer = this.state.display ? styles.container : styles.hideDisplay;
return (
<div className={styles.container} id="container">
<h1>{this.props.book_title}</h1>
<h2>{this.props.book_author}</h2>
<a href={this.props.buy} target="_blank"><img src = {this.props.img} alt="book cover"/></a>
<p>{this.props.summary}</p>
<a href={this.props.buy} target="_blank">Purchase</a>
</div> );
}
}
I think the problem stems from the getContainerContent
method in your child component.
Why would a single child component be concerned with data that belongs to other child components?
A way you can solve this is to first declare in the parent component which props are you going to match the filter against when displaying child components.
Then, you could iterate over these declared props and decide whether the component should display a child component or not.
// parent comp
private shouldDisplayElement (element, searchTerm: string): boolean {
const propsToMatchFiltersAgainst = ['book_title', 'book_author', 'buy', /* etc... */];
let shouldDisplay = false;
for (const p of propsToMatchFiltersAgainst) {
if (`${element[p]}`.toLowerCase().includes(searchTerm.trim())) {
shouldDisplay = true;
}
}
return shouldDisplay;
}
// parent's render fn
<div className={styles.container}>
{this.state.importedBooks.map((element, index) => (
this.shouldDisplayElement(element, this.state.searchTerm)
? <Tile
key={index}
buy={element.amazon_product_url}
img={element.book_image}
summary={element.description}
url={element.book_image}
book_author={element.author} book_title={element.title}
/>
: null
))}
</div>
This way, given the current situation, you don't need to use the getContainerContent
method in your child component anymore.