useEffect hook does not rerender an element - reactjs

I have an issue with useEffect, expect it to re render grid of elements, when on of the elements in this grid was deleted and a variable that contains data of that elements was changed, use effect refers to this variable so I expect it to re render grid where it placed.
const TasksView = ({current, tasksViewTriggered, cancelModals, addTaskToNote}) => {
const [tasks, setTasks] = useState([])
const onAddTask = () => {
addTaskToNote(current.id, '', false)
}
const onCancel = () => {
cancelModals()
}
useEffect(() => {
console.log('current was changed')
if (current) {
setTasks(current.tasks)
}
}, [current, tasksViewTriggered])
return (
<div className={`tasks-modal ${tasksViewTriggered ? '' : 'invisible'}`}>
<div className='taskModalContent'>
<div className="tasks-grid">
{tasks.length === 0 ? (<p className='center'>No tasks to show</p>) : (
tasks.map(task => <Task task={task} key={task.id}/>)
)}
</div>
</div>
<div className='modalFooter'>
<a href='#!' onClick={onCancel} className='btn btn-red'>
Close
</a>
<a href='#!' onClick={onAddTask} className='btn btn-green'>
Add Task
</a>
</div>
</div>
);
};
TasksView.propTypes = {
current: PropTypes.object,
tasksViewTriggered: PropTypes.bool.isRequired,
cancelModals: PropTypes.func.isRequired,
addTaskToNote: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
current: state.note.current,
tasksViewTriggered: state.note.tasksViewTriggered
})
export default connect(
mapStateToProps,
{cancelModals, addTaskToNote}
)
(TasksView);
Current value changes on task deletion and I expect this page to be re rendered after, but it does not happen.

useEffect(() => {
console.log('current was changed')
if (current) {
setTasks(current.tasks)
}
}, [current.tasks, tasksViewTriggered])
be sure current will not be null

Related

How to use this to target a item in a mapped list in react

I am still pretty new and am doing a simple food ordering app in React. I have a file of dummy meals and am able to create a cart through user input, create a modal on the "Your Cart" button from the header, and map through the cart displaying as a list, but I am unable to figure out how to delete an item from the cart. I realize I need to target the id of the list item that the X is connected to, but for some reason I can't get the this command to work.
Is there something about the this command that I am not understanding when it comes to a mapped list item? So far I have both tried this through my CartModal component and the Home component, but both turn up undefined when using this and I throw an error when using e.target (enter different options like e.target.value, e.target.id, etc...), and have even tried working on an inline targeting (which is where the code below is at currently) but still comes up nothing.
I will add a couple dummy meals to this as well.
Thanks so much!
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "reset":
return initialState;
default:
return state;
}
};
export const CountContext = React.createContext();
const Home = (props) => {
const [cart, setCart] = useState([]);
const [count, dispatch] = useReducer(reducer, initialState);
const {isVisible, toggleModal} = useModal();
let totalPrice;
let cartCounter ;
const cartUpdaterHandler = (cartItem) =>{
setCart([...cart, cartItem]);
cartItem= cartItem;
}
useEffect(() => {
cartCounter = cart.length;
totalPrice = cart.reduce((acc, {price}) => parseFloat(acc) + parseFloat(price), 0).toFixed(2);
}, [cart])
const modalHandler = () => {
toggleModal(true)
}
const removeCI = () => {
console.log(cart)
}
return (
<CountContext.Provider
value={{ countState: count,
countDispatch: dispatch,
currentCart: cart
}}
className={classes.contextProvider}
>
{isVisible && (
<CartModal
overlayClassName="custom_overlay"
isVisible={isVisible}
hideModal={toggleModal}
currentCart={cart}
totalPrice={totalPrice}
remove={removeCI}
/>
)}
<Header cart={cart} cartCounter={cart} modal={modalHandler}/>
<Intro />
<MealForm onAdd={cartUpdaterHandler} />
{/* <Cart /> */}
{/* <CartModal isVisible={isVisible} hideModal={toggleModal} currentCart={cart} totalPrice={totalPrice}/> */}
</CountContext.Provider>
)
}
export default Home;
const CartModal = ({ isVisible, hideModal, currentCart, remove}) => {
// const checkKey = () => {
// console.log(cartItem.id)
// console.log(this.id)
// }
return isVisible
? createPortal(
<Card className={classes.modal}>
<div className={classes.modalBackground}>
<div className={classes.modalDiv}>
<div className={classes.modalHeader}>
<h5 className={classes.modalHeaderH5}>Your Cart</h5>
<button className={classes.modalHeaderX} onClick={hideModal}> X </button>
</div>
<div className={classes.modalBody}>
{currentCart.map((cartItem, i) => {
return<div key={Math.random()}>
<ul className={classes.cartModalItem} key={Math.random()}>
<div className={classes.cartModalItemInfo} >
<li className={classes.cartModalName}>{cartItem.name}</li>
<li className={classes.cartModalPrice}>{cartItem.price}</li>
<li className={classes.cartModalDesc}>{cartItem.description}</li>
</div>
//I am asking about the X below this.
<button className={classes.cartModalX} onClick={()=>{console.log(this)}}>X</button>
</ul>
</div>
})}
<h5 className={classes.modalTotal}>Total:{currentCart.reduce((acc, {price}) => parseFloat(acc) + parseFloat(price), 0).toFixed(2)} </h5>
</div>
<div className={classes.modalFooter}>
<button className={classes.closeModalButton} onClick={hideModal}> Close </button>
<button className={classes.orderModalButton}>Checkout</button>
</div>
</div>
</div>
</Card>,
document.body,
)
: null;
};
export default CartModal;
const AvalibleMeals = [
{
id: 'm1',
name: 'Sushi',
description: 'Finest fish and veggies',
price: 22.99,
},
{
id: 'm2',
name: 'Schnitzel',
description: 'A german specialty!',
price: 16.5,
},
{
id: 'm3',
name: 'Barbecue Burger',
description: 'American, raw, meaty',
price: 12.99,
},
{
id: 'm4',
name: 'Green Bowl',
description: 'Healthy...and green...',
price: 18.99,
},
];
export default AvalibleMeals;

