Why component is not re-rendering after deletion in react - reactjs

Trying to delete element from list but its not re-rendering even I am using useEffect. my code is
import React from "react";
import "./styles.css";
import { useEffect, useState } from "react";
const initialList = [
{
id: 'a',
firstname: 'Robin',
lastname: 'Wieruch',
year: 1988,
},
{
id: 'b',
firstname: 'Dave',
lastname: 'Davidds',
year: 1990,
},
{
id: 'c',
firstname: 'ssh',
lastname: 'asssss',
year: 1990,
},
{
id: 'd',
firstname: 'Asdf',
lastname: 'we32e',
year: 1990,
},
];
export default function App() {
const [list, setList] = useState(initialList);
useEffect(() => {
console.log('useEffect has been called!');
setList(list);
}, [list]);
const handleRemove = (id,i) => {
list.splice(i,1)
setList(list);
}
return (
<div className="App">
<ul>
{list.map((item,i) => (
<li key={item.id}>
<span>{item.firstname}</span>
<span>{item.lastname}</span>
<span>{item.year}</span>
<button type="button" onClick={() => handleRemove(item.id,i)}>
Remove
</button>
</li>
))}
</ul>
</div>
);
}

It's always a problem in react modifying the state directly and is generally considered an anti-pattern.
You could just do something like:
const handleRemove = (id) => {
const newArr = list.filter((el) => el.id !== id);
setList(newArr);
}
And you don't need any useEffect either, the function should handle the state change.

Related

Click and insert object in one array to another empty array using react hooks

