React Hook useState behavior - reactjs

I don't quietly undestand when the useState sets initial value. In this code example only on first click props and state values in Table component will be the same. Why they will not be equal on a second/third/fourth click?
import React, { useState } from "react";
import "./App.module.css";
import styles from "./App.module.css";
function App() {
const [mode, setMode] = useState({ bo: false, value: "1" });
const onClickHanlder = (value: string) => {
setMode({ bo: true, value });
};
return (
<div className="App">
<div onClick={() => onClickHanlder("2")}>one</div>
<div onClick={() => onClickHanlder("3")}>one</div>
<div onClick={() => onClickHanlder("4")}>one</div>
<div onClick={() => onClickHanlder("25")}>one</div>
<div onClick={() => onClickHanlder("6")}>one</div>
{mode && <Table value={mode.value} />}
</div>
);
}
export default App;
type TablePropsType = {
value: string
};
export const Table: React.FC<TablePropsType> = (props) => {
const [a, setA] = useState(props.value);
return (
<div>
{a}
{props.value}
</div>
);
};

You never set the state in Table, only on initial render you give it the default state. If you want them to be the same you have to also have to set the state in Table:
import React, {useState, useEffect} from 'react';
function App() {
const [mode, setMode] = useState({bo: false, value: '1'})
const onClickHanlder = (value: string) => {
setMode({bo: true, value})
}
return (
<div className="App">
<div onClick={() => onClickHanlder('2')}>one</div>
<div onClick={() => onClickHanlder('3')}>one</div>
<div onClick={() => onClickHanlder('4')}>one</div>
<div onClick={() => onClickHanlder('25')}>one</div>
<div onClick={() => onClickHanlder('6')}>one</div>
{
mode && <Table value={mode.value}/>
}
</div>
);
}
export default App;
type TablePropsType = {
value: string,
}
export const Table:React.FC<TablePropsType> = (props) => {
const [a, setA] = useState(props.value)
useEffect(()=>{
setA(props.value)
},[props.value])
return (
<div>
{a}
{props.value}
</div>
)
}

Related

React Outside Hook not reacting to outside Click

