Removing a specific element of an array based on click - React/JS - javascript

I have a dropdown which controls state: [selected, setSelected].
This state is created into a flat array and then mapped into icons.
When the icon is selected I would like it to then remove that target specifically from the array. I assume this is just a simple (e) => onClick but am unsure how to write it.
Please see how my state is created:
const onPerksClick = (val) => {
console.log({ val });
if (!selected.includes(val)) {
setSelected((prev) => [...prev, val]);
}
};
i then map this into icons and different things.
I would like to include this so when it's clicked it will remove it from the array.
<BenefitsContainer>
{selected.map((perkTitle) => (
<SelectionBoxesChoiceOption >
{perkTitle=="Equity" &&
<BalanceIcon style={{ color: "white"}}/>
}
</SelectionBoxesChoiceOption>
))}
</BenefitsContainer>

Related

How do i add elements from a map, inside another map?

I'm making a json map, and sending it to another component, to assemble several Cards at the same time:
{search.map((element, index) => {
return(
<div key={index}>
<CardItem key={index} data={element}></CardItem>
</div>
)
})}
And then I made an entry that when I put the name of the card title, it only shows the cards with those titles. Basically a search filter in React
const [value, setValue] = useState('')
const searchLowerCase = value.toLowerCase()
const search = job.filter(element => element.company.toLowerCase().includes(searchLowerCase) || element.role.toLowerCase().includes(searchLowerCase) || element.level.toLowerCase().includes(searchLowerCase) )
const handleInput = (e) => {
setValue(e.target.value)
}
The problem is that inside my json, there is an array that brings other elements, that is, I need to make another map. I'm making this other map directly in the JSX of the cards component
{data.languages.map((element, index) => {
return(
{element}
)
})}
So I need to make my input also look for these other elements that are inside the map that is being performed in the other component. For when I write the name of these elements in the input, show only the card on the screen that has these selections. Any tips would be greatly appreciated
I'm thinking of some way to add this map made in my other component, inside this map that is doing the searches, so when I type inside the input, all the elements that are written, regardless of which map it is, are shown on the screen

Why is the first element getting removed too?

So I am new to javascript and I tried making a todo list. This works well with adding elements. The issue is when I am removing some item, the first one gets removed too, why is it so? I know I am missing a small thing and this may be really basic but I am not able to find out what that is.
const App1 = () => {
const [item, updatedItem]=useState('');
const [Items, setItems]=useState([]);
function inputEvent(event) {
updatedItem(event.target.value);
}
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return[
...prev,
item
]
});
updatedItem('');
}
let key=0;
return(<>
<div className='back'>
<div className='list'>
<header>ToDo List</header>
<form onSubmit={addItem}>
<input type='text' placeholder='Add an item' value={item} onChange={inputEvent}/>
<button type='submit'>+</button>
</form>
<div className='items'>
<ol>
{Items.map((val) => <li><button id={key++} onClick={(event) => {
setItems((Items) => {
return Items.filter((val, index) => {
if(index!==Number(event.target.id)){
return index;
}
}
);
});
key=0;
}}>x</button>{val}</li>)}
</ol>
</div>
</div>
</div>
</>);
You return the index in your filter, expecting this to always be true, yet 0 (the index of the first element) is a falsy value.
Try this instead:
return Items.filter((_val, index) => index !== Number(event.target.id));
Some unrelated code-quality notes:
In React, you should always set a key prop on each element when looping through them, rather than id.
map has a second argument, index, which it passes into the callback --- you don't have to keep track of this yourself with e.g. key++ etc.
If you use map's index parameter, then you can pass that directly into your filter rather than using Number(event.target.id), which is not very idiomatic in React.
If you don't use an argument of a callback, it's a good idea to prefix it with a _ (like I've done with _val here), to make it explicit that you're not using it.
Your filter callback should return a flag. index is a number. When treated as a flag, 0 is false (more on MDN). Instead:
return Items.filter((val, index) => index !== Number(event.target.id));
However, your code is returning an array of li elements without setting key on them (see: keys), which React needs in order to manage that list properly (you should be seeing a warning about it in devtools if you're using the development version of the libs, which is best in development). You can't use the mechanism you're using now for keys when doing that, it will not work reliably (see this article linked by the React documentation). Instead, assign each Todo item a unique ID when you create it that doesn't change, and use that as the key (and as the value to look for when removing the item):
// Outside the component:
let lastId = 0;
// Inside the component:
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return [
...prev,
{text: item, id: ++lastId}
];
});
updatedItem("");
};
// Add a remove function:
const removeItem = ({currentTarget}) => {
const id = +currentTarget.getAttribute("data-id"); // Get the ID, convert string to number
setItems(items => items.filter(item => item.id !== id));
};
// When rendering:
{Items.map((item) => <li key={item.id}><button data-id={item.id} onClick={removeItem}>x</button>{item.text}</li>)}
In some cases it may be useful to use useCallback to memoize removeItem to avoid unnecessary rendering, but often that's overkill.

