Why is may array returning empty? - javascript

Hi I am new to react and am trying to use onSelect to return on array of items that are associated with that name. I am using the dot filter method to filter an array so that only items with the same key as the name that is selected appear. However my array returns empty.
class HorizantScroller extends React.Component {
state = {
selected: 'Brands',
statelist: [
{name: "Brands",
items: ["1", "2", "3"]
},
{name: "Films",
items: ["f1", "f2", "f3"]
},
{name: "Holiday Destination",
items: ["f1", "f2", "f3"]
}
]
};
onSelect = key => {
this.setState({ selected: key });
const myList = this.state.statelist;
const myItemDetails = myList.filter(items=>items.key === key);
console.log(myItemDetails)
}
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(this.state.statelist, selected);
const {statelist} = this.state;
return (
<div className="HorizantScroller">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
<Items Items={Items[selected]}/>
</div>
);
}
}
export default HorizantScroller;

According to your data-structure, you need to use use item.name to check for the selectedKey
myList.filter(items=>items.name === key);
Note: you must make sure that you are not updating stateList state after filtering the array, otherwise your your state will loose the data
Instead you must use another state variable to store filtered list or apply the filter while render instead of storing the filtered value in state

Related

I'm trying to add to an array of objects that is broken into two inputs in React

So I have an array of objects where the keys are 'cost' and 'service' called estimate. You can add to the array by clicking 'Add' which adds a new index (i) to the array. The issue is on the first cycle I get a good array of {'cost': 2500, 'service': "commercial cleaning"} (imgSet-1) but when I add another item it completely erases the array and sets only one of the nested objects key and value. (imgSet-2). This is the outcome I'm looking for once the state has been saved (imgSet-3) I have tried going with #RubenSmn approach but then I receive this error. (imgSet-4)
imgSet-1 *********
Adding an initial service
Outcome of the initial service addition
imgSet-2 *********
Adding the second service
Outcome of the second service addition
imgSet-3 *********
imgSet-4 *********
Below is the code for the part of the page where you can add services and the output of the text inputs.
const [estimate, setEstimate] = useState([]);
{[...Array(numServices)].map((e, i) => {
return (
<div key={i} className="flex justify-between">
<div>
<NumericTextBoxComponent
format="c2"
name={`cost-${i}`}
value={estimate?.items?.["cost"]?.[i]}
change={(e) =>
setEstimate({ ...estimate, items: [{...estimate?.items?.[i],cost: e?.value}]})
}
placeholder='Price'
floatLabelType="Auto"
data-msg-containerid="errorForCost"
/>
</div>
<div>
<DropDownListComponent
showClearButton
fields={{ value: "id", text: "service" }}
name={`service-${i}`}
value={estimate?.items?.["service"]?.[i]}
change={(e) =>
setEstimate({ ...estimate, items: [{...estimate?.items?.[i],service: e?.value}]})
}
id={`service-${i}`}
floatLabelType="Auto"
data-name={`service-${i}`}
dataSource={estimateData?.services}
placeholder="Service"
data-msg-containerid="errorForLead"
></DropDownListComponent>
<div id="errorForLead" />
</div>
</div>
);
})}
</form>
<button onClick={() => setNumServices(numServices + 1)}>Add</button>
I have tried multiple variations of spread operators but I can't seem to get it to work. My expected result would be:
estimate:{
items: [
{'cost': 2500, 'service': 'Commercial Clean'},
{'cost': 500, 'service': 'Bathroom Clean'},
{'cost': 180, 'service': 'Apartment Clean'},
{etc.}
]
}
The initial state is an array which is not the object you're setting in the change handlers. You can have an initial state like this.
const [estimate, setEstimate] = useState({ items: [] });
You're not adding back the old items of the state when you're setting the new state.
setEstimate({
...estimate,
items: [{ ...estimate?.items?.[i], cost: e?.value }],
// should be something like
// items: [...estimate.items, { ...estimate.items?.[i], cost: e?.value }],
});
But you can't do that since it will create a new object in your items array every time you change a value.
I made this dynamic handleChange function which you can use for you state changes. The first if statement is to check if the itemIndex is already in the items array. If not, create a new item with the propertyName and the value
const handleChange = (e, itemIndex, propertyName) => {
const newValue = e?.value;
setEstimate((prevEstimate) => {
if (prevEstimate.items.length <= itemIndex) {
const newItem = { [propertyName]: newValue };
return {
...prevEstimate,
items: [...prevEstimate.items, newItem]
};
}
// loop over old items
const newItems = [...prevEstimate.items].map((item, idx) => {
// if index' are not the same just return the old item
if (idx !== itemIndex) return item;
// else return the item with the new service
return { ...item, [propertyName]: newValue };
});
return {
...prevEstimate,
items: newItems,
};
});
};
For the Service dropdown, you can do the same for the Cost just change the property name
<DropDownListComponent
...
value={estimate.items[i]?.service}
change={(e) => handleChange(e, i, "service")}
...
></DropDownListComponent>
See here a simplified live version