I am finishing up a project where I want to use a small dropdown menu when I click on my settings icon. The problem is that for some reason it is not recognized when I click outside of that dropdown menu. I used a hook that worked in the same project with a different dropdown menu, but now it doesn't. Maybe because it is in a modal? I really don't know.
Here is the Repo of this Project: https://github.com/Clytax/fem-kanban
The Hook (I modified it a bit by excluding the elipsis icon so it doesnt reopen when Click on it to close it.)
import React from "react";
export const useOutsideClick = (callback, exclude) => {
const ref = React.useRef();
React.useEffect(() => {
const handleClick = (e) => {
if (
ref.current &&
!ref.current.contains(e.target) &&
!exclude.current.contains(e.target)
) {
callback();
}
};
document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
};
}, [callback, exclude]);
return ref;
};
The Modal:
import React, { useRef, useState, useEffect } from "react";
import "./taskModal.scss";
import { ReactComponent as Elipsis } from "../../../assets/Icons/icon-vertical-ellipsis.svg";
import { ReactComponent as Close } from "../../../assets/Icons/icon-chevron-up.svg";
import { ReactComponent as Open } from "../../../assets/Icons/icon-chevron-down.svg";
import { useSelector, useDispatch } from "react-redux";
import modalSlice, {
closeViewTaskModal,
openEditTaskModal,
closeAllModals,
openDeleteTaskModal,
} from "../../../features/global/modalSlice";
import Backdrop from "../Backdrop/Backdrop";
import Subtask from "../../Task/Subtask";
import "../../Extra/DropdownSettings.scss";
import { useOutsideClick } from "../../../hooks/useOutsideClick";
import { motion } from "framer-motion";
import DropdownStatus from "../../Extra/DropdownStatus";
import DropdownSettings from "../../Extra/DropdownSettings";
import DropdownSettingsTask from "../../Extra/DropdownSettingsTask";
const ViewTaskModal = ({ handleClose }) => {
const [openSettings, setOpenSettings] = useState(false);
const dispatch = useDispatch();
const task = useSelector((state) => state.modal.viewTaskModal.task);
const handleCloseSettings = () => {
console.log("hi");
setOpenSettings(false);
};
const modal = useSelector((state) => state.modal);
const viewTaskModal = useSelector((state) => state.modal.viewTaskModal);
const elipsisRef = useRef(null);
const wrapperRef = useOutsideClick(handleCloseSettings, elipsisRef);
const getFinishedSubTasks = () => {
let finishedSubTasks = 0;
task.subTasks.forEach((subtask) => {
if (subtask.isDone) {
finishedSubTasks++;
}
});
return finishedSubTasks;
};
const closeModal = () => {
dispatch(closeViewTaskModal());
};
return (
<Backdrop onClick={closeModal} mobile={false}>
<motion.div
onClick={(e) => {
e.stopPropagation();
}}
onMouseDown={(e) => {
e.stopPropagation();
}}
className="view-task"
>
<div className="view-task__header | flex">
<h2 className="view-task__header__title">{task.name}</h2>
<div className="view-tastk__settings">
<div
className="view-task__header__icon"
style={{ cursor: "pointer" }}
ref={elipsisRef}
onClick={() => {
setOpenSettings(!openSettings);
}}
>
<Elipsis />
</div>
{openSettings && (
<div className="dropdown-settings__task" ref={wrapperRef}>
<div
className="dropdown-settings__item"
onClick={() => {
dispatch(closeAllModals());
dispatch(openEditTaskModal(task));
}}
>
Edit Task
</div>
<div
className="dropdown-settings__item"
onClick={() => {
dispatch(closeAllModals());
dispatch(openDeleteTaskModal(task));
}}
>
Delete Task
</div>
</div>
)}
</div>
</div>
<p className="view-task__description">{task.description}</p>
<div className="view-task__subtasks">
<p>
Subtasks ({getFinishedSubTasks()} of {task.subTasks.length})
</p>
<div className="view-task__subtasks__list">
{task.subTasks.map((subtask, index) => (
<Subtask
subtaskID={subtask.id}
boardID={task.boardID}
taskID={task.id}
columnID={task.columnID}
key={index}
/>
))}
</div>
</div>
<div className="view-task__status">
<p>Current Status</p>
<DropdownStatus click={handleCloseSettings} task={task} />
</div>
</motion.div>
</Backdrop>
);
};
export default ViewTaskModal;

How can I display the result of the child component in the parent component?

