I have an array of objects like this getting mapped and rendered.
const items = [
{
id: '1',
name: 'name',
comment: 'text',
},
{
id: '2',
name: 'name',
},
etc...
]
var hasComment = items.some(item => item.hasOwnProperty('comment'));
const card = items.map(item =>
<div key={item.id}>
<ul className="detail">
<li>{item.name}</li>
{hasComment ? <li>{item.comment}</li> : ''}
</ul>
</div>
I want the comment to be displayed depending of the property of each individual object. I googled the above solution but as soon as any object of my array has this property it gets displayed for all, leaving some empty list items. The comment on each individual item should only be rendered, if the object actually has this property.
Hopefully it's clear what I mean.
You need to put the property check inside a .filter before mapping to render:
const card = items
.filter(item => item.hasOwnProperty('comment')
.map(item => {
// ...
Related
I have a following array,
const bio = [
{id: 1, name: "Talha", age: 26},
{id: 2, name: "Ayub", age: 22}
]
Here is a complete code of mine,
import './App.css';
import React, {useState} from 'react';
const bio = [
{id: 1, name: "Talha", age: 26},
{id: 2, name: "Ayub", age: 22}
]
const App = () => {
const [bioData, setbioData] = useState(bio);
const clear_function = () => setbioData([])
return (
<div className="App">
{
bioData.map((arr) =>
<>
<h3 key={arr.id}>Name: {arr.name}, Age: {arr.age}</h3>
<button onClick={() => clear_function()}>Click me</button>
</>
)}
</div>
);
}
export default App;
Now, this code hooks the whole array to useState. Whenever I click the button "Click me", it sets the state to empty array and returns empty array as a result.
But what if I want to remove a specific row of the array? I have made two buttons, each for one row. What I want to do is if I click the first button, it removes the first row from the array as well and keeps the 2nd array.
Similarly, if I want to change a specific attribute value of a specific row, how can I do that using useState hook? For example, I want to change the name attribute of the 2nd row. How is it possible to do this with useState?
And same case for addition. IF I make a form and try to add a new row into my bio array, how would I do that? I searched it up on Google, found a similar post as well but the answer given in it wasn't satisfactory and did not work, which is why I am asking this question again.
If I understood the question right, I think you can pass the updated object to set state and that'll be it.
To change a particular object in array, do something lie this:
// Update bio data with id = 2
setbioData(prevValue =>
[...prevValue].map(el =>
el.id === 2 ? ({...el, name:'new name'}) : el)
)
Also, you have set key at the wrong place
Full refactored code:
const bio = [
{id: 1, name: "Talha", age: 26},
{id: 2, name: "Ayub", age: 22}
]
const App = () => {
const [bioData, setbioData] = useState(bio);
const clear_function = (id) => {
setbioData(prevValue =>
[...prevValue].map(el =>
el.id === id ? ({...el, name:'new name'}) : el)
)
}
return (
<div className="App">
{
bioData.map((arr) =>
<div key={arr.id}>
<h3>Name: {arr.name}, Age: {arr.age}</h3>
<button onClick={() => clear_function(arr.id)}>Click me</button>
</div>
)}
</div>
);
}
Adding a row to the array with react useState would be as simple as the following:
function addRow(objToAdd){
//get old data and add new row
let newBioData = bioData.push(objToAdd)
setBioData(newBioData)
}
To remove a specific row you would need to find the object within the bioData array, remove it and set the new bioData like so:
function removeRow(rowId){
//Filter the array by returning every object with a different Id than the one you want to remove
let newBioData = bioData.filter(function (value, index, arr){ return value.id != rowId })
setBioData(newBioData)
}
To update the state you could use the spread operator like so:
function updateRow(rowId, data){
//Iterate list and update certain row
bioData.filter(function (value, index, arr){
//Row to update
if(value.id == rowId){
return {name: data.name, age: data.age}
}else{
//Nothing to update, return current item (spread all values)
return {...value}
}
})
setBioData ([])
}
I'm not sure if I fully understand your question, but when you want to change the state in a functional component, you can't directly change the state object, instead you need to create a new desired state object, and then set the state to that object.
for example, if you want to add a new row to your array, you'll do the following:
setbioData((currBioData) => [...oldBioData, {id: 3, name: "Bla", age: 23}]);
When you want to change the state based on the current state, there's an option to send the setState function a callback function which accepts the current state, and then using the spread operator we add a new row to the array.
I'm not going to get into why it is better to pass setState a function to modify current state but you can read about it in React official docs
Looks like there are 3 questions here. I'll do my best to help.
"what if I want to remove a specific row of the array?"
One way is to filter your current array and update the state with the new array.
In your example: onClick={()=>setbioData(arr.filter(row=>row.id !== arr.id))}
"if I want to change a specific attribute value of a specific row, how can I do that using useState hook?"
You will need to create a new array with the correct information and save that array to state.
In your example: [... bioData, {id:/row_id/,name:/new name/,age:/new age/}]
"IF I make a form and try to add a new row into my bio array, how would I do that"
In this case, you would push a new object into your array. In your example:
setbioData(previousData=>previousData.push({id:previousData.length+1,name:/new name/,age:/new age/})
I hope this helps you, best of luck
import './App.css';
import React, {useState} from 'react';
const bio = [
{id: 1, name: "Talha", age: 26},
{id: 2, name: "Ayub", age: 22}
]
const App = () => {
const [bioData, setbioData] = useState(bio);
const clear_function = (i) => {
let temp = bioData.slice()
temp = temp.splice(i, 1)
setbioData(temp)
}
return (
<div className="App">
{
bioData.map((arr, i) =>
<>
<h3 key={arr.id}>Name: {arr.name}, Age: {arr.age}</h3>
<button onClick={() => clear_function(i)}>remove row</button>
</>
)}
</div>
);
}
export default App;
Same way you need to just to update a specific attribute just update that element in the temp array and do setBioData(temp)
Similarly, If you want to add another row do temp.push({id: 1, name: "Talha", age: 26}) and do setBioData(temp)
I am mapping an array of objects and rendering a div based on it.
Below is my code:
const App = () => {
const types = React.useMemo(() => {
return get(data, 'something.typesDetails', []);
}, [data]);
return (
{true &&
<div> header </div>
<div>
{types.map((t => {
return (
<div>{type.name}</div>
)
})}
</div>
}
);
}
Here is the types array:
const types = [
{
id: '1',
name: 'type1',
},
{
id: '2',
name: 'type2',
},
]
The above code works fine when data structure is like above.
But consider if types has value like so
const types = [
{
id: null,
name: null,
}
]
In this case, it will still map through types and display div. I don't want to display div element. How can I check if types value contains null or stop looping through types if it has value like above?
You can use filter to remove elements with null values.
types = types.filter(x => !Object.values(x).includes(null));
You can also use Array#every to check:
if(types.every(x => !Object.values(x).includes(null)){
//go ahead with mapping
}
You can filter object inside of array and then map through them:
return (
{true &&
<div> header </div>
<div>
{types.filter(t => t.id != null && t.name != null)
.map((t => {
return (<div>{type.name}</div>)
})}
</div>
}
);
I have a main component, App.js, with the following state.
state = {
cats: [
{ id: '1', name: 'Cat 1'},
{ id: '2', name: 'Cat 2'},
{ id: '3', name: 'Cat 3'},
],
dogs: [
{ id: '1', name: 'Dog 1'},
{ id: '2', name: 'Dog 2'},
],
birds: [
{ id: '1', name: 'Bird 1'},
]
}
Also, in the same component a method (now just consoling the index) and the imported component that list the animals.
Method:
deleteAnimalHandler = (index) => {
console.log(index)
}
JSX or return:
<div>
<AnimalList deleteAnimal={this.deleteAnimalHandler} animal={this.state.cats} />
<AnimalList deleteAnimal={this.deleteAnimalHandler} animal={this.state.dogs} />
<AnimalList deleteAnimal={this.deleteAnimalHandler} animal={this.state.birds} />
</div>
In AnimalList component I´m mapping the props and rendering the lists.
{props.animal.map(({id, name}, index) =>
<div key={id}><div>{name}</div><div onClick={() => props.deleteAnimal(index)}>Delete animal</div></div>
)
}
Every time I click on the div with content "Delete animal" I´m executing the deleteTaskHandler() method and logging in the console the index of that element in the array.
What I´m trying to do of course is not logging into the console, if not, when I click on that div, delete in the particular property of the state the element with the passed index.
So, if I click in Delete animal below Cat 2 I should delete Cat 2 from the cats array. The same for dogs and birds.
I cannot find the logic to let the handler know which is the context or which should be the array that setState() should update.
Anyone can help me? Thanks
You could pass the animal type
<AnimalList deleteAnimal={this.deleteAnimalHandler} animal={this.state.cats} animalType='cats' />
And add to the click
onClick={() => props.deleteAnimal(index, this.props.animalType)
And implement the deleteAnimalHandler
deleteAnimalHandler = (index, animalType) => {
let newAnimals = this.state.animals[animalType].filter((x, i) => i != index)
let newState = {}
newState[animalType] = newAnimals
this.setState(newState)
}
You need to pass category of the animal too.
deleteAnimalHandler = (index,category) => {
let temp = JSON.parse(JSON.stringify(this.state))
temp[category] = temp[category].filter(({id})=> id !== index )
// you can set state now
}
Here you can pass the name of category
{props.animal.map(({id, name}, index) =>
<div key={id}><div>{name}</div><div onClick={() => props.deleteAnimal(index,this.props.category)}>Delete animal</div></div>
)
}
In my React state, I want to reorder an array of 3 objects by always putting the selected one in the middle while keeping the others in ascending order.
Right now, I'm using an order property in each object to keep track of the order, but this might not be the best approach.
For example :
this.state = {
selected: 'item1',
items: [
{
id: 'item1',
order: 2
},
{
id: 'item2'
order: 1
},
{
id: 'item3'
order: 3
}
]
}
Resulting array : [item2, item1, item3]
Now, let's imagine that a user selects item2. I will update the selected state property accordingly, but how can I update the items property to end up with a result like this:
this.state = {
selected: 'item2',
items: [
{
id: 'item1',
order: 1
},
{
id: 'item2'
order: 2
},
{
id: 'item3'
order: 3
}
]
}
Resulting array : [item1, item2, item3]
How would you do it? I have seen some lodash utility functions that could help but I would like to accomplish this in vanilla JavaScript.
You could do something crude like this:
// Create a local shallow copy of the state
var items = this.state.items.slice()
// Find the index of the selected item within the current items array.
var selectedItemName = this.state.selected;
function isSelectedItem(element, index, array) {
return element.id === selectedItemName;
};
var selectedIdx = items.findIndex(isSelectedItem);
// Extract that item
var selectedItem = items[selectedIdx];
// Delete the item from the items array
items.splice(selectedIdx, 1);
// Sort the items that are left over
items.sort(function(a, b) {
return a.id < b.id ? -1 : 1;
});
// Insert the selected item back into the array
items.splice(1, 0, selectedItem);
// Set the state to the new array
this.setState({items: items});
This assumes the size of the items array is always 3!
I'm gonna be lazy and just outline the steps you need to take.
Pop the selected item out of the starting array
Push the first item of the starting array into a new array
Push the selected item into the new array
Push the last item of the starting array into the new array
Set your state to use the new array
You can do something like:
NOTE: This works assuming there would three items in the array. However, if there are more we just need to specify the index position in the insert function.
this.state = {
selected: 'item1',
items: [
{
id: 'item1',
order: 1
},
{
id: 'item2',
order: 2
},
{
id: 'item3',
order: 3
}
]
};
// To avoid mutation.
const insert = (list, index, newListItem) => [
...list.slice(0, index), // part of array before index arg
newListItem,
...list.slice(index) // part of array after index arg
];
// Get selected item object.
const selectedValue = value => this.state.items.reduce((res, val) => {
if (val.id === selectedValue) {
res = val;
}
return res;
}, {});
const filtered = this.state.items.filter(i => i.id !== state.selected);
const result = insert(filtered, 1, selectedValue(this.state.selected));
We can get rid of the extra reduce if instead of storing id against selected you store either the index of the item or the whole object.
Of course we need to use this.setState({ items: result }). This solution would also ensure we are not mutating the original state array at any point.
I put together a fully working example what can be extended on so you can experiment with different ways to achieve your intended use-case.
In this case I created a button component and rendered three of them to provide a means of changing the selected state.
Important things to remember, always use the setState() function for updating React Class state. Also, always work on state arrays and objects with a cloned variable as you'll want to update the whole object/array at once. Don't modify attributes of pointer variables pointing to state objects or arrays.
It is very possible to add bugs to your code by referencing state objects/arrays and then changing their properties (accidentally or not) by modifying the pointer referencing the object. You will lose all guarantees on how the state will update, and comparing prevState or nextState with this.state may not work as intended.
/**
* #desc Sub-component that renders a button
* #returns {HTML} Button
*/
class ChangeStateButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = ({
//any needed state here
});
}
handleClick(e) {
//calls parent method with the clicked button element and click state
this.props.click(e.nativeEvent.toElement.id);
}
render() {
return (
<button
id = {this.props.id}
name = {this.props.name}
className = {this.props.className}
onClick = {this.handleClick} >
Reorder to {this.props.id}!
</button>
);
}
}
/**
* #desc Creates button components to control items order in state
* #returns {HTML} Bound buttons
*/
class ReorderArrayExample extends React.Component {
constructor(props) {
super(props);
this.reorderItems = this.reorderItems.bind(this);
this.state = ({
selected: 'item1',
//added to give option of where selected will insert
selectedIndexChoice: 1,
items: [
{
id: 'item1',
order: 2
},
{
id: 'item2',
order: 1
},
{
id: 'item3',
order: 3
}
]
});
}
reorderItems(selected) {
const {items, selectedIndexChoice} = this.state,
selectedObjectIndex = items.findIndex(el => el.id === selected);
let orderedItems = items.filter(el => el.id !== selected);
//You could make a faster reorder algo. This shows a working method.
orderedItems.sort((a,b) => { return a.order - b.order })
.splice(selectedIndexChoice, 0, items[selectedObjectIndex]);
//always update state with setState function.
this.setState({ selected, items: orderedItems });
//logging results to show that this is working
console.log('selected: ', selected);
console.log('Ordered Items: ', JSON.stringify(orderedItems));
}
render() {
//buttons added to show functionality
return (
<div>
<ChangeStateButton
id='item1'
name='state-button-1'
className='state-button'
click={this.reorderItems} />
<ChangeStateButton
id='item2'
name='state-button-2'
className='state-button'
click={this.reorderItems} />
<ChangeStateButton
id='item3'
name='state-button-2'
className='state-button'
click={this.reorderItems} />
</div>
);
}
}
/**
* #desc React Class renders full page. Would have more components in a real app.
* #returns {HTML} full app
*/
class App extends React.Component {
render() {
return (
<div className='pg'>
<ReorderArrayExample />
</div>
);
}
}
/**
* Render App to DOM
*/
/**
* #desc ReactDOM renders app to HTML root node
* #returns {DOM} full page
*/
ReactDOM.render(
<App/>, document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
<!-- This div's content will be managed by React. -->
</div>
background:
I have a redux store with an Immutable.Map()s, something like this:
//items
itemID_1: {
prop1: 'a',
prop2: 'b',
prop3: 'c',
},
itemID_2: {
// etc etc
}
The store is assigned to props via mapStateToProps in a component, at which point I filter out any items where prop2 != 'a', and convert the values into a List:
const mapStateToProps = (state) => ({
items: state.items
.filter((item) => {return (item.get('prop2') === 'a')}
.values()
.toList(),
})
All of these items are then rendered into a grid of <Card> components:
render() {
return (
<div>
{this.props.items.map((item, key) => <Card key={key} content={item} />)}
</div>
)
}
question:
I want to add the ability for the user select their own filters.
My list of properties is pretty long, and will be volatile, so I don't want to write a filter function for every one.
How can I add the ability for the user to compose combinations of filters?
I considered maintaining an array of filter config objects, e.g.:
[
{
propToFilter: 'prop1',
params: {
max: 10,
min: 5
}
},
{
propToFilter: 'prop2',
params: {
is: ['B', 'C']
}
},
]
The .filter() function would then loop through this array and check each item in the List().
This sort of gets at what I want, in that it effectively classifies each prop into categorical or continuous.
But, my data isn't as flat as the example implies, so something as simple as items.get(filters[0].propToFilter) wouldn't always work. This means I'd have to a) build a list of paths to find each prop, or b) somehow search the keys of my store for each one (which i suspect isn't super performant).
Is this solution somehow workable? Or is there a more efficient way?