Changing state of object nested in array of objects

As stated in the title I'm trying to change the state of an object nested in an array of objects. I just can not get this to work. I've added a sample of what I'm trying to do in a codesandbox.
I'm using a Material-UI component call ToggleButton (link to demo ToggleButton). I want to change the state to toggle the buttons in the group. I was able to get this working for a create function but can not get it working for my update function.
Trying to change the values of the object in the array is just not working for me. Below are some things I've tried to no success. I want to change the IsTrue: so I can toggle the button to display the users selection.
setAppetizer(true);
setRecipeObjectState({
...recipeObjectState,
Category: [
...recipeObjectState.Category,
{
IsTrue: appetizer,
category: "Appetizer",
},
],
});
This just adds more buttons to my button group.
setAppetizer(true);
setRecipeObjectState((prevState) => ({
...prevState,
Category: [
{
IsTrue: appetizer,
category: "Appetizer",
},
],
}));
I'm just lost now at this point. I just want to also state that this is my first React project that is not just a sandbox for learning the framework and I'm also a jr. developer trying to learn. I have search stackoverflow and nothing has helped me out. I hope I have included enough information for someone to help out.
Your state and app appear to be very convoluted, but the general idea when updating nested array state is to shallowly copy the state at each level where an update is being made. Use array::map to map the Category property to a new array object reference and when the category matches toggle the IsTrue "selected" property.
setRecipeObjectState((prevState) => ({
...prevState,
Category: prevState.Category.map((category) =>
category.category === newCategory // <-- newCategory value from toggled button
? {
...category,
IsTrue: !category.IsTrue
}
: category
)
}));
Since your "selected" calculation is selected={Boolean(item.IsTrue)} you'll want to ensure your IsTrue element values are actually togglable, i.e. just store the boolean value right in the array.
const recipeObject = {
AuthorId: authorId,
BookAuthor: bookAuthor,
BookTitle: bookTitle,
Calories: parseInt(calories),
Category: [
{
IsTrue: false,
category: "Appetizer"
},
{
IsTrue: false,
category: "Breakfast"
},
{
IsTrue: false,
category: "Soup / Salad"
},
{
IsTrue: false,
category: "Vegetarian"
},
{
IsTrue: true,
category: "Meat (Beef, Pork, Chicken)"
},
{
IsTrue: false,
category: "Fish"
},
{
IsTrue: false,
category: "Dessert"
}
],
Description: description,
DurationInMinCook: parseInt(durationInMinCook),
DurationInMinPrep: parseInt(durationInMinPrep),
ImageUrl: imageUrl,
Ingredients: addedIngredients, // array
Instructions: addedInstructions, // array
IsRecipe: true,
Likes: 0,
RecipeId: selectedRecipeId,
ServingSize: parseInt(servingSize),
Title: title,
YouTubeUrl: youTubeUrl
};
You mutating the same reference, you need to render a copy or the component won't render (shallow comparison):
Try updating state like this.
const oldState = recipeObjectState;
oldState.Category = [
{
IsTrue: appetizer,
category: "Appetizer"
}
];
setRecipeObjectState(oldState);
I didn't try your component because it's huge.
Updating a single object property in an array of objects seems to be a very common use-case. Here is generally how you do that. Suppose id is a unique identifier of each object and that we want to toggle selected:
import React, { useState } from "react";
import "./styles.css";
import faker from "faker";
const array = [];
for (let id = 1; id < 10; id++) {
array.push({
id,
name: faker.name.firstName(),
age: faker.random.number(100),
selected: false
});
}
export default function App() {
const [list, setList] = useState(array);
const onClick = (id) => (event) => {
setList((list) =>
list.map((item) =>
item.id === id ? { ...item, selected: !item.selected } : item
)
);
};
return (
<div className="App">
Click on a item:
<ul>
{list.map(({ id, name, age, selected }) => (
<li key={id} onClick={onClick(id)} className="item">
{name} {age}
{selected ? " ✅" : null}
</li>
))}
</ul>
</div>
);
}
https://codesandbox.io/s/xenodochial-river-9sq6g?file=/src/App.js:0-825

React-Select if value exists show value if not show placeholder text