my application consists of a filter select by provinces, the filter is a modal that shows me the result in the filter component (child), I would like to show the result in the parent not in the filter modal. I do not know what to pass to the parent to show the result or how to show it on the screen.
///parent component
import React, { useState, useEffect } from 'react'
import { useDispatch } from "react-redux";
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import { selectClinics } from '../../actions/detailClinics'
import { useNavigate } from "react-router-dom";
import contentUtils from '../../lib/contentUtils'
import { SearchFilterClinics } from './SearchFilterClinics'
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
const ClinicList = () => {
const [clinicList, setClinicList] = useState<Clinic[]>([]);
const [clinicListFiltered, setClinicListFiltered] = useState<Clinic[]>([]);
const [searchClinic, setSearchClinic] = useState("");
const dispatch = useDispatch();
const navigate = useNavigate();
///modal control
const [isOpen, setIsOpen] = useState(false);
const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);
console.log(searchClinic);
const fetchClinicList = async () => {
getClinic().then((response)=>{
console.log(response)
setClinicList(response);
setClinicListFiltered(response)
}).catch ( (error) => {
console.error(error);
throw error;
});
}
const handleChange=e=>{
setSearchClinic(e.target.value);
filter(e.target.value);
}
const filter=(termSearch)=>{
const resultSearch= clinicList.filter((element)=>{
if(element.title.toString().toLowerCase().includes(termSearch.toLowerCase())
){
return element;
}
});
setClinicListFiltered(resultSearch);
}
function handleAddToDetail(clinic) {
dispatch(selectClinics(clinic));
navigate('clinicdetail');
}
function goToPageSearchFilterClinics() {
navigate('filterclinics');
}
useEffect (() => {
fetchClinicList();
}, []);
return(
<>
<div style={{display: 'flex'}}>
<div style={{width:'5rem'}}>
{/* <button onClick={() => goToPageSearchFilterClinics()}>filtro</button> */}
<button onClick={openModal}>filtro</button>
</div>
< SearchFilterClinics isOpen={isOpen} closeModal={closeModal} clinicFilter={clinicFilter}></SearchFilterClinics>
<Form className="d-flex">
<Form.Control
type="search"
value={searchClinic}
placeholder="Search"
className="me-2"
aria-label="Search"
onChange={handleChange}
/>
</Form>
</div>
<div className="content-cliniclist">
{
clinicListFiltered.map((clinic) => (
<div style={{marginBottom: '3rem'}}>
<button
type="button"
onClick={() => handleAddToDetail(clinic)}
style={{all: 'unset'}}
>
<div>
{/* <img src={ contentUtils.getLargeImageUrl(clinic.logoWidth )} alt="#"></img> */}
<div>{clinic.title}</div>
<div>{clinic.propsPhone}</div>
<div>{clinic.mobile}</div>
<div>{clinic.email}</div>
<div>{clinic.registry}</div>
</div>
</button>
</div>
))
}
</div>
</>
)
}
export default ClinicList;
////child component
import React, { useState, useEffect } from 'react'
import Select, { SingleValue } from 'react-select'
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import "./Modal.css";
interface Props {
isOpen: boolean,
clinicFilter: String,
closeModal: () => void
}
export const SearchFilterClinics : React.FC<Props> = ({ children, isOpen, closeModal, clinicFilter }) => {
////filter
type OptionType = {
value: string;
label: string;
};
const provincesList: OptionType[] = [
{ value: 'Todos', label: 'Todos' },
{ value: 'Valencia', label: 'Valencia' },
{ value: 'Castellon', label: 'Castellon' },
{ value: 'Alicante', label: 'Alicante' },
]
const [clinicList, setClinicList] = useState<Clinic[]>([]);
const [clinicListFilteredSelect, setClinicListFilteredSelect] = useState<Clinic[]>([]);
const [filterSelectClinic, setFilterSelectClinic] = useState<SingleValue<OptionType>>(provincesList[0]);
const handleChangeSelect = async (provinceList: SingleValue<OptionType>) => {
getClinic().then((response) => {
setClinicList(response);
setClinicListFilteredSelect(response)
setFilterSelectClinic(provinceList);
filterSelect(provinceList );
}).catch ((error) => {
console.error(error);
throw error;
});
}
const filterSelect=(termSearch)=>{
const resultFilterSelect = clinicList.filter((element) => {
if(element.province?.toString().toLowerCase().includes(termSearch.value.toLowerCase() )
){
return element;
}
});
setClinicListFilteredSelect(resultFilterSelect);
}
const handleModalContainerClick = (e) => e.stopPropagation();
return (
<>
<div className={`modal ${isOpen && "is-open"}`} onClick={closeModal}>
<div className="modal-container" onClick={handleModalContainerClick}>
<button className="modal-close" onClick={closeModal}>x</button>
{children}
<div>
<h1>Encuentra tu clínica</h1>
</div>
<div>
<form>
<label>Provincia</label>
<Select
defaultValue={filterSelectClinic}
options={provincesList}
onChange={handleChangeSelect}
/>
</form>
{
clinicListFilteredSelect.map((clinicFilter) => (
<div>
<div>{clinicFilter.title}</div>
<div>{clinicFilter.propsPhone}</div>
<div>{clinicFilter.mobile}</div>
<div>{clinicFilter.email}</div>
<div>{clinicFilter.province} </div>
<div>{clinicFilter.registry}</div>
</div>
))
}
</div>
</div>
</div>
</>
)
}
You need to pass a callback function to the parent and set the state value in the parent, not in the child. (This is known as lifting state up.)
In your case this would involve moving
const [filterSelectClinic, setFilterSelectClinic] = useState<SingleValue<OptionType>>(provincesList[0]);
Into the parent component. Then passing the setFilterSelectClinic function into the child component.
<SearchFilterClinics setFilterSelectClinic={(value) => setFilterSelectClinic(value)} isOpen={isOpen} closeModal={closeModal} clinicFilter={clinicFilter}/>
The value (within the parenthesis) is being passed up by the child component. This is the value you set here:
getClinic().then((response) => {
...
// this is the function we pass in from the parent. It set's the value
// of the callback function to provinceList
setFilterSelectClinic(provinceList);
...
We then setFilterSelectClinic to that value. Meaning filterSelectClinic now has the value passed up in the callback.

Change of icon in favorites

I try to change the icon and add it to favorites when I click on it. It works fine, my icon is changed but it impacts all my images icons instead of one. How can I fix this? Here is my code:
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState(false);
useEffect(() => {
...
}, []);
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => setFavorite(!favorite)}>
{favorite ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
Your problem is favorite state is only true/false value, when it's true, all images will have the same favorite value.
The potential fix can be that you should check favorite based on item.id instead of true/false value
Note that I added updateFavorite function for handling favorite state changes on your onClick
Here is the implementation for multiple favorite items
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState([]);
useEffect(() => {
...
}, []);
const updateFavorite = (itemId) => {
let updatedFavorite = [...favorite]
if(!updatedFavorite.includes(itemId)) {
updatedFavorite = [...favorite, itemId]
} else {
updatedFavorite = updatedFavorite.filter(favoriteItem => itemId !== favoriteItem)
}
setFavorite(updatedFavorite)
}
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => updateFavorite(item.id)}>
{favorite.includes(item.id) ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
Here is the implementation for a single favorite item
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState(); //the default value is no favorite item initially
useEffect(() => {
...
}, []);
const updateFavorite = (itemId) => {
let updatedFavorite = favorite
if(itemId !== updatedFavorite) {
updatedFavorite = itemId
} else {
updatedFavorite = null
}
setFavorite(updatedFavorite)
}
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => updateFavorite(item.id)}>
{favorite === item.id ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
This is what you're looking for based on the code you've provided:
import { useState, useEffect } from "react";
const Item = ({ item }) => {
const [favorite, setFavorite] = useState(false);
const toggleFavorite = () => setFavorite((favorite) => !favorite);
return (
<div>
<span>{item.title}</span>
<span>{item.description}</span>
<span onClick={toggleFavorite>
{favorite ? "[♥]" : "[♡]"}
</span>
</div>
);
};
const Row = () => {
const [items, setItems] = useState([]);
useEffect(() => {
// use setItems on mount
}, []);
return (
<div>
{items.map((item) => <Item item={item} key={item.id} />)}
</div>
);
};
export default Row;
You can break up your code into smaller components that have their own states and that's what I did with the logic that concerns a single item. I've created a new component that has its own state (favorited or not).

Delete an item on click

I am trying to write the very first to-do application in REACT. I want to add functionality to delete to-do item when the user clicks on the delete icon. When I click on delete icon it only removes the text. Here I would like to delete the entire item. Can someone please suggest?
App.js
import './App.css';
import { useState } from 'react';
import TodoList from './TodoList';
import { v4 as uuidv4 } from 'uuid';
function App() {
const [input, setInput] = useState('');
const [todos, setTodo] = useState([]);
const addTodo = (e) => {
e.preventDefault();
const id = uuidv4();
setTodo([...todos, { id: id, text: input}])
// setTodo({todos: [...todos, input], id })
setInput('');
}
const deleteTodo = (id) => {
console.log("id" + id);
const filteredItem = todos.filter(todo => todo.id !== id);
setTodo([filteredItem]);
}
return (
<div className="App">
<form>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)}/>
<button type="submit" onClick={addTodo}>Enter</button>
</form>
<TodoList todos={todos} deletetodo={deleteTodo}/>
</div>
);
}
export default App;
TodoList.js
import React from 'react'
import DeleteIcon from '#material-ui/icons/Delete';
import EditIcon from '#material-ui/icons/Edit';
const todo = ({todos, deletetodo}) => {
return (
<div>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<div>
<DeleteIcon onClick={(todo) => deletetodo(todo.id)}/>
<EditIcon/>
</div>
</li>
))}
</div>
)
}
export default todo;
There are a few problems with your code. I will start with the most obvious. You re-render your App on EVERY change of the input field. That's just unnecessary. So insated of storing the value of the input in a state variable, I would use useRef(). So you only really need one state variable, one that stores the list of todos.
Second, your filter is correct, but then you incorrectly set the state variable with the filtered result:
const filteredItem = todos.filter(todo => todo.id !== id);
setTodo([filteredItem]);
It will already return an array and there is no need to wrap it into another one.
With those 2 main issue fixed, here is a working example along with a Sandbox:
import React, { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import "./styles.css";
const TodoList = ({ todos, deletetodo }) => {
return (
<div>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<div>
<button onClick={() => deletetodo(todo.id)}>delete</button>
<button>edit</button>
</div>
</li>
))}
</div>
);
};
export default function App() {
const [todos, setTodo] = useState([]);
const input = React.useRef();
const addTodo = (e) => {
e.preventDefault();
const id = uuidv4();
setTodo([...todos, { id: id, text: input.current.value }]);
input.current.value = "";
};
const deleteTodo = (id) => {
setTodo(todos.filter((item) => item.id !== id));
};
return (
<div className="App">
<form>
<input ref={input} type="text" />
<button type="submit" onClick={addTodo}>
Enter
</button>
</form>
<TodoList todos={todos} deletetodo={deleteTodo} />
</div>
);
}
You have a mistake in how you're setting todo in deleteTodo:
const deleteTodo = (id) => {
console.log("id" + id);
const filteredItem = todos.filter(todo => todo.id !== id);
// Mistake! Your filteredItem is an array, you're putting your array into an array.
setTodo([filteredItem]);
}
Consequently, when you pass it further down, your component tries to get [filteredItem].text, which is undefined and React sees an empty string.
Fix:
setTodo(filteredItem);
There are multiple issues within the code:
First one is setting the values after deleting the row:
should be like this : setTodo(filteredItem);
Second issue was calling the onClick function, you already have the id with you so no need to re-call it again:
<div>
{todos.map(todoss =>
<li onClick={() => deletetodo(todoss.id)} key={todoss.id}>
{todoss.text}
</li>
)}
</div>