Display the values from select properly

I have an app where i use a select tag to add values in a table. With this select i can select just one value and to add it in the table.
Some of the code of select input:
const searchPlayer = selectedItems => {
selectedItems = selectedItems.map(name => name.toLowerCase());
let arrayOfMatchedObjects = team.filter(object => {
return selectedItems.some(selectedItem =>
JSON.stringify(object)
.toString()
.toLowerCase()
.includes(selectedItem)
);
});
return arrayOfMatchedObjects;
};
The issue is that it doesn't work properly, because when i select a name but don't save it, and after that i change my mind and i select another name, in the table will be added both names, but i can't understand why, because in this way i add 2 names just clicking once on the button. Who knows how to solve this? link to app: https://codesandbox.io/s/serene-hamilton-go4m3
From your current code, you concate the selection in useEffect(), which causes the multi-selected items to been passed to the table.
Remove the concate method would fit your demand.
Update
Replace this
const addPlayer = () => {
setnewPlayer(searchPlayer(savedPlayer));
};
useEffect(() => {
setSavedPlayer(
Array.from(new Set(savedPlayer.concat(selectedItems)).values())
);
}, [selectedItems]);
to
const addPlayer = () => {
const data = Array.from(new Set(savedPlayer.concat(selectedItems)).values());
setnewPlayer(searchPlayer(data));
setSavedPlayer(data);
};

How can you create a Tree display using dynamic data from an API in React?