I'm using this component here: https://react-select.com/home
What I'm trying to accomplish is on page load if my array contains a value in my Assigned element then I want to display that by default in my react-select box if not then I want my placeholder Assign to to show.
Here is how my select looks:
<Select
value={this.state.Product[0].Assigned ? this.state.Product[0].Assigned : selectedAssigned}
onChange={this.handleChangeAssigned}
options={this.state.AssignedList}
placeholder={<div>Assign to:</div>}
/>
In my Product[0].Assigned there is currently a value, however the dropdown still has the placeholder Assign To. I tried changing value to value={this.state.Product[0].Assigned} but still no luck.
Here is my change handle:
handleChangeAssigned = selectedAssigned => {
this.setState(
{ selectedAssigned },
() => console.log(`Option selected:`, this.state.selectedAssigned)
);
};
It seems like you are storing the same data in multiple places. You are checking if you have an assignment by looking at this.state.Product[0].Assigned. But when you select an assignment you are not updating that property. Instead you are updating a completely separate property this.state.selectedAssigned. this.state.Product[0].Assigned never changes so if you see a placeholder at first then you will always see a placeholder.
The value that you set on the Select needs to be the same as the value that you update.
import React from "react";
import Select from "react-select";
interface Option {
label: string;
value: string;
}
interface State {
Product: any[];
AssignedList: Option[];
selectedAssigned: Option | null;
}
export default class MyComponent extends React.Component<{}, State> {
state: State = {
Product: [],
AssignedList: [
{ label: "a", value: "a" },
{ label: "b", value: "b" },
{ label: "c", value: "c" }
],
selectedAssigned: null
};
handleChangeAssigned = (selectedAssigned: Option | null) => {
this.setState({ selectedAssigned }, () =>
console.log(`Option selected:`, this.state.selectedAssigned)
);
};
render() {
return (
<Select
value={this.state.selectedAssigned}
onChange={this.handleChangeAssigned}
options={this.state.AssignedList}
placeholder={<div>Assign to:</div>}
/>
);
}
}
Code Sandbox Link

how to multiple spread operator inside map

class TodoApp extends React.Component {
state = {
item: [{fruit: 'apple'}]
}
handle=(name, value, num)=>{
console.log(name, value, num)
this.setState(({item}) => ({
item: item.map((cv,i)=>( {...cv, [name]: {[num] :value}})
)
}), ()=>{ console.log(this.state.item)})
}
render() {
return (
<div>
<Input name={'items'} onChange={this.handle}/>
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
A working example: https://jsfiddle.net/dapyb0ef/
item: item.map((cv,i)=>( {...cv, [name]: {[num] :value}})
each time I type in a field, it will overwrite the previous one. so it prints only one item in items e.g.
fruit: "apple"
items: {2: "123"}
What I am achieving is to keep all items when typing in each input field
fruit: "apple"
items: {0: "typed string..."}, {1: "typed information"}, {2: "typed information"}
So my question is item: item.map((cv,i)=>( {...cv, [name]: {[num] :value}})
How do i store [num] and keep all information. Thanks
You simply need to spread the inner items as well like
this.setState(({item}) => ({
item: item.map((cv,i)=>( {...cv, [name]: {...cv[name], [num] :value}})
)
Working demo
what you're doing is spreading cv then overriding his items property completely, you forgot to spread the existing cv.items content
{item: item.map((cv,i)=>( {...cv, [name]: {...cv[name], [num] :value}})}

How to set multiple dropdown values to each dynamic element Semantic UI React

I'm having trouble figuring out how to set a dynamic dropdown component with multiple-value selections to each rendered element in a feature I'm working on. I think I'm really close but ultimately need a bit of guidance.
Here's the component:
import React, { Component } from 'react'
import { List, Dropdown, Label } from 'semantic-ui-react'
const directions = [
{key: "0.0", text: "0.0", value: "0.0"},
{key: "22.5", text: "22.5", value: "22.5"},
{key: "45.0", text: "45.0", value: "45.0"},
{key: "67.5", text: "67.5", value: "67.5"},
{key: "90.0", text: "90.0", value: "90.0"}
]
const channels = [
{ch: 65, callsign: "TEST1"},
{ch: 49, callsign: "TEST2"},
{ch: 29, callsign: "TEST3"}
]
export default class DirectionalSelection extends Component {
constructor(props) {
super(props)
this.state = {
channels,
directions,
currentValues: {}
}
}
handleDropdownChange = (e, index, { value }) => {
this.setState(({ currentValues }) => {
currentValues[index] = value
return currentValues
})
}
handleDirAddition = (e, index, { value }) => {
this.setState(({ directions }) => {
directions[index] = [{ text: value, value }, ...this.state.directions]
return directions
})
}
render() {
const { channels, currentValues, directions } = this.state
return (
<div>
<List>
{channels.map((el, index) => (
<List.Item key={index}>
<Label>{el.ch}</Label>
<Dropdown
search
multiple
selection
scrolling
allowAdditions
options={directions}
value={currentValues[index]}
placeholder='Choose directions'
onAddItem={this.handleDirAddition.bind(this, index)}
onChange={this.handleDropdownChange.bind(this, index)}
/>
</List.Item>
))}
</List>
</div>
)
}
}
Right now every time I select dropdown values on any channel, currentValues returns as [object Object]: ["22.5", "45.0"]. I want to set the ch key in channels as the key and the dropdown values array as the value and append them to currentValues.
I hope I've clarified the question enough to understand. Here is a link to Semantic-UI-React docs with the original component I'm using: https://react.semantic-ui.com/modules/dropdown#dropdown-example-multiple-allow-additions. Thanks for the help!
I figured it out! It was so simple, just had to switch the params in handleDropdownChange = (e, index, { value }) to handleDropdownChange = (index, e, { value }). It was setting the event function as the object key.

Categories

Resources