How to toggle class of a single element in a .map() function?

I am trying to toggle a class for a specific element inside a loop.
const ItemList: React.FC<ListItemUserProps> = (props) => {
const { items } = props;
const [showUserOpt, setShowUserOpt] = useState<boolean>(false);
function toggleUserOpt() {
setShowUserOpt(!showUserOpt);
}
const userOptVisible = showUserOpt ? 'show' : 'hide';
return (
<>
{items.map((t) => (
<React.Fragment key={t.userId}>
<div
className={`item ${userOptVisible}`}
role="button"
tabIndex={0}
onClick={() => toggleUserOpt()}
onKeyDown={() => toggleUserOpt()}
>
{t.userNav.firstName}
</div>
</React.Fragment>
))}
</>
);
};
export default ItemList;
When I click on an element, the class toggles for every single one.
You can create another component that can have it's own state that can be toggled without effecting other sibling components' state:
Child:
const ItemListItem: React.FC<SomeInterface> = ({ item }) => {
const [show, setShow] = useState<boolean>(false);
const userOptVisible = show ? "show" : "hide";
const toggleUserOpt = (e) => {
setShow((prevState) => !prevState);
};
return (
<div
className={`item ${userOptVisible}`}
role="button"
tabIndex={0}
onClick={toggleUserOpt}
onKeyDown={toggleUserOpt}
>
{item.userNav.firstName}
</div>
);
};
Parent:
const ItemList: React.FC<ListItemUserProps> = ({ items }) => {
return (
<>
{items.map((t) => (
<ItemListItem key={t.userId} item={t} />
))}
</>
);
};
If you simply adding classes to the element, I would keep it simple and use a handler to toggle the class using pure JS.
const handleClick = (e) => {
// example of simply toggling a class
e.currentTarget.classList.toggle('selected');
};
Demo:
const {
useState,
} = React;
// dummy data
const data = Array(20).fill(null).map((i, index) => `item ${(index + 1).toString()}`);
function App() {
const [items, setItems] = useState(data);
const handleClick = (e) => {
e.currentTarget.classList.toggle('selected');
};
return (
<div>
{items.map((item) => (
<button key={item} onClick={handleClick}>{item}</button>
))}
</div>
);
}
ReactDOM.render( <
App / > ,
document.getElementById("app")
);
.selected {
background: red;
}
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I think it'd be best if you kept track of the index so that you could target a single item in your list. As it stands the boolean is going to change the styling for all as you haven't specified which one should get the className.
Add a useState hook to keep track of it like:
const [activeIndex, setActiveIndex] = useState(null);
Then create a new function:
function handleIndexOnClick(index) {
setActive(index);
}
Then in your map() function add index. You'll then need to pass index in to you className attribute and the onClick function. The end result for that bit should look like:
{items.map((t, index) => (
<React.Fragment key={t.userId}>
<div
className={`item ${activeIndex && items[activeIndex] ? 'show' : 'hide }`}
role="button"
tabIndex={0}
onClick={() => handleIndexOnClick(index)}
onKeyDown={() => toggleUserOpt()}
>
{t.userNav.firstName}
</div>
</React.Fragment>
))}

