I'm working on a react project to learn react.
In a component's render method, when I use .map to iterate over values and return an array of components, everything works as expected.
<ol className="books-grid">
{
books && books.map((book, index) => {
if (book.shelf === shelf) {
return (
<Book key={book && book.id ? book.id : index} changeShelf={this.props.changeShelf} book={book} />
);
}
})}
</ol>
But when I use filter:
<ol className="books-grid">
{
books && books.filter((book, index) => {
if (book.shelf === shelf) {
return (
<Book key={book && book.id ? book.id : index} changeShelf={this.props.changeShelf} book={book} />
);
}
})}
</ol>
I get the error (which I've researched)
Uncaught (in promise) Error: Objects are not valid as a React child
I don't understand why filter is throwing this error vs map? Is there something unique to react and .map? Both return an array.
Array.filter does not allow you to transform the data into components. That is the job of Array.map.
You should instead filter first, then chain the map call afterward:
{
books && books
.filter(book => book.shelf === shelf)
.map((book, index) => {
return (
<Book
key={book && book.id ? book.id : index}
changeShelf={this.props.changeShelf}
book={book} />
);
})
}
If you want to avoid a second pass over your list of books, you can return null as well, though this is "less good" because you're forcing React to render null when it doesn't need to do any work at all:
{
books && books
.map((book, index) => {
if (book.shelf !== shelf) {
return null;
}
return (
<Book
key={book && book.id ? book.id : index}
changeShelf={this.props.changeShelf}
book={book} />
);
})
}
There is nothing unique to React and map() or filter().
In the first example when using map() you are returning an array of React components which are rendered in the DOM. You are transforming (mapping) each plain JavaScript object in the array into a React component. As a matter of fact, you are also going to return some undefined elements in the resulting array, if the condition book.shelf === shelf is falsy. Your array may look like [<Book />, <Book />, undefined, <Book />, undefined]. That's not such a big deal, since React won't render falsy values (null or undefined elements will just be skipped).
The second example won't return the same result (an array of React components), but an array of plain JavaScript objects (of type book). This is because no matter what are you returning from the filter function, it's going to be cast to a Boolean value - true or false and that value is going to decide if the current element is going to be filtered or not. The result of your .filter() function is going to be something like this (imagine shelf === 'Science'):
Original array: [{ shelf: "Science" }, { shelf: "Thrillers" }, { shelf: "Informatics" }]
Filtered array: [{ shelf: "Science" }]
As you can see, the items in the array won't be React components (<Book />) and React won't be able to render them in the DOM, thus the error it throws.
If you only want to use one pass over the array you can use reduce:
books && books
.reduce(
(all,book, index) => {
if (book.shelf !== shelf) {
return all;
}
return all.concat(
<Book
key={book && book.id ? book.id : index}
changeShelf={this.props.changeShelf}
book={book} />
);
}
,[]
)
However; I think using filter and map makes for code that's easier to read.
The answer to me seems good but I needed to use includes() in order to work, also maybe a good idea to use toLowerCase():
{
books && books
.filter(book => book.shelf.toLowerCase().includes(shelf.toLowerCase()))
.map((book, index) => {
return (
<Book
key={book && book.id ? book.id : index}
changeShelf={this.props.changeShelf}
book={book} />
);
})
}
I also facing the same error. then, I also wrap my map method on filter method which helps me to solve the error and accept it as react valid child.
<ul className="menu">
{navigationLinks?.filter((eachNavigation) => {
if (eachNavigation.select.length < 1) {
return eachNavigation;
}
}).map((eachNavigation, index) => {
return (
<li key={index} className="menu__list menu__list--noSelect">
<a href={eachNavigation.link}>{eachNavigation.title}</a>
</li>
)
})
}
</ul>
<ul className="menu">
{navigationLinks?.filter((eachNavigation) => {
if (eachNavigation.select.length < 1) {
return eachNavigation;
}
}).map((eachNavigation, index) => {
return (
<li key={index} className="menu__list menu__list--noSelect">
<a href={eachNavigation.link}>{eachNavigation.title}</a>
</li>
)
})
}
</ul>
Related
I'm trying to .filter() an array to remove items from my data.map(), but I don't want to call filter() multiple times. How can I pass an array into .includes()?
this works, but uses multiple .filter()
const items = ['apple','pear','peach','pineapple']
return (
<>
{items
.filter(item => !item.includes('r'))
.filter(item => !item.includes('c'))
.filter(item => !item.includes('i'))
.map(item =>
{item}
)}
</>
)
// returns 'apple'
I tried this but it's not right
const items = ['apple','pear','peach','pineapple']
const toBeRemoved = ['r','c','i'];
return (
<>
{items
.filter(item => !item.includes(toBeRemoved))
.map(item =>
{item}
)}
</>
)
You can use filter and Array.prototype.some() together to achieve this
const items = ["apple", "pear", "peach", "pineapple"];
const toBeRemoved = ["r", "c", "i"];
return (
<>
{items
.filter((item) => !toBeRemoved.some((letter) => item.includes(letter)))
.map((item) => (
<div>{item}</div>
))}
</>
);
my answer was simple, answered here :
.filter(item => !toBeRemoved.some(i => item.includes(i)))
It looks like you just have the ordering of includes backward. It's a property of arrays, so you have to start with the list of things, and ask what it might include. You have it the wrong way around.
Then also your map function is returning undefined, so you wouldn't get any results anyway. Maybe you confused it for the curly brackets in JSX that let you insert variables? But in this case they're acting as a function body. This might work?
const items = ['apple','pear','peach','pineapple']
const toBeRemoved = ['r','c','i'];
return (
<ul>
{items
.filter(item => !toBeRemoved.includes(item))
.map(item => (
<li>item</li>
))
}
</ul>
)
As includes method take only single string value and returns true if found.
But in filter function we can attach multiple conditions.
So, this can help you.
const items = ['apple','pear','peach','pineapple']
const toBeRemoved = ['r','c','i'];
console.log(items.filter(item => !item.includes("r") && !item.includes("c") && !item.includes("i")));
const items = ['apple','pear','peach','pineapple']
const toBeRemoved = ['r','c','i'];
return (
<ul>
{items
.filter(item => toBeRemoved.every(word => !item.includes(word)))
.map(item => <li>{item}</li>)
}
</ul>
)
array.every() is equivalent to !array.some()
.some(callback): loops each items of array with callback. If at least one callback returns truthy, some() returns true.
.every(callback): If at least one callback returns falsy, every() returns false.
i want to return jsx if some condition is true if not undefined should be returned.
below is my code,
const showInfo = (item) {
return (
<div>
<div>
<span>name</span>
</div>
<div>
<button>click</button>
</div>
</div>
);
}
const Parent = () => {
return (
<Child
onDone = {({item}) => {
notify ({
actions: (condition === 'value1' || condition === 'value2' ) &&
showInfo(item) //should put this condition into showInfo method
})
}}
/>
);
}
what i am trying to do?
the above code works. but now i want to put the condition inside the showInfo method. so if condition is true return jsx and if condition is false should return undefined.
what i have tried?
I have tried something like below
const showInfo = (item) {
return
{(condition === 'value1' || condition === 'value2' ) ? <div>
<div>
<span>name</span>
</div>
<div>
<button>click</button>
</div>
</div>
: undefined
}
);
}
const Parent = () => {
return (
<Child
onDone = {({item}) => {
notify ({
actions: showInfo(item) //error here
})
}}
/>
);
}
but the above tried code, gives error "Type 'void' is not assignable to type 'ReactNode'" at actions statement.
could someone help me with this. i am not sure if i have used ternary operator properly. thanks.
EDIT
after trying one of the answers provided,
notify is a method that is returned from usehook
and it evaluates to the component below
const Something: React.FC<SomethingProps> = ({
description,
actions,
...props
}) =>
(
<Header>
<Title>{title}</Title>
</Header>
{(description ||actions) && (
<Body> //this is displayed
{description && <Description>{description}</Description>}
{actions && <Actions>{actions}</Actions>}
</Body>
)}
);
here the component is displayed when the condition fails in showInfo component.
in showInfo i am returning undefined if condition fails but still in the Something component the is displayed even though i have {description || actions}
i am not sure what is happening here.what is the condition i have to check for actions to not display in this case
i have tried
{(description ||actions !== 'false') && (
<Body> //this is displayed
{description && <Description>{description}</Description>}
{actions && <Actions>{actions}</Actions>}
</Body>
)}
and this works. i am wondering why i should specifically mention
actions !== 'false'
instead of actions only
could someone help me with this. thanks.
If you want to return jsx from function you should wrap them inside some component. In this case you cen use <React.Fragment> or just <>. Another problem which I can see is that you probably forgot about arrow in you arrow function. Also don't know from where variable names condition comes from.
const showInfo = (item) => {
return (
<>
{ condition === "value1" || condition === "value2" ? (
<div>
<div>
<span>name</span>
</div>
<div>
<button>click</button>
</div>
</div>
) : undefined}
</>
);
};
Wouldn't it be better to use the useState or useEffect hooks?
I have this response object from an api, and I want to loop it and render it as if it was a normal array, how can I render tshirt, jeans and furniture? I will not like to render the value of sneakers, Any suggestion?
const items = {
tshirt: "Model TS",
jeans: "ModelXW",
sneakers: "indcdsc54",
furniture: "Table31S"
};
{Object.keys(items).map=>{i =>
<Card>
{items[key]}
</Card>
}
}
Try this one implementation line:
{Object.entries(items).filter(v => v[0] !== 'sneakers').map((v, idx) => <Card key={idx}>v[1]</Card>)}
You can read properties of an object using dynamic key: objectName[keyName]:
{
Object.keys(items).map(key => <Card key={key}>{items[key]}</Card>)
}
and to filter out sneakers:
{Object.keys(items).filter(key => key !== 'sneakers').map((key) => (
<Card key={key}>{items[key]}</Card>
))}
Instead of multiple loops, add an if condition to your code:
Object.keys(items).map(key => {
if (key != 'sneakers') {
return(<Card>{items[key]}</Card>);
}
});
We can use destructuring and it is definitely more readable.
const { sneakers, ...rest } = items;
Object.keys(rest).map((item, id) => {
<Card key={id}>
{item}
</Card>
}
);
I trying to generate a big form based on what I get from the server.
sometimes I generate 32 elements sometimes 57 or 4 I don't know.
I try to create a component for each type of element like select, text, number, textarea and so on.
each component passes the value to the parent component and setState the value to the parent.
imagine I have 20 inputs and custom select-option elements.
when I type something in one of the inputs characters show up after 2seconeds and there is a huge lag in my component.
I know because of the setState method my hole component (I mean my parent component or my single source of truth) re-renders and causes the problem.
in fact, I don't know other ways.
I try to use a "this.VARIABLE" and instead of setState, I update the "this.VARIABLE" and problem solved. but I need my state.
any help or solution?
my code (parent Component, source of truth ):
// ---> find my component based on the type that I get from server
findComponent ( item , index) {
if ( item.type === 'text' || item.type === 'number') {
return (<Text data={item} getUpdated={this.fetchingComponentData} />);
} else if ( item.type === 'longtext') {
return (<Textarea data={item} getUpdated={this.fetchingComponentData} />);
} else if ( item.type === 'select' ) {
return (<SelectOption data={item} getUpdated={this.fetchingComponentData} />);
} else if ( item.type === 'autocomplete') {
return (<AutoTag data={item} url={URL1} getUpdated={this.fetchingComponentData} />);
} else if ( item.type === 'checkbox_comment' ) {
return (<CheckboxComment data={item} getUpdated={this.fetchingComponentData} />);
} else if ( item.type === 'multiselect' ) {
return (<Multiselect data={item} getUpdated={this.fetchingComponentData} />);
} else {
return (<p>THERE IS NO TYPE OF => {item.type}</p>);
}
}
// ----> if i setState here ==> big lag
fetchingComponentData(OBJ) {
let index = null;
// let Answer = [...this.state.Answer];
index = Helper.find_item(this.Answer , OBJ , 'unique_key');
if ( index === -1 ) {
this.Answer.push(OBJ);
} else {
this.Answer[index].value = OBJ.value;
}
}
// ----> in my render method
render () {
return (
<React.Fragment>
<div className="row Technical section" data-info="Technical">
<div className="col-6">
{data.map( (item,index) => {
return (
<React.Fragment key={index}>
<div className="rowi">
{item.attributes.map( (item, index)=> {
return <React.Fragment key={index}>{this.findComponent(item, index)}</React.Fragment>;
})}
</div>
</React.Fragment>
)
})}
</div>
<div className="col-6"></div>
</div>
</React.Fragment>
);
}
Have you tried to make an object out of your components and pass it to setState at once?
const nextState = componentList.map(component => {
return {[component]: value};
});
this.setState({...nextState});
Edit: Okay i got another part you could do better.
You should build an array with you components in componentWillMount function instead of fetching all the data inside the render. Like you said, it's updating everytime any state changes, and all the components are also updating with the parent.
This is to be made in addition with what I suggested before, but it is of far more importance because of the impact on the ressource.
I am trying to render an objects key and value, however it just doesn't seem to work.
I have managed to display it in the console, but not the actual dom.
I am iterating through the object which has multiple entries.
What do I need to do to actually print out the values?
{attributes.map(items => {
{Object.keys(items).map((key) => {
console.log(key, items[key]);
})}
})}
Like this:
{attributes.map((items, index) => {
return (
<ul key={index}>
{Object.keys(items).map((key) => {
return (
<li key={key + index}>{key}:{items[key]}</li>
)
})}
</ul>
)
})}