Filtering array of child components - reactjs

In the parent component, I have a state array of child components:
const [myList, setMyList] = useState([]);
setMyList([
<MyComponent color='blue' />
<MyComponent color='blue1' />
<MyComponent color='blue2' />
<MyComponent color='red' />
])
I display these later on with
myList.map(item => item)
I sometimes might have a large number of items in the list and would like to have a search box that does myList.filter() on the name in the input box.
I have something like this
const itemsToHide = myList.filter(item => !item.props.color.includes(e.target.value));
This correctly gives me the items that do not match the search.
Is there a way to now set display to none to these items? Or hide them in another way that will not dismount them since when i clear the search box i want them to still be there.

Is there a reason to have them in another array? Just checking the condition inside the renderer might be easier. Either wrap your Component in another div
myList.map((item) => {
const isVisible = item.props.name.includes(searchTerm);
return (
<div key={item.props.id} style={{display: `${isVisible ? 'block' : 'none'}`>
{item}
</div>
)
}
or pass a prop and render your component in the same manner. Don't forget to add a unique key to all the divs though.

Just render the items that satisfy the condition:
myList
.filter(item => item.name.includes(searchText))
.map((item) =>
(<div key={item.props.id}>
{item}
</div>
))

Related

Correct way to keep track of an array of Toggle Button States?

I am generating constant 4 element array of styled mui Toggle Buttons in this form:
{qualityValues.map(({key, value}) => (
<Grid item xs={6}>
<StyledToggleButton
props
defaultState={value.defaultState}
key={key}
/>
</Grid>
))}
And the button itself is created like so:
function StyledToggleButton(props) {
const [selected, setSelected] = React.useState(props.defaultState);
return (
<StyledToggleButton_
props
selected={selected}
onChange={() => {
setSelected(!selected);
}}
>
{props.text}
</StyledToggleButton_>
)}
Now I'd like to keep track of which buttons are toggled on in a single array. Initially I did this with array comprehension but found it to struggle keeping states accurately. I think the correct way to go about it is to return an {selected, render : {}} object from my StyledToggleButton component but I can't seem to get it all to harmonize. Additionally, I suspect I can use React.Children on my parent Grid in some clever way but I don't have a grasp on how or where to use it. How can I cleanly implement this?

I can't figure out a way to set the key on list elements from the container even though the container can very well set any other properties

Ok so here is a simplification of my real-world use-case. Basically I have a complicated layout and I pass the layout component to the child so that it renders itself within it and then the parent layout displays it as its children. Works great, except I use this in a loop and I cannot figure out how to set the key on the children from the parent.
It's strange because I can set other properties without any issues. In the example below, the "id" property is correctly set from the list, but the console emits the warning:
Warning: Each child in a list should have a unique "key" prop.
const Child = Parent => (
<Parent>
The child is reponsible
<br />
for writing the contents.
</Parent>
);
const present = (title) => {
const Tag = ({ children }) => (
<article id={title} key={title}>
<h1>{title}</h1>
{children}
</article>
);
return Child(Tag);
};
const app = (
<div>
{[
'hello',
'this',
'is',
'a list'
].map(present)}
</div>
);
ReactDOM.render(
app,
document.getElementById('root')
);
here's a demo of that code on codepen
Is there any way to accomplish what I'm trying to do?
This may seem like an over-complicated way to render a template, I know, but I have - I think - a good real-world use-case.
Actually I figured it out.
I need to use a React.Fragment around the return value of "present",
like so:
const present = (title) => {
const Tag = ({ children }) => (
<article id={title}>
<h1>{title}</h1>
{children}
</article>
);
return (
<React.Fragment key={title}>
{Child(Tag)}
</React.Fragment>
);
};
the problem is you are providing the key to the child & not the parent of what is being rendered from the map items.
you can set key to the parent component (instead of <article>) as well as sending the Tag parameter:
const Child = (Parent, key) => (
<Parent key={key}>
The child is reponsible
<br />
for writing the contents.
</Parent>
);
//...
const present = (title) => {
const Tag = ({ children }) => (
<article id={title}>
<h1>{title}</h1>
{children}
</article>
);
return Child(Tag, title);
};
your solution by using a React.Fragment also does a similar job (setting the key for the outer wrapper (here being fragment) in rendering the list using the map method). the difference being you are creating a fragment for each iteration in the map method, but by passing the key you can avoid that as well.

Jest: functional component array list not mapping after adding element

I am trying to list person by clicking onClick function to add empty object so that map can loop
over and show div. I see prop onclick function calling works but i map is not looping over it.
a little help would be appreciated.
// functional component
const listing = ({list=[]}) => (
<>
{
<div id='addPerson' onClick={() => list.push({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
);
export default listing;
// test case
it('renders list-items', () => {
const wrapper = shallow(<listing />);
wrapper.find('#addPerson').prop('onClick')();
console.log(wrapper.find('.listItems').length); // showing zero, expected 1
});
Updating List is not going to cause a re-render in your component. I am not sure where List is coming from, but I am assuming it is coming from a local state in the parent, or probably redux store. From inside your component, you will have to call a function in your parent to update the list array there.
Below is a version of your component, assuming the list in your local state. You will have to adjust the code to update the state in the parent or redux store, but the below should be enough to give you an idea of what to do. Also, don't push the element to array, as it mutates the object and will not contact re-render.
const Listing = ({list = [], addToList}) => {
return <>
{
<div id='addPerson' onClick={() => addToList({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
};

React - Setting state to target with onClick method

I am trying to recreate a tabs component in React that someone gave me and I am getting stuck while getting the onClick method to identify the target.
These are the snippets of my code that I believe are relevant to the problem.
If I hardcode setState within the method, it sets it appropriately, so the onClick method is running, I am just unsure of how to set the tab I am clicking to be the thing I set the state to.
On my App page:
changeSelected = (event) => {
// event.preventDefault();
this.setState({
selected: event.target.value
})
console.log(event.target.value)
};
<Tabs tabs={this.state.tabs} selectedTab={this.state.selected}
selectTabHandler={() => this.changeSelected}/>
On my Tabs page:
{props.tabs.map(tab => {
return <Tab selectTabHandler={() => props.selectTabHandler()} selectedTab={props.selectedTab} tab={tab} />
})}
On my Tab page:
return (
<div
className={'tab active-tab'}
onClick={props.selectTabHandler(props.tab)}
>
{props.tab}
</div>
When I console.log(props.tab) or console.log(event.target.value) I am receiving "undefined"
There are a few issues causing this to happen. The first issue is that you wouldn't use event.target.value in the Content component because you aren't reacting to DOM click event directly from an onClick handler as you are in Tab, instead you are handling an event from child component. Also keep in mind that event.target.value would only be applicable to input or similar HTML elements that have a value property. An element such as <div> or a <span> would not have a value property.
The next issues are that you aren't passing the tab value from Tabs to Content and then from within Content to it's changeSelected() handler for selectTabHandler events.
In addition the onClick syntax in Tab, onClick={props.selectTabHandler(props.tab)} is not valid, you will not be able to execute the handler coming from props and pass the props.tab value. You could instead try something like onClick={() => props.selectTabHandler(props.tab)}.
Content - need to pass tab value coming from child to changeSelected():
render() {
return (
<div className="content-container">
<Tabs
tabs={this.state.tabs}
selectedTab={this.state.selected}
selectTabHandler={tab => this.changeSelected(tab)}
/>
<Cards cards={this.filterCards()} />
</div>
);
}
Tabs - need to pass tab coming from child to selectTabHandler():
const Tabs = props => {
return (
<div className="tabs">
<div className="topics">
<span className="title">TRENDING TOPICS:</span>
{props.tabs.map(tab => {
return (
<Tab
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
);
})}
</div>
</div>
);
};
export default Tabs;
Also don't forget the unique key property when rendering an array/list of items:
<Tab
key={tab}
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
Here is a forked CodeSandbox demonstrating the functionality.

React: Hiding/Showing an element while hiding the rest of the similar elements

I have a recipe creator that creates recipes and also lists their ingredients. I want it to only show the Recipe name and it's div it's inside of, but when you click it, it reveals the ingredients section. If any other sections are open I want it to also close them.
I've tried a couple solutions with state but haven't come up with a solid solution. I could probably do it with jQuery but I've heard it's not good practice to use React and jQuery so I'd rather do it properly.
This is the full app: https://github.com/jeffm64/recipe-box2/tree/master/src/components
The recipe boxes are rendered through .map in the main app render function as shown here:
{Recipes.map(function(item, key) {
return <RecipeBox recipe={Recipes} name={item.name} ingredients={item.ingredients} order={item.order} key={key} generalUpdate={genUpdate} />;
})}
Revealing just the one component's ingredients will be easier. Closing the rest will be a little trickier.
Revealing ingredients - Without Redux, you could add a state property isOpen. When you click on a button in that component it should change the state property isOpen to true. Hide/show the ingredients based on that state property value, which you can do in the markup (shown below) or with a class and a CSS rule.
<div key={1}>
showIngredients = () => { this.setState({isOpen: true}) }
<button onClick={this.showIngredients}>Show ingredients</button>
{this.state.isOpen && <div>Ingredients list</div>}
</div>
Hiding other ingredients - This is a bit trickier and will require a different kind of solution. You'll need to define a method in a parent component that sets the state of all child components. Note the key in the component above. If each child has a unique key, and the parent defines a state property of selectedKey, you could do something like this:
// parent
revealChild = (key) => {this.setState(selectedKey: key)}
...
<div>
{children.map((child) => {
return <Child onReveal={this.revealChild} key={child.key} isSelected={this.state.selectedKey == child.key}/>
})}
</div>
// child
handleReveal = () => this.props.onReveal(this.props.key)
showIngredientsClass = () => this.props.isSelected ? 'visible' : 'hidden'
showButtonClass = () => this.props.isSelected ? 'hidden' : 'visible'
...
<button onClick={handleReveal} className={this.showButtonClass()}>Show ingredients</button>
<div className={this.showIngredientsClass()}>
My ingredients
</div>

Resources