I have been trying to implement a drag and drop functionality using the react-beautiful-dnd library.
I am displaying some buttons from the initial state of redux and trying to make those items draggable.
But, for some reasons,I am having an error in console saying that "Unable to find draggable element with id: " when trying to move the button elements. This is for the first time,I am trying to implement such functionalities and not sure where I have done the mistake. I know that the Droppable wrapper should have a droppableId but based on the structure of my state data, how can I pass the id?
Here is my, ListItems components that I have modified to use the drag and drop logic:
import React from "react";
import { connect } from "react-redux";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
const ListItems = props => {
const onDragEnd = result => {
console.log("drag end");
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<div>
List Items
<Droppable>
{provided => (
<div innerRef={provided.innerRef} {...provided.droppableProps}>
{props.items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{provided => (
<div
className="user-lists"
key={item.id}
{...provided.draggableProps}
{...provided.dragHandleProps}
innerRef={provided.innerRef}
>
<button>{item.name}</button>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
</DragDropContext>
);
};
const mapStateToProps = state => ({
items: state.items.buttons
});
export default connect(
mapStateToProps,
{}
)(ListItems);
and then the ListItems component is used in App component.
For complete demo here is the sandbox link:
https://codesandbox.io/s/adoring-jones-roswh?file=/src/components/ListItems.js:0-1298
So, I fixed that problem and draggbale is working accordingly. But, now the problem is that I can't move the item by clicking or selecting the button elements rather I need to drag the button from the right side. probably because of the div element that wraps the buttons are handling the dragging. Any fixes to make the buttons draggable, would be nice..
Related
I have created a functionality for my "menu item buttons" to display their assigned data through an object format into a separate page component called "SidePage" (gained help also here in StackOverflow). What I am not sure is that if this functionality that I have created is a formal or effective one since I am planning to implement a backend functionality into my full main app. The functionality involves useState, onClick, and onSelected.
I used props to hold the resulting data after the "menu item button" is clicked.
(Chicken Button should be clicked first in order to display the menu item buttons)
SidePage source code:
import * as React from "react";
import { Typography } from "#mui/material";
export default function SidePage(props) {
return (
<div>
<Typography sx={{ mt: 20 }}>Menu Item Details:</Typography>
<div>{props.menuItemDetails}</div>
</div>
);
}
HomeOrderPage (main page) source code:
import * as React from "react";
import { useState } from "react";
import { Stack } from "#mui/material";
import ButtonCategoryStyle from "./ButtonCategoryStyle";
import ChickenButtons from "./categoryButtons/ChickenButtons";
import SidePage from "./SidePage";
const categories = ["Chicken"];
export default function HomeOrderPage() {
const [myCategory, setMyCategory] = useState("");
const [food, setFood] = useState(null);
return (
<div>
<Stack spacing={0} direction="row">
{categories.map((category) => (
<ButtonCategoryStyle
title={category.toLocaleUpperCase()}
key={category}
onClick={() => setMyCategory(category)}
/>
))}
</Stack>
<div>
<p>
{myCategory === "Chicken" && <ChickenButtons onSelected={setFood} />}
</p>
{/* ------------------------------------------------------------------------------------------------ */}
</div>
{/* Displays object after menu item is clicked and renders the side page to show the menu item details:: */}
<div
style={{
backgroundColor: "blue"
}}
>
<SidePage
menuItemDetails={
food && <pre sx={{ ml: 120 }}>{JSON.stringify(food, null, 2)}</pre>
}
/>
</div>
</div>
);
}
Full source code (CodeSandbox): https://codesandbox.io/s/nice-stack-question-page-component-data-transfer-ejebxz?file=/src/HomeOrderPage.jsx
What I also want is to store the property values of the object into variables (in order to display the details of the selected menu item button properly but I am not sure how to do this since I am baffled with using this.state or still using props for this.
Hoping for all of your response as this would help me a lot with my first big React project that I am working on. Thank you very much everyone!
Since you already have props. Why copy it to state? Just keep single source of truth.
checkResult is a helper function which is imported in my component.jsx
component.jsx
return(
<ul>
{options.map((option) => {
return (
<li key={option.value}>
<button
data-testid="unlock-btn"
onClick={() => {
checkResult()
? lunch(option.value)
: showError();
}}
>
{option.label}
</button>
</li>
);
})}
</ul>;
)
my test
import * as helper from "../helpers/checkResult";
it("fires action when lunch is clicked", async () => {
const spy = jest.spyOn(helper, 'checkResult');
let component;
await act(async()=>{
component = <component /> ;
})
await expect(screen.queryByTestId("unlock-btn"));
fireEvent.click(screen.queryByTestId("unlock-btn"));
expect(spy).toHaveBeenCalled();
});
this is the error i'm getting
Unable to fire a "click" event - please provide a DOM element.
i have also provided my getComponent Method above
You're not providing options to the component so it has nothing to render. You're also using a map to render a list of items all of which have the same id. You should do something like
map((option, index) => {
return (
<li key={option.value}>
<button
data-testid=`unlock-btn-${index}`
This way you can target each individual option by ID in your test.
Edit: Your fireEvent is not defined in your example either.
The right way would be using the aria-label and attributes to be able to select those buttons without the need of data-testid.
<button
onClick={() => { checkResult() ? lunch(option.value): showError();}}
name={option.label} // assuming the labels are unique
>
{option.label}
</button>
then:
import React from 'react';
import { render, screen, fireEvent } from '#testing-library/react';
it('Should do some test', ()=>{
render(<MyComponent/>)
const button = screen.getByRole('button', {name: "some-label"})
fireEvent.click(button)
expect(....).toBe(...)
}
I'm trying to create rich React component as popover content.
If I use example with simple const popover (https://react-bootstrap.netlify.app/components/overlays/#examples-1) everything works fine.
Problem
But custom react component fails to position itself. It appears on top left of the screen
const MyPopover = React.forwardRef((props, ref) => {
return <Popover ref={ref} placement={"bottom"}>
<Popover.Header as="h3">
<Form.Control></Form.Control>
</Popover.Header>
<Popover.Body>
<strong>Holy guacamole!</strong> Check this info.
</Popover.Body>
</Popover>
})
const PopoverChooser = ({children, container}) => {
const _refTarget = useRef(null)
return <>
<OverlayTrigger
trigger="click"
placement="bottom"
overlay={<MyPopover ref={_refTarget}/>}
target={_refTarget.current}
>
{children}
</OverlayTrigger>
</>
}
export default PopoverChooser;
As you can see, I'v tried to use ref's, but it's doesn't help.
Question
How can it link popover to target button (in image as dropdown button and in code as {children}).
Or should I position MyPopover manually (by checking button ID and assigning position.. etc.?)
Completely different approach to dig in..?
Your approach to forward the ref was right. What you actually forgot is to also inject props. According to the documentation:
The and components do not position themselves.
Instead the (or ) components, inject ref and
style props.
https://react-bootstrap.netlify.app/components/overlays/#overview
So what you need to do is to spread the props like this:
const MyPopover = React.forwardRef((props, ref) => {
return (
<Popover ref={ref} {...props}>
https://codesandbox.io/s/trusting-sid-0050g9
I got a OnClick which actually receives an id:
<Button onClick={() => addToCart(id)} >Buy</Button>
On the other hand, in a different JS file,I got a modal which appears with a click via useState:
const [stateModal1, changeModalState1] = useState(false);
Now, in the same component I work with this modal, I map an array which returns a Button, which now is working with the "addToCart(id)" value mentioned before, like this:
{products.map((product) => {
return <Product image={product.image}
key={product.id}
data={product}
addToCart={() =>addToCart(product.id)} />})}
The question that is driving me crazy is: how can I use the button in the mapped array to trigger that modal, and at the same time, to pass values to that modal in order to show the mapped item IN that modal?
Thanks in advance.
EDIT: this is the modal, which is another component:
const Modal = ({
children,
state,
stateModal1,
})
return (
<>
{state &&
<Overlay>
<Container>
<CloseButton onClick={() => changeState(false)}>{FaWindowClose}</CloseButton >
{children}
<Header>
<h3>Confirm buy</h3>
<h4>{name}</h4>
<h4>$ {price}</h4>
</Header>
<Button onClick={() => changeState(false)}>Confirm</Button>
</Container>
</Overlay>
}
</>)
PS: the "confirm" button which triggers the "changeState()", should also trigger the addToCart().
As mentioned by other comments above, you can pass a prop to the modal component from the parent component to achieve your demand normally.
The only thing that needs to be done is set the open/close modal state and the passing data state at the same time, or, probably use one state directly
sample of the code:
import "./styles.css";
import "antd/dist/antd.css";
import { useState } from "react";
import { Modal } from "antd";
export default function App() {
// init with undefined, if not undefined, open the modal
const [modal, setModal] = useState(undefined);
const list = [...Array(20).keys()];
// set the state to open the modal, as well as pass it to the modal itself as a prop if necessary
const handleClick = (idx) => () => {
setModal(idx);
};
return (
<div className="App">
{list.map((x, idx) => (
<div style={{ border: "1px solid black" }} onClick={handleClick(idx)}>
{x}
</div>
))}
<Modal visible={modal !== undefined}>The value you passed: {modal}</Modal>
</div>
);
}
the online demo could be found here: https://codesandbox.io/s/hardcore-shape-89y78?file=/src/App.js
I'm using React and React-Spring to animate a questionnaire app.
I want the questionnaire to animate the leaving/enter of a question when the user answer one.
I'm using React for the app and try to use React-Spring to animate the transitions. The issue is that when the user is answering a question, the question component is updated with the new content before it leaving.
To simplify it, the Question component look like this:
export default function Question({question, onAnswer}) {
const [answer, setAnswer] = useState(null);
return (
<animated.div ...>
{question.title}
<select>...{quesiton.options}...</select>
<button onClick={() => onAnswer(question.id, answer)}>Next</button>
</animated.div>
);
}
I create a Code Sandbox that illustrates my issue:
https://codesandbox.io/s/nostalgic-swartz-lhkj0?file=/src/AnimatedComponent.js
How should I handle this? couldn't find any example on the web
Thanks!
You should use the item property in the transition map instead of using the text directly in the animated.div.
{transitions.map(
({ item, key, props }) =>
item && (
<animated.div key={key} style={props}>
{item}
</animated.div>
)
)}