I have array of objects (items) with button clickHandler function. When you click on button, it should add that object to new array named ‘myNewArray’. Please help me to achieve this. I added demo object inside array ‘myNewArray’.
Explaination: If i click on category button '1-furniture', that object will added to new array named 'myNewArray'
import React, { useState } from "react";
const App = () => {
const [items, setItems] = useState([
{ name: "Furniture", categoryKey: 1 },
{ name: "Shoes", categoryKey: 2 },
{ name: "Electronics", categoryKey: 3 },
{ name: "Clothes", categoryKey: 4 },
{ name: "Grocery", categoryKey: 5 },
]);
const [myNewArray, setMyNewArray] = useState([{ name: "demo-item", categoryKey: 100 }]);
const clickHandler = (categoryKey: any) => {
console.log(categoryKey);
};
return (
<div>
{items.map((item) => (
<button onClick={() => clickHandler(item.categoryKey)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
<h4>New array after clicking on item from above array[items]</h4>
{myNewArray.map((item) => (
<button onClick={() => clickHandler(item.categoryKey)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
</div>
);
};
export default App;
just use set method in the useState
const clickHandler = (item: any) => {
setMyNewArray(prev => [...prev, {name:item.name,categoryKey: item.categoryKey}])
};
and pass item in the click
onClick={() => clickHandler(item)}
Here's the working solution:
import React, { useState, useEffect } from "react";
const App = () => {
const [items, setItems] = useState([
{ name: "Furniture", categoryKey: 1 },
{ name: "Shoes", categoryKey: 2 },
{ name: "Electronics", categoryKey: 3 },
{ name: "Clothes", categoryKey: 4 },
{ name: "Grocery", categoryKey: 5 }
]);
const [myNewArray, setMyNewArray] = useState([
{ name: "demo-item", categoryKey: 100 }
]);
useEffect(() => {
console.log(myNewArray);
}, [myNewArray]);
const clickHandler = (item) => {
setMyNewArray([...myNewArray, item]);
};
return (
<div>
{items.map((item) => (
<button onClick={() => clickHandler(item)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
<h4>New array after clicking on item from above array[items]</h4>
{myNewArray.map((item, i) => (
<button key={i}>
{item.categoryKey}-{item.name}
</button>
))}
</div>
);
};
export default App;
The live demo is here: https://codesandbox.io/s/determined-solomon-phyy9u?file=/src/App.js:0-1163
You can have a look at the console to check the myNewArray updates.
You could also do like this.
if Item is with the matching category Key. Then, it's Update the myNewArray state with the new item
const clickHandler = (categoryKey: any) => {
const item = items.find((i) => i.categoryKey === categoryKey);
setMyNewArray([...myNewArray, item]);
};
Here is Codesandbox

React - Encountered two children with the same key

I'm fairly new to React. I am working on a note app and when I add 2 notes, they have the same key and the next 2 notes also share their own key and so on. I started off with prop drilling from the App to the AddNote file via NotesList.js and it was working fine and the problem has only occurred since I used useContext API so maybe I am not coding the useContext in the correct way. The useContext component looks like this:
import { createContext } from "react";
const HandleAddContext = createContext();
export default HandleAddContext;
This is my App.js
import { useState } from "react";
import { v4 as uuid } from "uuid";
import NotesList from "./components/NotesList";
import HandleAddContext from "./components/UseContext/HandleAddContext";
const unique_id = uuid();
const small_id = unique_id.slice(0, 8);
const initialState = [
{
id: small_id,
text: "1st note",
date: "12/10/22022",
},
{
id: small_id,
text: "2nd note",
date: "15/10/22022",
},
{
id: small_id,
text: "3rd note",
date: "16/10/22022",
},
{
id: small_id,
text: "4th note",
date: "30/10/22022",
},
];
export const App = () => {
const [notes, setNote] = useState(initialState);
const addHandleNote = (text) => {
console.log(text);
const date = new Date();
const newNote = {
id: small_id,
text: text,
date: date.toLocaleDateString(),
};
console.log(newNote);
const newNotes = [...notes, newNote];
setNote(newNotes);
};
return (
<HandleAddContext.Provider value={addHandleNote}>
<div className="container">
<NotesList notes={notes} />
</div>
</HandleAddContext.Provider>
);
};
export default App;
This is the component with map notes
import Note from "./Note";
import AddNote from "./AddNote";
const NotesList = ({ notes }) => {
return (
<div className="notes-list">
{notes.map((note) => (
<Note key={note.id} id={note.id} text={note.text} date={note.date} />
))}
<AddNote />
</div>
);
};
export default NotesList;
This is the Note:
import { RiDeleteBin6Line } from "react-icons/ri";
const Note = ({ text, date }) => {
return (
<div className="note">
{/* <div> */}
<p>{text}</p>
{/* </div> */}
<div className="note-footer">
<p className="note-footer-text">{date}</p>
<RiDeleteBin6Line />
</div>
</div>
);
};
export default Note;
This is the AddNote.js component
import { useState } from "react";
import { RiSave2Line } from "react-icons/ri";
const AddNote = ({ handleAddNote }) => {
const [addText, setAddText] = useState("");
const [errorMsg, setErrorMsg] = useState("");
//handle text input
const handleChange = (e) => {
console.log(e.target.value);
setAddText(e.target.value);
};
//handle save
const handleSaveClick = () => {
if (addText.trim().length > 0) {
handleAddNote(addText);
}
};
return (
<div>
<textarea
rows="8"
cols="10"
placeholder="Type here to add a note..."
value={addText}
onChange={handleChange}
/>
<div>
<p>200 characters remaining</p>
<RiSave2Line onClick={handleSaveClick} />
</div>
</div>
);
};
export default AddNote;
The issue is your unique_id and small_id are only being generated once due to your function call syntax.
const unique_id = uuid();
Assigns unique_id the result of uuid(), rather than referencing the function. And therefore small_id is simply slicing the already generated uuid. To fix this your must generate a new uuid every time you create a note. Your can create a function that return a new 'small ID' everytime.
function genSmallID() {
return uuid().slice(0, 8);
}
And now when you create your initial notes use the function:
const initialState = [{
id: genSmallID(),
text: "1st note",
date: "12/10/22022",
}, {
id: genSmallID(),
text: "2nd note",
date: "15/10/22022",
}, {
id: genSmallID(),
text: "3rd note",
date: "16/10/22022",
}, {
id: genSmallID(),
text: "4th note",
date: "30/10/22022",
}];
by setting a variable
const small_id = unique_id.slice(0, 8);
you create a variable and assign it to each element of your initialState array's id.
you should delete small_id and unique_id and do this:
const initialState = [{
id: uuid().slice(0, 8),
text: "1st note",
date: "12/10/22022",
}, {
id: uuid().slice(0, 8),
text: "2nd note",
date: "15/10/22022",
}, {
id: uuid().slice(0, 8),
text: "3rd note",
date: "16/10/22022",
}, {
id: uuid().slice(0, 8),
text: "4th note",
date: "30/10/22022",
}];
In order to have different id (here you have always the same), or if the id isn't relevant for you you can always use the element's position in the array as key with the 2nd parameter of the map function like this:
<div className="notes-list">
{notes.map((note, key) => (
<Note key={key} id={note.id} text={note.text} date={note.date} />
))}
<AddNote />

React/Firebase. How can i filter some products by categories using firebase?

How can i filter some products by categories using firebase? This is a fragment of my code
Not sure if you have a correct db.json file, i had to flatMap the result but here is a working code. I used require to load you json file and left const [products, setProducts] = useState([]); just in case. Also i switched categories to useMemo so this variable will not update on each re-render.
import React, { useState, useEffect, useMemo } from "react";
import "./styles.scss";
import { Link } from "react-router-dom";
const dbProducs = require("./db.json");
const CategoriesPage = () => {
// const {product} = useContext(Context)
const [products, setProducts] = useState([]);
const categories = useMemo(() => {
return [
{ id: 1, title: "Tablets" },
{ id: 2, title: "Computers" },
{ id: 3, title: "Consoles" },
{ id: 4, title: "Photo and video" },
{ id: 5, title: "Technics" },
{ id: 6, title: "Game Content" },
{ id: 7, title: "Notebooks" },
{ id: 8, title: "Smartphones" },
{ id: 9, title: "Headphones" },
{ id: 10, title: "Steam" }
// {id: 11,imageSrc:steamcards, title: 'Стиральные машины'},
// {id: 12,imageSrc: coffeemaschine, title: 'One stars'},
// {id: 13,imageSrc:headphones, title: 'Холодильники'},
];
}, []);
useEffect(() => {
const flatMapped = dbProducs.flatMap((x) => x.products);
setProducts(flatMapped);
}, []);
return (
<section className="popular__categories">
<h3 className="events__title">
<span>Categories</span>
</h3>
<div className="categories__wrapper">
{categories.map((category) => (
<Link
to={`${category.id}`}
className="categories__content"
key={category.id}
>
<h2 className="categories__title">{category.title}</h2>
<img
className="categories__img"
alt={category.title}
src={category.imageSrc}
/>
<ul>
{products
.filter((p) => p.category === category.title)
.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</Link>
))}
</div>
</section>
);
};
export default CategoriesPage;
Technically it would be better to clone and extend your categories objects with additional array property with useMemo, or you can add additional Map object with key = Category(title) and value = products (filtered) but it is up to you.
Full example with Context, Routes, Navigation:

Pass props between components using state hook

sorry if this question is so simple, I want to pass props between components using state hooks, this is the first component:
import React, { useState } from "react";
import NewExpense from "../components/Expenses/NewExpense/NewExpense";
import Expenses from "../components/Expenses/Expenses";
import "../components/Expenses/NewExpense/ExpenseForm.css";
const DUMMY_EXPENSES = [
{
id: "e1",
title: "Lego Simpsons",
amount: 100.5,
date: new Date(2021, 1, 15),
},
{
id: "e2",
title: "Lego Mindstorms",
amount: 200.5,
date: new Date(2021, 2, 15),
},
{
id: "e3",
title: "Lego Batman",
amount: 300.5,
date: new Date(2021, 3, 15),
},
{
id: "e4",
title: "Lego Star Wars",
amount: 400.5,
date: new Date(2021, 4, 15),
},
];
const VerExpensesV3 = () => {
const [expenses, setExpenses] = useState(DUMMY_EXPENSES);
const [showForm, setShowForm] = useState(false);
const addExpenseHandler = (expense) => {
setExpenses((prevExpenses) => {
return [expense, ...prevExpenses];
});
};
const statusFormHandler = () => {
return showForm;
}
const showFormHandler = () => {
setShowForm(true);
};
return (
<div>
<h1>My expenses</h1>
{showForm && (
<NewExpense
onAddExpense={addExpenseHandler}
statusForm={statusFormHandler}
/>
)}
<div className="new-expense__actions">
<button onClick={showFormHandler}>Add New Expense</button>
</div>
<Expenses records={expenses} />
</div>
);
};
export default VerExpensesV3;
this is the second one:
import React, { useState } from 'react';
import './NewExpense.css';
import ExpenseFormV2 from './ExpenseFormV2';
const NewExpense = (props) => {
const [statusForm, setStatusForm] = useState(props.statusFormHandler);
console.log("el valor de statusForm en NewExpense es: "+statusForm);
const saveExpenseDataHandler =
(enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString()
};
props.onAddExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseFormV2
onSaveExpenseData={saveExpenseDataHandler}
currentStatusForm={statusForm}/>
</div>
)
};
export default NewExpense;
So far I'm stuck because in these lines:
const [statusForm, setStatusForm] = useState(props.statusFormHandler);
console.log("el valor de statusForm en NewExpense es: "+statusForm);
I get "undefined", from my point of view the value passed is NULL from component 1 to component 2 and I don't understand why, so your comments and suggestions will be appreciated.
Thanks a lot
You're using the wrong property on props in <NewExpense>. Try this instead:
const [statusForm, setStatusForm] = useState(props.statusForm);
statusForm is the prop you defined on the component, and statusFormHandler is the name of the function you assign to the statusForm in the first component.
If you actually want your prop name to be statusFormHandler then disregard the above and use this
<NewExpense
onAddExpense={addExpenseHandler}
statusFormHandler={statusFormHandler}
/>
In my case I was using lower letter variable name.
const newExpense = (props) => {
const [isDefault, setDefault] = useState(true);
const saveNewExpenseHandler = (expenseData) => {
props.onSaveExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSavePressed={saveNewExpenseHandler} />
</div>
);
};
export default newExpense;
once I changed newExpense to NewExpense it all worked fine

Data get updated twice on button onClick event

I've a very simple React Web App where I want to add new object of review to an array of an object:-
I'm using useReducer to handle default state of my data as show below:-
reducer function:-
const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
state.forEach(
(data) =>
data.id === action.payload.selectedDataId &&
data.listOfReview.push(action.payload.newReview)
);
return [...state];
default:
return state;
}
};
default data for my reducer:-
const data = [
{
id: 1607089645363,
name: "john",
noOfReview: 1,
listOfReview: [
{
reviewId: 1607089645361,
name: "john doe",
occupation: "hero",
rating: 5,
review: "lorem ipsum"
}
]
},
{
id: 1507089645363,
name: "smith",
noOfReview: 1,
listOfReview: [
{
reviewId: 1507089645361,
name: "smith doe",
occupation: "villain",
rating: 5,
review: "lorem ipsum"
}
]
}
];
App.js, demo of what's happening:-
import React, { useState, useEffect, useReducer } from "react";
import "./styles.css";
export default function App() {
const [state, dispatch] = useReducer(reducer, data);
// hnadle adding of new review
const handleAddNewReview = (id) => {
dispatch({
type: "ADD_REVIEW_ITEM",
payload: {
selectedDataId: id,
newReview: {
reviewId: new Date().getTime().toString(),
name: "doe doe",
occupation: "doctor",
rating: 5,
review: "lorem ipsum"
}
}
});
};
return (
<>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
{state?.length > 0 &&
state.map((data) => (
<div key={data.id}>
<h1>{data.name}</h1>
{data.listOfReview?.length > 0 &&
data.listOfReview.map((review) => (
<div key={review.reviewId}>
<h3>{review.name}</h3>
<p>{review.occupation}</p>
</div>
))}
<button onClick={() => handleAddNewReview(data.id)}>
Add new review
</button>
</div>
))}
</>
);
}
The problem is, once I clicked the button for the first time, the state gets updated right. But if I click it for the second time, it somehow added TWO more of the same review. How should I change my code in reducer to fixed this issue?
This is a working sandbox of said case.
Try changing your reducer to be:
case "ADD_REVIEW_ITEM":
return state.map((data) => {
if (data.id === action.payload.selectedDataId) {
return {
...data,
listOfReview: [...data.listOfReview, action.payload.newReview]
};
}
return data;
});
Currently, you are mutating the state variable and pushing in an array which can lead to side-effect.

Resources