React, context remove item from cart

I have a demo here
Its a simple cart app where I'm listing products and then adding them to a cart components
It uses context for the app state.
I'd now like to be able to remove items from the cart but I'm struggling to get this to work.
Do I need to add a DeleteCartContext.Provider
import React, { useContext } from "react";
import { CartContext } from "./context";
import { IProduct } from "./interface";
const Cart = () => {
const items = useContext(CartContext);
const handleClick = (
e: React.MouseEvent<HTMLInputElement, MouseEvent>,
item: IProduct
) => {
e.preventDefault();
};
const cartItems = items.map((item, index) => (
<div>
<span key={index}>{`${item.name}: £${item.price}`}</span>
<input type="button" value="-" onClick={e => handleClick(e, item)} />
</div>
));
return (
<div>
<h2>Cart</h2>
{cartItems}
</div>
);
};
export default Cart;
There are many ways to control Items in Context so I tried to answer as similar to your structure, here is how you do that:
import React, { useContext } from "react";
import { CartContext, RemoveCartContext } from "./context";
import { IProduct } from "./interface";
const Cart = () => {
const items = useContext(CartContext);
const removeItem = useContext(RemoveCartContext);
const handleClick = (
e: React.MouseEvent<HTMLInputElement, MouseEvent>,
item: IProduct
) => {
e.preventDefault();
};
const cartItems = items.map((item, index) => (
<div>
<span key={index}>{`${item.name}: £${item.price}`}</span>
<input type="button" value="+" onClick={e => handleClick(e, item)} />
<input type="button" value="-" onClick={e => removeItem(item)} />
</div>
));
return (
<div>
<h2>Cart</h2>
{cartItems}
</div>
);
};
export default Cart;
import React, { createContext, useCallback, useRef, useState } from "react";
export const CartContext = createContext([]);
export const AddCartContext = createContext(item => {});
export const RemoveCartContext = createContext(item => {});
export const CartProvider = ({ children }) => {
const [items, setItems] = useState([]);
const itemsRef = useRef(items);
itemsRef.current = items;
return (
<AddCartContext.Provider
value={useCallback(item => {
setItems([...itemsRef.current, item]);
}, [])}
>
<RemoveCartContext.Provider
value={useCallback(item => {
const newItems = itemsRef.current.filter(
_item => _item.id !== item.id
);
setItems(newItems);
}, [])}
>
<CartContext.Provider value={items}>{children}</CartContext.Provider>
</RemoveCartContext.Provider>
</AddCartContext.Provider>
);
};
and here is a working demo: https://stackblitz.com/edit/react-ts-cart-context-mt-ytfwfv?file=context.tsx

Resources