Inside a component, there is a mapping for creating multiple elements. Each element can be deleted if a button is clicked:
const ParentComponent = () => {
...
const [fields, setFields] = useState([{value: 'a', editable: false},
[{value: 'b', editable: true},
[{value: 'c', editable: false}]);
const handleRemoveField = (id) => {
const values = [...fields];
values.splice(id, 1);
setFields(values);
}
...
return (
...
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
</div>
);
})}
...
);
The above code is working fine. The problem comes up when the delete part must be done from a modal component instead of directly clicking the button.
So I created a new Modal component:
const DeleteModal = ({ isDeleteModalOpen, closeDeleteModal }) => {
return (
<Modal isOpen={isDeleteModalOpen}>
<ModalTitle handleClose={closeDeleteModal} />
<button onClick={deleteElement}> OK </button>
<button onClick={closeDeleteModal}> cancel </button>
</Modal>
);
};
And inside ParentComponent, DeleteModal was imported. When handleRemoveField is called, instead of directly deleting the element it is opening the modal.
What I don't know how to do is to delete de element when OK button from modal is clicked (deleteElement function from modal should do that).
Modified ParentComponent:
const ParentComponent = () => {
...
const [fields, setFields] = useState([{value: 'a', editable: false},
[{value: 'b', editable: true},
[{value: 'c', editable: false}]);
const handleRemoveField = (id) => { // modified the method to call modal
openDeleteModal(id);
};
// added for open/close modal
const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);
const openDeleteModal = () => {
setDeleteModalOpen(true);
};
const closeDeleteModal = () => setDeleteModalOpen(false);
...
return (
...
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
// imported modal here:
<DeleteModal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal} />
</div>
);
})}
...
);
The cancel button is working, it closes the modal. The problem is with OK button that must delete the element.
How can that be done?
You can add a new prop to your Modal components, maybe something along the lines of onDelete. Than you can pass a method which deletes your element to the Modal like so:
<Modal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal}
onDelete={() => deleteElement(id)}
/>
Inside your Modal component you can call your onDelete prop to call deleteElement(id):
const DeleteModal = ({ isDeleteModalOpen, closeDeleteModal, onDelete }) => {
return (
<Modal isOpen={isDeleteModalOpen}>
<ModalTitle handleClose={closeDeleteModal} />
<button onClick={onDelete}> OK </button>
<button onClick={closeDeleteModal}> cancel </button>
</Modal>
);
};
So long story short: You can pass down a method with your child component to call it from there.
You could save the ID that is going to be deleted before open modal, and use it to decide if modal is visible or not, as well.
const [idToDelete, setIdToDelete] = useState(null);
const handleRemoveField = (id) => {
setIdToDelete(id);
};
...
<DeleteModal isDeleteModalOpen={idToDelete !== null} closeDeleteModal={closeDeleteModal} />
you use an variable to save which item you are deleting
const [itemDel,setItemDel] = React.useState(null);
then, when click edit to show modal, you set that item to itemDel ( in handleRemoveField function)
something like
return (
<>
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
</div>
);
})}
{
!!itemDel&& <DeleteModal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal} />
}
</>
);
and, when you done, or want to hide modal, just call setItemDel(null)
Related
I have a parent component with a handler function:
const folderRef = useRef();
const handleCollapseAllFolders = () => {
folderRef.current.handleCloseAllFolders();
};
In the parent, I'm rendering multiple items (folders):
{folders &&
folders.map(folder => (
<CollapsableFolderListItem
key={folder.id}
name={folder.name}
content={folder.content}
id={folder.id}
ref={folderRef}
/>
))}
In the child component I'm using the useImperativeHandle hook to be able to access the child function in the parent:
const [isFolderOpen, setIsFolderOpen] = useState(false);
// Collapse all
useImperativeHandle(ref, () => ({
handleCloseAllFolders: () => setIsFolderOpen(false),
}));
The problem is, when clicking the button in the parent, it only collapses the last opened folder and not all of them.
Clicking this:
<IconButton
onClick={handleCollapseAllFolders}
>
<UnfoldLessIcon />
</IconButton>
Only collapses the last opened folder.
When clicking the button, I want to set the state of ALL opened folders to false not just the last opened one.
Any way to solve this problem?
You could create a "multi-ref" - ref object that stores an array of every rendered Folder component. Then, just iterate over every element and call the closing function.
export default function App() {
const ref = useRef([]);
const content = data.map(({ id }, idx) => (
<Folder key={id} ref={(el) => (ref.current[idx] = el)} />
));
return (
<div className="App">
<button
onClick={() => {
ref.current.forEach((el) => el.handleClose());
}}
>
Close all
</button>
{content}
</div>
);
}
Codesandbox: https://codesandbox.io/s/magical-cray-9ylred?file=/src/App.js
For each map you generate new object, they do not seem to share state. Try using context
You are only updating the state in one child component. You need to lift up the state.
Additionally, using the useImperativeHandle hook is a bit unnecessary here. Instead, you can simply pass a handler function to the child component.
In the parent:
const [isAllOpen, setAllOpen] = useState(false);
return (
// ...
{folders &&
folders.map(folder => (
<CollapsableFolderListItem
key={folder.id}
isOpen={isAllOpen}
toggleAll={setAllOpen(!isAllOpen)}
// ...
/>
))}
)
In the child component:
const Child = ({ isOpen, toggleAll }) => {
const [isFolderOpen, setIsFolderOpen] = useState(false);
useEffect(() => {
setIsFolderOpen(isOpen);
}, [isOpen]);
return (
// ...
<IconButton
onClick={toggleAll}
>
<UnfoldLessIcon />
</IconButton>
)
}
I have a list of task which inside have another task(subTask).
I use a map to list all the tasks and have a button to add more subtasks to that task.
The input button is supposed to be invisible unless the button is pressed.
The problem is that I used the useState hook to conditional render the input but whenever I click in any of the button, the inputs of all tasks open and is supposed to only open the input from that certain index.
const Task = () => {
const [openTask, setOpenTask] = useState(false);
return task.map((item, index) => {
return (
<div>
<button onClick={() => setOpenTask(!openTask)}>Add</button>
{item.name}
{openTask && <input placeholder="Add more tasks..."/>}
{item.subTask.map((item, index) => {
return (
<>
<span>{item.name}</span>
</>
);
})}
</div>
);
});
};
Try using array
const Task = () => {
const [openTask, setOpenTask] = useState([]);
const openTaskHandler = (id) => {
setOpenTask([id])
}
return task.map((item, index) => {
return (
<div>
<button onClick={() => openTaskHandler(item.id)}>Add</button>
{item.name}
{openTask.includes(item.id) && <input placeholder="Add more tasks..."/>}
{item.subTask.map((item, index) => {
return (
<>
<span>{item.name}</span>
</>
);
})}
</div>
);
});
};
That's because you are setting openTask to true for all the items. You can create object and store the specific id to true.
for eg.
const [openTask, setOpenTask] = useState({});
and then on button onClick you can set the specific id to open
setOpenTask({...openTask,[`${item.name}-${index}`]:true});
and then check against specific id
{openTask[`${item.name}-${index}`] && <input placeholder="Add more tasks..."/>}
So I'm doing a list in which you can add items. When you add them you have two options:
Delete the whole list
Delete a specific item.
But for some reason the "handeDelete" button is not working. Can somebody tell me what did I write wrong in the code?
The link to CodeSandbox is:
codesandbox
import { useState } from "react";
import uuid from "react-uuid";
export default function ItemList() {
const [items, setItems] = useState({ item: "" });
const [groceryList, setGroceryList] = useState([]);
function handleChange(value, type) {
setItems((prev) => {
return { ...prev, [type]: value };
});
}
function handleSubmit(e) {
e.preventDefault();
const newItem = { ...items, id: uuid() };
setGroceryList([...groceryList, newItem]);
setItems({ item: "" });
}
function handleDelete(id) {
setGroceryList(groceryList.filter((items) => items.id !== id));
}
return (
<>
<form autoComplete="off" onSubmit={handleSubmit}>
<input
type="text"
name="item"
id="item"
value={items.item}
onChange={(e) => handleChange(e.target.value, "item")}
/>
</form>
{groceryList.map((list) => {
return (
<div key={list.id}>
<ul>
<li> {list.item}</li>
</ul>
<button onClick={(id) => handleDelete()}>Delete</button>
</div>
);
})}
<button onClick={() => setGroceryList([])}>Clear</button>
</>
);
}
Your delete button definition is wrong:
<button onClick={() => handleDelete(list.id)}>Delete</button>
the parameter you are receiving from the click event is not the id. Since you are not working with the event args itselfy you can safely ignore it. The second mistake was, that you are not passing the id itself to your handleDelete function.
For learning purposes, humor yourself and print the event to the console, while developing:
<button onClick={(evt) => {
console.log(evt)
handleDelete(list.id)
}}>
Delete
</button>
This will show you, that the parameter, that you named id (and I renamend to evt), is in fact reacts Synthetic Event: https://reactjs.org/docs/events.html
I have a Tooltip component which is a wrapper for dynamic content.
I am trying to use it as a popup, which will have Delete and Cancel buttons.
I am passing the delete and cancel button as children prop, the problem is cus Close and Open state is in the Tooltip component.
I need to attach the close function on Cancel button which lives in Children prop.
Need help to find an elegant solution to this.
Tooltip component:
export const Tooltip: FC<TooltipProps> = ({
content,
helperText,
children,
...props
}) => {
const [visible, setVisible] = useState(false);
const show = () => setVisible(true);
const hide = () => setVisible(false);
return (
<div>
<Tippy
content={content}
visible={visible}
onClickOutside={hide}
interactive
{...props}
>
<div onClick={visible ? hide : show}>
// =====>>> **Close button which be in children prop, need to attach hide() function**
{children}
</div>
</Tippy>
</div>
);
};
This is Call of Tooltip component and passing buttons as Children:
<Tooltip
content={
<div className="popover-buttons">
// Need to attach here **hide()** function from Tooltip coomponent
<button>
Cancel
</button>
<button>
Delete
</button>
</div>
</Tooltip>
You can make the content prop passed to Tippy a component that has hide() function passed to it as a prop
export const Tooltip: FC<TooltipProps> = ({
content: Content,
helperText,
children,
...props
}) => {
const [visible, setVisible] = useState(false);
const show = () => setVisible(true);
const hide = () => setVisible(false);
return (
<div>
<Tippy
content={<Content hide={hide} />}
visible={visible}
onClickOutside={hide}
interactive
{...props}
>
<div onClick={visible ? hide : show}>
// =====>>> **Close button which be in children prop, need to attach hide() function**
{children}
</div>
</Tippy>
</div>
);
};
Then you have:
<Tooltip
content={ ({ hide }) =>
<div className="popover-buttons">
// Need to attach here **hide()** function from Tooltip coomponent
<button onClick={hide}>
Cancel
</button>
<button>
Delete
</button>
</div>
</Tooltip>
I have a parent component and some child components and I want send function from parent to child and in child component call it.
parent:
const buttons = [
{
key: 'driveFormulaButton',
title: 'Drive Formula',
icon: faGitAlt,
type: 'primary',
function: (message) => {
console.log('fasf');
},
},
];
return (
<Child
buttons={buttons}
></Child>
);
Child Component:
const Child = (props) => {
return (
<Button size="small" type={props.buttons.type} onClick={props.buttons.function('test')}> //not work after set propery
<FontAwesomeIcon icon={props.buttons.icon} />
</Button>
);
});
You call the function instead of passing it an onClick callback and you should map the buttons array:
const Child = (props) => {
return props.buttons.map((prop) => (
<Button
key={prop.key}
size="small"
type={prop.type}
onClick={() => prop.function("test")}
>
<FontAwesomeIcon icon={prop.icon} />
</Button>
));
}
Your parent component needs to map through the children components:
Parent
function renderChildren() {
const buttons = []; // <--- populate this as in your OP
return buttons.map((button) => {
return (
<Button size="small" type={button.type} onClick={() => button.function('test')}>
<FontAwesomeIcon icon={button.icon} />
</Button>
)
})
}
return (
<>
{renderChildren()}
</>
);
Well you send an array of buttons as props to you single Child-Component. Then you try to access a single object of that array in the Button-Component. This cant work. Better map throught you button in the parent and send a single button down as props:
const buttons = [{*Button1*, *Button2*}]
render(
{buttons.map((button) => <Child button={button}>)}
)
Then you can access the button as you intend to in your Child-component.
Next time you ask a question please provide better information to what you are actually trying to do and what the problem (e.g. error message) is. Ask specific questions. This makes it easier to answer you.