React Hooks Remove item from array - reactjs

In my react hooks component I am rendering data from an array of objects.
const lineInfo = [
{
id: '001',
line: 's-1125026',
arrival: '7:30',
departure: '8:00',
authorization: 'User 1',
},
{
id: '002',
line: 's-1125027',
arrival: '8:01',
departure: '8:50',
authorization: 'User 1',
},
In the .map() I'm returning data using this data:
<div>
{lineInfo.map((line) => (
<Row key={`line_${line.id}`}>
// remaining code removed for length
The list returns fine, so now I am trying to remove a row from the list.
Remove func
const [list, setList] = useState(lineInfo);
function handleRemove(id) {
console.log('id' id);
const newList = list.filter((line) => line.id !== id);
setList(newList);
}
Remove Button
<Button
className={style.close_button}
onClick={() => handleRemove(line.id)}
>
<img src={close} alt="trash" />
</Button>
</Row>
The problem I am running into is that in my console log, is that only the line.id is being removed from the array instead of the whole row of data.
How do I remove all the data belonging to a particular id?
Even though the console log shows that the text is removed, why is the text that is displayed in my row not removed from the view?
I'm not too familiar with hooks and have only been able to find examples of my particular problem with class components. Thanks in advance.

You should display the defined state with the hook. in this case the list , and not the lineInfo.
<div>
{list.map((line) => (
<Row key={`line_${line.id}`}>
// remaining code removed for length

You should not render lineInfo, render the list from local state instead:
const { useState, useCallback } = React;
const lineInfo = [
{
id: '001',
line: 's-1125026',
arrival: '7:30',
departure: '8:00',
authorization: 'User 1',
},
{
id: '002',
line: 's-1125027',
arrival: '8:01',
departure: '8:50',
authorization: 'User 1',
},
];
//make row pure component using React.memo
const Row = React.memo(function Row({ item, remove }) {
return (
<div>
<pre>{JSON.stringify(item, undefined, 2)}</pre>
<button onClick={() => remove(item.id)}>
remove
</button>
</div>
);
});
const App = () => {
const [list, setList] = useState(lineInfo);
//make remove a callback that is only created
// on App mount using useCallback with no dependencies
const remove = useCallback(
(removeId) =>
//pass callback to setList so list is not a dependency
// of this callback
setList((list) =>
list.filter(({ id }) => id !== removeId)
),
[]
);
return (
<ul>
{list.map((line) => (
<Row
key={`line_${line.id}`}
item={line}
remove={remove}
/>
))}
</ul>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

How to get All data in CurrentState in React js?

how to get single data from state ? basically I want to post the data in firestore but i cant get that how to get all data from specific index of array ?
const [tableData, setTableData] = useState([]);
//this contain all the data but how to get single data from state?
console.log("postData", tableData);
//just like this
console.log("date" , tableData.date); //undefined
console.log("date" , tableData.description); //undefined
console.log("date" , tableData.cashAdd); //undefined
//also I do with find function but there is an another way to do it ?
const dataa = tableData.find((x)=> {
return x;
})
console.log("date", dataa.date);
//only get 1 date of a single array not an all the dates of all arrays
You need to filter out that particular data based on id from main data array.
Example:
const { useState } = React;
const data = [{
id:1,
name: 'Dan',
description:'Dab',
date: 'someDate',
cashAdd: true
},{
id:2,
name: 'John',
description:'John',
date: 'someDate',
cashAdd: false
},{
id:3,
name: 'Riya',
description:'Riya',
date: 'someDate',
cashAdd: true
}]
function App() {
const [selectedId, setSelectedId] = useState(null);
const selectedItem = data.find(item => item.id === selectedId)
return (
<div className="App">
<ul>
{data.map(item => {
return <li onClick={() => setSelectedId(item.id)}>{item.name}</li>
})}
</ul>
<h2>Selected Item</h2>
{JSON.stringify(selectedItem)}
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Changing classname of a specific element on click from a mapped array an keeping the same classname until clicked again in react

const allTodos = [{id: 1, name: 'whatever user type'}, { }, { }, .... { }] // Defined as an array using setState in other file. Imported in this file as allTodos using props.
export const Todos = (props) => {
const [index, setIndex] = useState(0)
props.allTodos.map((prev) => {
return (
<div id="item_container">
<button type='button' className = `check_button ${prev.id === index ? 'checked' : 'not_checked'}`
onClick = {() => setIndex(prev.id)}>
<img src = {check} alt="" id = "check_img"></img>
</button>
<li id="li_item">{prev.name}</li>
</div>
)}
}
Explanation : Here, I set up a const index using useState that wiil change its value to the id of the element clicked upon in order to change the className of that element.
Question : Now, I succeeded in doing that but everytime I click on other element, the className gets added to that element but gets removed from the previous element it was added upon. Now I want to somehow preserve the className to those every elements I click on until I click on them again to change their className. By the way the styling that I desire to change is the background of that button/element.
You need to be able to keep track of every index that's checked - for this you'll need an array (or a number you do bit calculations with). A stateful index number doesn't contain enough information.
const allTodos = [{ id: 1, name: 'whatever user type' }, { id: 2, name: 'foo' }];
const Todos = ({ allTodos }) => {
const [todos, setTodos] = React.useState(() => allTodos.map(todo => ({ ...todo, checked: false })));
const handleClick = (i) => () => {
setTodos(todos.map((todo, j) => j !== i ? todo : ({ ...todo, checked: !todo.checked })))
};
return todos.map(({ id, name, checked }, i) => (
<div id="item_container">
<button
type='button'
className={`check_button ${checked ? 'checked' : 'not_checked'}`}
onClick={handleClick(i)}
>
<img alt="" id="check_img"></img>
</button>
<div>{name}</div>
</div >
))
}
ReactDOM.render(<Todos allTodos={allTodos} />, document.querySelector('.react'));
.checked {
background-color: green;
}
.not_checked {
background-color: yellow;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
You also need:
When not using plain ' " quotes, use {} delimiters around the prop
<li>s should only be children of <ul>s
Duplicate IDs are invalid HTML
You need to return the mapped JSX from the Todos component
You are missing a ) at the end of allTodos.map
You should use array to save ids clicked and removed id from array when clicked again.
const [idList, setIdList] = useState([])
const handleClick=(id, checked) => {
if(checked){
setIdList.filter(item => item !== id)
} else {
setIdList([...idList, id])
}
}
props.allTodos.map((prev) => {
const checked = idList.includes(prev.id)
...
className ={`check_button ${checked ? 'checked' : 'not_checked'}`}
onClick = {() => handleClick(prev.id, checked)
...

useState not causing rerender when objects array changes *hooks*

I have an object, which has a number of keys, and at each key, there is an array of objects. Each object contains an image file that I need to show to preview the image that the user has just uploaded. But it's showing the image that was uploaded the previous time (not the one that's been just uploaded)
Apparently useState doesn't immediately cause a rerender unless it sees a change in the array or object How do I update states onchange in an array of object in React Hooks
I followed the above (and a number of similar suggestions from StackOverflow) and made a copy of the object and a copy of the array at the key but it still doesn't work
Any idea why?
const Form = props => {
let [images, setImages] = useState({
bannerPicture: [],
projectPictures: [],
projectsPictures: [],
thumbnailPictures: []
})
const showTempImage = (file, tempFileObj, key) => {
const imagesCopy = {...images}
// add this image to the end of the array at the given key
imagesCopy[key] = [...imagesCopy[key], tempFileObj]
setImages({...imagesCopy})
....
}
I just want the image to show immediately after the user uploads it
Do it from your async function.
I'm assuming you'll only show the image after the upload is complete.
const mockUploadApi = () => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve("uploadedImage");
},1000);
});
};
const App = () => {
const [state,setState] = React.useState({
status: "IDLE",
image: ""
});
const uploadImage = () => {
setState((prevState) => ({
...prevState,
status: "UPLOADING"
}));
// THIS SHOULD USE await BUT THE SNIPPET DOES NOT ALLOW IT
// SO I'M USING .then()
mockUploadApi().then((uploadedImage) => {
setState({
status: "IDLE",
image: uploadedImage
});
});
};
return(
<div>
<div>
status: {state.status}
</div>
<div>
image: {state.image || "no image"}
</div>
<div>
<button onClick={uploadImage}>
Upload
</button>
</div>
</div>
);
};
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Loop over array with map() function onClick (ReactJS)

I want to create a wizard that changes content when the user clicks a "next" button.
I'm currently trying to use the .map function, it works but how can I adjust my code to loop over each step in my array onClick?
Currently, my code just displays 3 separate inputs with all of the steps in the array, what I want to do is iterate over each step onClick.
Here is an example of my code:
Array:
const wizardControls = {
steps: [
{
step: 1,
name: 'name1',
type: 'text',
placeholder: 'name1',
},
{
step: 2,
name: 'name2',
type: 'text',
placeholder: 'name2',
},
{
step: 3,
name: 'name3',
type: 'text',
placeholder: 'name3',
},
],
};
JSX using map() function:
{steps.map((step, index) => (
<div key={index}>
<input
value={value}
name={step.name}
type={step.type}
placeholder={step.placeholder}
onChange={onChange}
/>
</div>
))}
I'm thinking the button will need a handler function to loop over the index, however, I'm unsure how to do this with the map() function.
I'm open to a better approach if the map() function isn't the best route.
One way you could do this is by slicing by which step you're on (based on index).
Here's an example of what that might look like with your code.
const [step, setStep] = useState(1)
...
steps.slice(step - 1, step).map((step, index) => (
...
))
See a working example here: https://codesandbox.io/s/pensive-northcutt-el9w6
If you want to show a step at a time, don't use Array.map() to render all of them. Use useState to hold the current index (step), and take the current item from the steps array by the index. To jump to the next step, increment the index by 1.
const { useState } = React;
const Demo = ({ steps }) => {
const [index, setIndex] = useState(0);
const [values, setValue] = useState([]);
const next = () =>
setIndex(step => step < steps.length -1 ? step + 1 : step);
const onChange = e => {
const val = e.target.value;
setValue(v => {
const state = [...v];
state[index] = val;
return state;
})
};
const step = steps[index];
return (
<div>
<input
value={values[index] || ''}
name={step.name}
type={step.type}
placeholder={step.placeholder}
onChange={onChange}
/>
<button onClick={next}>Next</button>
</div>
);
};
const wizardControls = {"steps":[{"step":1,"name":"name1","type":"text","placeholder":"name1"},{"step":2,"name":"name2","type":"text","placeholder":"name2"},{"step":3,"name":"name3","type":"text","placeholder":"name3"}]};
ReactDOM.render(
<Demo steps={wizardControls.steps} />,
root
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>

Removing object from array using hooks (useState)

I have an array of objects. I need to add a function to remove an object from my array without using the "this" keyword.
I tried using updateList(list.slice(list.indexOf(e.target.name, 1))). This removes everything but the last item in the array and I'm not certain why.
const defaultList = [
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }]
const [list, updateList] = useState(defaultList);
const handleRemoveItem = e => {
updateList(list.slice(list.indexOf(e.target.name, 1)))
}
return (
{list.map(item => {
return (
<>
<span onClick={handleRemoveItem}>x </span>
<span>{item.name}</span>
</>
)}
}
)
Expected behaviour: The clicked item will be removed from the list.
Actual behaviour: The entire list gets removed, minus the last item in the array.
First of all, the span element with the click event needs to have a name property otherwise, there will be no name to find within the e.target. With that said, e.target.name is reserved for form elements (input, select, etc). So to actually tap into the name property you'll have to use e.target.getAttribute("name")
Additionally, because you have an array of objects, it would not be effective to use list.indexOf(e.target.name) since that is looking for a string when you are iterating over objects. That's like saying find "dog" within [{}, {}, {}]
Lastly, array.slice() returns a new array starting with the item at the index you passed to it. So if you clicked the last-item, you would only be getting back the last item.
Try something like this instead using .filter(): codesandbox
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
const defaultList = [
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }
];
const [list, updateList] = useState(defaultList);
const handleRemoveItem = (e) => {
const name = e.target.getAttribute("name")
updateList(list.filter(item => item.name !== name));
};
return (
<div>
{list.map(item => {
return (
<>
<span name={item.name} onClick={handleRemoveItem}>
x
</span>
<span>{item.name}</span>
</>
);
})}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can use Array.filter to do this in a one-liner:
const handleRemoveItem = name => {
updateList(list.filter(item => item.name !== name))
}
Eta: you'll also need to pass the name of your item in your onClick handler:
{list.map(item => {
return (
<>
<span onClick={() =>handleRemoveItem(item.name)}>x </span>
<span>{item.name}</span>
</>
)}
const defaultList = [
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }
]
const [list, updateList] = useState(defaultList);
const handleRemoveItem = idx => {
// assigning the list to temp variable
const temp = [...list];
// removing the element using splice
temp.splice(idx, 1);
// updating the list
updateList(temp);
}
return (
{list.map((item, idx) => (
<div key={idx}>
<button onClick={() => handleRemoveItem(idx)}>x </button>
<span>{item.name}</span>
</div>
))}
)
Small improvement in my opinion to the best answer so far
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
const defaultList = [
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }
];
const [list, updateList] = useState(defaultList);
const handleRemoveItem = (item) => {
updateList(list.filter(item => item.name !== name));
};
return (
<div>
{list.map(item => {
return (
<>
<span onClick={()=>{handleRemoveItem(item)}}>
x
</span>
<span>{item.name}</span>
</>
);
})}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Instead of giving a name attribute we just send it to the handle function
I think this code will do
let targetIndex = list.findIndex((each) => {each.name == e.target.name});
list.splice(targetIndex-1, 1);
We need to check name value inside object so use findIndex instead. then cut the object start from target index to 1 array after target index.
Codepen
From your comment your problem came from another part.
Change this view section
return (
<>
<span onClick={() => handleRemoveItem(item) }>x </span>
<span>{item.name}</span>
</>
)}
change function handleRemoveItem format
const handleRemoveItem = item => {
list.splice(list.indexOf(item)-1, 1)
updateList(list);
}
Redundant one liner - would not recommend as hard to test / type / expand / repeat / reason with
<button onClick={() => setList(list.slice(item.id - 1))}
A version without exports:
const handleDeleteItem = id => {
const remainingItems = list.slice(id - 1)
setList(remainingItems);
}
However I would consider expanding the structure of your logic differently by using helper functions in another file.
With that in mind, I made one example for filter and another for slice. I personally like the slice option in this particular use-case as it makes it easy to reason with. Apparently, it is also slightly more performant on larger lists if scaling (see references).
If using slice, always use slice not splice unless you have good reason not to do so as it adheres to a functional style (pure functions with no side effects)
// use slice instead of splice (slice creates a shallow copy, i.e., 'mutates' )
export const excludeItemFromArray = (idx, array) => array.slice(idx-1)
// alternatively, you could use filter (also a shallow copy)
export const filterItemFromArray = (idx, array) => array.filter(item => item.idx !== idx)
Example (with both options filter and slice options as imports)
import {excludeItemFromArray, filterItemFromArray} from 'utils/arrayHelpers.js'
const exampleList = [
{ id: 1, name: "ItemOne" },
{ id: 2, name: "ItemTwo" },
{ id: 3, name: "ItemThree" }
]
const [list, setList] = useState(exampleList);
const handleDeleteItem = id => {
//excluding the item (returning mutated list with excluded item)
const remainingItems = excludeItemFromArray(id, list)
//alternatively, filter item (returning mutated list with filtered out item)
const remainingItems = filterItemFromArray(id, list)
// updating the list state
setList(remainingItems);
}
return (
{list.map((item) => (
<div key={item.id}>
<button onClick={() => handleDeleteItem(item.id)}>x</button>
<span>{item.name}</span>
</div>
))}
)
References:
Don't use index keys in maps: https://robinpokorny.com/blog/index-as-a-key-is-an-anti-pattern/
Performance of slice vs filter: https://medium.com/#justintulk/javascript-performance-array-slice-vs-array-filter-4573d726aacb
Slice documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
Functional programming style: https://blog.logrocket.com/fundamentals-functional-programming-react/#:~:text=Functional%20programming%20codes%20are%20meant,computations%20are%20called%20side%20effects.
Using this pattern, the array does not jump, but we take the previous data and create new data and return it.
const [list, updateList] = useState([
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }
]);
updateList((prev) => {
return [
...prev.filter((item, i) => item.name !== 'ItemTwo')
]
})
This is because both slice and splice return an array containing the removed elements.
You need to apply a splice to the array, and then update the state using the method provided by the hook
const handleRemoveItem = e => {
const newArr = [...list];
newArr.splice(newArr.findIndex(item => item.name === e.target.name), 1)
updateList(newArr)
}

Resources