So, I've got a bit of a doozy here. I've looked into trees and the like in react, and I'm fairly confident I can implement one, given the right data structure. The problem I'm running into is that I'm getting my data from an API that doesn't, at least natively, have the structure for a tree, so I'm trying to create that structure on the fly.
This is what the data I'm getting back from the API looks like:
const category = //could have children
{
"object_name":"B2B",
"data_provider_key":"bluekai",
"object_key": "bluekai-31",
"object_type":"category",
};
const segment = //will only be a child
{
"object_name":"B2B > Role/Title > Admin Exec",
"data_provider_key":"bluekai",
"object_key": "bluekai-1145",
"object_type":"segment",
"cpm_cost":2.500
};
And this is the logic that I'm using to try and manipulate the data from the API to add children/create parents, etc.
const asyncView = async function (segTree: string | undefined) {
const categoryDataCall = api.getBeeswaxSegmentView(categoryBody);
const segmentDataCall = api.getBeeswaxSegmentView(segmentBody);
const data = await Promise.all([categoryDataCall, segmentDataCall]);
const parent = categoryData.find( (el: any) => el.object_key === segTree);
const categories = data[0].payload;
if (categories.length >= 1) {
for (let i = 0; i < categories.length; i++) {
categories[i].children = [];
}
}
parent.children = categories.concat(data[1].payload);
setCategoryData(parent.children);
setParent(parent);
}
asyncView(e.currentTarget.dataset.segment_tree);
}
return (
<>
<div>PARENT: {parent.object_name}</div>
{categoryData.length === 0
? <div>No category data</div>
: categoryData.map((e: any) => {
if (e.object_type === 'segment') {
return (
<div data-segment_tree={`${e.object_key || "NULL"}`}
data-provider_key={`${e.data_provider_key}`}
>
{`Name: ${e.object_name} (${e.object_key}, $${parseFloat(e.cpm_cost).toFixed(2)} CPM)`}
</div>
)
}
return (
<div data-segment_tree={`${e.object_key || "NULL"}`}
data-provider_key={`${e.data_provider_key}`}
onClick={getCategoryAndSegmentData}
>
{`Name: ${e.data_provider_name || e.object_name}`}
</div>
)
})
}
</>
);
}
I haven't implemented the Tree part yet, but that's because I am fairly confident I'm not creating the relations between elements correctly in my logic/the logic breaks if there are multiple 'trees'/categories on a page (which there will be.)
Sorry if this is a bit much, but any help or just ideas on dynamically modifying the data from the API to fit the tree structure of child/parent relationships would be appreciated!
Edit in response to Ray Hatfield:
What's the relationship between a category and a segment?
Segments will always be children of Categories, and will never have children of their own. Categories can have other categories as children.
How do you establish which category a segment belongs to?
The object_key property from the Category object gets passed to the API call(s) (two calls are made: one for segments, and one for categories). This is the only relation between segments and categories - nothing else in the return data ties them together.
What is e?
I assume you mean in the e.currentTarget.dataset.segment_tree line.
e is the event object, which I'm using to create the queries and firing them off on click events. I'm storing the object_key in a data-attribute in the HTML, and then passing it to a handler to generate the categoryBody and segmentBody used in the asyncView() function.
For some reason I have to explicitly pass the e.currentTarget.dataset.segment_tree as an argument to the async function even though they're in the same scope, but all it's doing is allowing me to find the Category that was clicked in the existing array of data in state.
What is categoryData?
categoryData is the array of values ( that is currently in state. So, each time I hit the API I update category data to re-render everything.
Effectively, I'm finding the parent (category that was clicked) firing off the API calls to get all the subcategories/segments associated with the clicked categories object_key, and then adding a children prop to any incoming categories, and then setting the children of the last clicked element equal to the returned segments + categories, and then rendering.
I put together this working demo on jsfiddle. Here are the highlights:
The Core Idea
The core idea is a Category component that's responsible for loading and rendering its own segments and subcategories. The subcategories get rendered using the same Category component, resulting in a recursive tree structure.
The Category Component
const Category = ({item}) => {
const [data, setData] = React.useState();
const onClick = data
? () => setData(null) // discard data (collapse) on subsequent click
: () => load(item.object_key).then(setData);
return (
<div className="category">
<div
className={`category-name ${data ? 'open' : ''}`}
onClick={onClick}
>
{item.object_name}
</div>
{data && (
<ul>
{ data.map((child, i) => (
<li key={i}><Node item={child}/></li>
))}
</ul>
)}
</div>
)
}
This component takes a single item prop representing the category. The component expects item to have object_key and object_name fields, like the category object in your example.
Initially the component has no information other than what's in the item, so it renders the category's name with an onClick handler that makes API calls to fetch the category's children and then stores the result in the component's state:
const [data, setData] = React.useState();
const onClick = () => load(item.object_key).then(setData);
On the subsequent render the Category component renders its children (segments and subcategories) in addition to the category name. Subcategories are rendered using the same Category component, resulting in a recursive tree structure.
The Segment Component
const Segment = ({item: {object_name}}) => (
<div className="segment">{object_name}</div>
);
Simple component for rendering segments. Just returns the segment name here, but you could of course expand it to do whatever you need it to do.
The Node Component
const Node = ({item}) => {
const Cmp = item.object_type === 'category' ? Category : Segment;
return <Cmp item={item} />;
};
Convenience component for rendering a <Segment /> or <Category /> for the given item according to its type.
The rest of the example code is just hand waving to simulate the API calls and generate mock data.
load function
const load = async (parentKey) => {
const [categories, segments] = await Promise.all([
mockApiRequest('category'),
mockApiRequest('segment')
]);
return [
...categories,
...segments
];
}
Given a category's object_key, this makes the api calls to get the segments and subcategories, merges and returns the results as a single array.
mockApiRequest
const mockApiRequest = (type) => (
new Promise((resolve) => {
setTimeout(() => resolve(fakeData(type)), 200);
})
)
Simulates the API request. Waits 200ms before resolving with mock data.
fakeData
// generate mock response data
const fakeData = (type) => {
// copy the list of names
const n = [...names];
// plucks a random name from the list
const getName = () => (
n.splice(Math.floor(Math.random() * n.length), 1)[0]
);
// generate and return an array of data
return Array.from(
{length: Math.floor(Math.random() * 5) + 1},
(_, i) => ({
...samples[type],
object_name: getName()
})
)
};
Generates mock category or segment data by copying the sample and choosing a random name.

How can I give a key in JSX the value of a variable depending on conditions

I'm learning React by implementing a front-end interface for the note app API that I created. I have succeeded in having a list of all the note titles in my database appear. I want to be able to click on a title and have the note expand into the text of the note. The easiest way I've found for this is to give the "key" attribute of the 'li' as a variable and to also declare the same variable in the JSX { } object because they have the same name.
I've been looking for an answer for this for a few days and have been unable to find this exact problem. You can put a variable in a normal JSX expression but I need to do it on the 'li' which means technically in the HTML.
Here's some code to understand what I'm saying.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [open, setOpen] = useState(false);
useEffect(() => {
//AXIOS CALL
setTitles(response.data[0]);
});
}, []);
//^^^^^add the array there to stop the response.data from repeating WAY TOO MANY TIMES
let listTitles = titles.map(titles => (
<li className="noteTitles" key={titles.title}>
{titles.title}
</li>
));
let showText = titles.map(titles => (
<li className="openText" key= {titles.text_entry}>
{titles.text_entry}
</li>
))
let openNote = () => {
setOpen(open => !open);
if (open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{showText}
</ul>
</div>
);
}
if (!open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listTitles}
</ul>
</div>
);
}
};
return { openNote };
};
export default NoteData;
That is the code I currently have. Here's showing a more simplified version of the openNote function that maybe makes more sense and shows what I'm trying to do:
VariableHere = "";
let openNote = () => {
setOpen(open => !open);
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
};
let listNotes = titles.map(titles => (
<li className="noteTitles" key={VariableHere}>
{VariableHere}
</li>
));
return (
<div>
<ul onClick={openNote}>
{listNotes}
</ul>
</div>
);
On click of each element there should be a switch of the key elements so if the element is 'open' the key variable and given variable in the JSX object should be mapped to titles.text_entry and on '(!open)' the key and JSX should be mapped to titles.title.
first of all, you're using a ternary in a weird way:
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
Ternaries are meant to be expressions whose value is conditional, but you're using it like a shorthand if/else. Try something like
VariableHere = open ? titles.text_entry : titles.title;
which is both shorter and more readable.
Second of all, keys in an array of elements are meant to help React determine which elements to update, if an item represents the same object, its key shouldn't change. In this case, regardless of what you're displaying, an item in the array represents the same note. Always using the title as the key should be fine provided items can't have the same title. If they can, use some sort of unique ID instead. If the order of the items doesn't change throughout the life of the component, using the array index as the key is fine.
Lastly, what you seem to want to do is called "conditional rendering". There are many ways to achieve this in react, one such way is to use the pre-cited ternary operator. Here is a minimal working example:
const listNotes = titles.map(note => (
<li className="noteTitles" key={note.title}>
{open ? note.title : note.text_entry}
</li>
));
const openNote = () => {
setOpen(!open);
}
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listNotes}
</ul>
</div>
)
You could also use a ternary in the key expression, but as I talked about above, it's not a good idea to do so.
Given your data-structure, I think you can simplify your code a bit. There is no need to create separate arrays for titles and contents. It sounds like you just want to expand and collapse a note when it is selected.
Here is a really simplified version on how you an do this. I'll use a sample data-set since we don't have access to your API.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [currentNote, setCurrentNote] = useState({});
useEffect(() => {
//AXIOS CALL
// setTitles(response.data[0]);
let data = [
{ id: 1, title: "a", text_entry: "what" },
{ id: 2, title: "b", text_entry: "is" },
{ id: 3, title: "c", text_entry: "up?" }
];
setTitles(data);
}, []);
const handleClick = noteId => {
let selectedTitle = titles.find(title => title.id == noteId);
//"collapse" if already selected
if (noteId === currentNote.id) {
setCurrentNote({});
} else {
setCurrentNote(selectedTitle);
}
};
let listTitles = titles.map(title => (
<li
className="noteTitles"
key={title.title}
onClick={() => handleClick(title.id)}
>
{title.title}
{title.id === currentNote.id && <div>{title.text_entry}</div>}
</li>
));
return (
<div>
Click on link item
<ul>{listTitles}</ul>
</div>
);
};
See working sandbox: https://codesandbox.io/s/old-silence-366ne
The main updates:
You don't need to have an "open" state. To be more succinct and
accurate, you should have a currentNote state instead, which is
set when clicking on a list item.
Have your handleClick function accept a noteId as an argument.
Then use that noteId to find the corresponding note in your titles
state. Set that found note as the currentNote. If the selected
note was already the currentNote, simply set currentNote to an
empty object {}, thus creating our expanding/collapsing effect.
In the JSX, after the title, use a ternary operator to conditionally
display the currentNote. If the note being mapped matches the
currentNote, then you would display a div containing the
text_entry.

Categories