Update one element of big list without re render others elements in react hooks?

i want to optimize my react App by testing with a large list of li
Its a simple todo List.
By exemple, when click on a li, task will be line-through, and check icon will be green. This simple action is very slow with a large list because, the whole list is re render.
How to do this with React Hooks?
function App() {
const [list, setList] = useState([]);
const [input, setInput] = useState("");
const inputRef = useRef(null);
useEffect(() => inputRef.current.focus(), []);
//Pseudo Big List
useEffect(() => {
const test = [];
let done = false;
for (let i = 0; i < 5000; i++) {
test.push({ task: i, done });
done = !done;
}
setList(test);
}, []);
const handlerSubmit = (e) => {
e.preventDefault();
const newTask = { task: input, done: false };
const copy = [...list, newTask];
setList(copy);
setInput("");
};
const checkHandler = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy[index].done = !copy[index].done;
setList(copy);
};
const suppression = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy.splice(index, 1);
setList(copy);
};
const DisplayList = () => {
return (
<ul>
{list.map((task, index) => (
<Li
key={index}
task={task}
index={index}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
);
};
//JSX
return (
<div className='App'>
<h1>TODO JS-REACT</h1>
<form id='form' onSubmit={handlerSubmit}>
<input
type='text'
placeholder='Add task'
required
onChange={(e) => setInput(e.target.value)}
value={input}
ref={inputRef}
/>
<button type='submit'>
<i className='fas fa-plus'></i>
</button>
</form>
{list.length === 0 && <div id='noTask'>No tasks...</div>}
<DisplayList />
</div>
);
}
export default App;
Li component
import React from "react";
export default function Li(props) {
return (
<li
onClick={(e) => props.checkHandler(e, props.index)}
className={props.task.done ? "line-through" : undefined}
>
{props.task.task}
<span className='actions'>
<i className={`fas fa-check-circle ${props.task.done && "green"}`}></i>
<i
className='fas fa-times'
onClick={(e) => props.suppression(e, props.index)}
></i>
</span>
</li>
);
}
CodeSandbox here: https://codesandbox.io/s/sad-babbage-kp3md?file=/src/App.js
I had the same question, as #Dvir Hazout answered, I followed this article and made your code the changes you need:
function App() {
const [list, setList] = useState([]);
const { register, handleSubmit, reset } = useForm();
//Pseudo Big List
useEffect(() => {
const arr = [];
let done = false;
for (let i = 0; i < 20; i++) {
arr.push({ id: uuidv4(), task: randomWords(), done });
done = !done;
}
setList(arr);
}, []);
const submit = ({ inputTask }) => {
const newTask = { task: inputTask, done: false, id: uuidv4() };
setList([newTask, ...list]);
reset(); //clear input
};
const checkHandler = useCallback((id) => {
setList((list) =>
list.map((li) => (li.id !== id ? li : { ...li, done: !li.done }))
);
}, []);
const suppression = useCallback((id) => {
setList((list) => list.filter((li) => li.id !== id));
}, []);
//JSX
return (
<div className="App">
<h1>TODO JS-REACT</h1>
<form onSubmit={handleSubmit(submit)}>
<input type="text" {...register("inputTask", { required: true })} />
<button type="submit">
<i className="fas fa-plus"></i>
</button>
</form>
{list.length === 0 && <div id="noTask">No tasks...</div>}
<ul>
{list.map((task, index) => (
<Li
key={task.id}
task={task}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
</div>
);
}
Li component
import React, { memo } from "react";
const Li = memo(({ task, suppression, checkHandler }) => {
// console.log each time a Li component re-rendered
console.log(`li ${task.id} rendered.`);
return (
<li
onClick={(e) => checkHandler(task.id)}
className={task.done ? "line-through" : undefined}
>
{task.task}
<span className="actions">
<i className={`fas fa-check-circle ${task.done && "green"}`}></i>
<i className="fas fa-times" onClick={(e) => suppression(task.id)}></i>
</span>
</li>
);
});
export default Li;
You can check it live here
I know it's probably late for your question, but may help others ;)
You can use React.memo and wrap the Li component. This will cache the instances of the Li component based on shallow comparison. Read more in the docs
Otherwise, if you don't need the state in the container, you can keep it locally in the Li component and then it won't cause a whole list rerender.

How to create a confirmation delete popup in React?

I am creating a Todo App, and trying to create a confirmation delete popup which is going to be visible when the user wants to delete a todo.
In my todo.js component I have created an onClick callback, handleDelete, in my delete button, that callback will set the popup to true making it visible, the problem is that in my handleDelete I pass the Id as argument, so I can track which todo has been clicked and filter it to show the new data updating the todos state, but I only want to do update the data when the user have clicked in the confirm button that is in the popup.
App Component:
function App() {
const [inputValue, setInputValue] = useState("");
const [todos, setToDos] = useState([]);
const [noToDo, setNoToDo] = useState(false);
const [popup, setPopup] = useState(false);
const handleOnSubmit = (e) => {
e.preventDefault();
setNoToDo(false);
const ide = nanoid();
const date = new Date().toISOString().slice(0, 10);
const newToDo = { task: inputValue, id: ide, date: date };
setToDos([...todos, newToDo]);
setInputValue("");
};
const handleDelete = (id) => {
setPopup(true);
let filteredData = todos.filter((todo) => todo.id !== id);
{
/*
filteredData is the new data, but I only want to update
todos with filteredData when the user has clicked on the confirm
button in the modal component, which execute(handleDeleteTrue)*/
}
};
const handleDeleteTrue = () => {
setPopup(false);
setToDos(filteredData);
};
const handleEdit = (id, task) => {
setInputValue(task);
const EditedData = todos.filter((edited) => edited.id !== id);
setToDos(EditedData);
};
return (
<div className="App">
<div className="app_one">
<h1>To do app</h1>
<form action="" className="form" onSubmit={handleOnSubmit}>
<input
type="text"
placeholder="Go to the park..."
onChange={(e) => setInputValue(e.target.value)}
value={inputValue}
/>
<button type="submit">ADD TO DO</button>
</form>
</div>
{noToDo && <FirstLoad />}
{todos.map((todo) => {
return (
<div key={todo.id} className="result">
<Todo
{...todo}
handleDelete={handleDelete}
handleEdit={handleEdit}
/>
</div>
);
})}
{popup && <Popup handleDeleteTrue={handleDeleteTrue} />}
</div>
);
}
export default App;
Todo Component:
const Todo = ({ handleDelete, handleEdit, task, id, date }) => {
return (
<>
<div className="result_text">
<h3>{task}</h3>
<p className="result_textP">{date}</p>
</div>
<div>
<button onClick={() => handleEdit(id, task)} className="button green">
Edit
</button>
<button onClick={() => handleDelete(id)} className="button">
delete
</button>
</div>
</>
);
};
export default Todo;
Modal Component:
function Popup({ handleDeleteTrue }) {
return (
<div className="modal">
<div className="modal_box">
<p>You sure you wanna delete?</p>
<button className="modal_buttonCancel">Cancel</button>
<button onClick={handleDeleteTrue} className="modal_buttoDelete">
Confirm
</button>
</div>
</div>
);
}
export default Popup;
I tried to declare filteredData as global variable, outside my App component, so when I execute handleDelete it initializes that variable with the filtered data, and only when the user click the confirm button on the popup it executes a new function, handleDeleteTrue, which updates the data to filteredData.
It works, but declaring variables outside my component is not a good practice, so is there a better approach?
The issue in your current code is that, you are losing the id that should be deleted, so you need to store it in a ref or state.
Here is a solution that stores the id in state along with the boolean flag that shows/hides the Confirmation Box:
const [popup, setPopup] = useState({
show: false, // initial values set to false and null
id: null,
});
Modify the delete-handlers as:
// This will show the Cofirmation Box
const handleDelete = (id) => {
setPopup({
show: true,
id,
});
};
// This will perform the deletion and hide the Confirmation Box
const handleDeleteTrue = () => {
if (popup.show && popup.id) {
let filteredData = todos.filter((todo) => todo.id !== popup.id);
setToDos(filteredData);
setPopup({
show: false,
id: null,
});
}
};
// This will just hide the Confirmation Box when user clicks "No"/"Cancel"
const handleDeleteFalse = () => {
setPopup({
show: false,
id: null,
});
};
And, in the JSX, pass the handlers to Popup:
{popup.show && (
<Popup
handleDeleteTrue={handleDeleteTrue}
handleDeleteFalse={handleDeleteFalse}
/>
)}

Deconstructing state in useState in react and typescript

Is there a way to destructure a current state that has a specified shape? I have a current state in personJob but I want to able to specify which object to look at (when I click on a button that will slice that certain object and render only that data).
I get an error in TypeScript const {company, dates, duties, title} = personJob[value]; when I try to slice by that index
The error is:
Cannot destructure property 'company' of 'personJob[value]' as it is undefined.
Component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const url = 'https://course-api.com/react-tabs-project';
interface IPerson {
id: string;
company: string;
dates: string;
duties: string[];
title: string;
}
function App() {
const [personJob, setPersonJob] = useState<IPerson[]>([]);
const [value, setValue] = useState<number>(0);
const fetchData = async () => {
const response = await axios(url);
setPersonJob(response.data);
};
useEffect(() => {
fetchData();
}, []);
const { company, dates, duties, title, id } = personJob[value];
return (
<main>
<h1>Jobs</h1>
{personJob.map((job, index) => {
return (
<button key={job.id} onClick={() => setValue(index)}>
{job.company}
</button>
);
})}
<section>
<article className="border-2 px-5 py-5">
<div key={id}>
<h2>{title}</h2>
<h3>{company}</h3>
<p>{dates}</p>
{duties.map((duty) => {
return <div>*** {duty}</div>;
})}
<button type="button">More Info</button>
</div>
</article>
</section>
</main>
);
}
export default App;
Issue
On the initial render personJob is still an empty array and personJob[0] is undefined, so values can't be destructured from it.
Solution
Provide a fallback object to destructure from, personJob[value] || {}.
Conditionally render the section if personJob[value] is truthy and exists.
Code:
function App() {
const [personJob, setPersonJob] = useState<IPerson[]>([]);
const [value, setValue] = useState<number>(0);
const fetchData = async () => {
const response = await axios(url);
setPersonJob(response.data);
};
useEffect(() => {
fetchData();
}, []);
const { company, dates, duties, title, id } = personJob[value] || {}; // <-- fallback for destructuring
return (
<main>
<h1>Jobs</h1>
{personJob.map((job, index) => {
return (
<button key={job.id} onClick={() => setValue(index)}>
{job.company}
</button>
);
})}
{personJob[value] && <section> // <-- conditionally render section if data available
<article className="border-2 px-5 py-5">
<div key={id}>
<h2>{title}</h2>
<h3>{company}</h3>
<p>{dates}</p>
{duties.map((duty) => {
return <div>*** {duty}</div>;
})}
<button type="button">More Info</button>
</div>
</article>
</section>}
</main>
);
}
Demo

Resources