React Typescript: different clickhandler on specific parts of same div - reactjs

I'm new to React and Typescript and Coding in general so I'm not sure if that what I'm trying to do is even possible. I have a donut chart with clickable segments. it's from a minimal pie chart: https://github.com/toomuchdesign/react-minimal-pie-chart.
So as you see the chart is round but the container is square. When I click on the segment I can check other statistics. but i want to reset it when I click on some empty place. Right now with the clickawaylistener from material UI or my own clickhandler i have to move the mouse outside of the square and can't just click next to the segments to reset since the clickaway is outside of the element. Any suggestions on how to solve this?
this is my chart with the onClick handler:
<PieChart
className={classes.chart}
onClick={handleSegment}
segmentsStyle={handleSegmentsCSS}
lineWidth={20}
label={handleChartLabels}
labelStyle={{
fontSize: "3px",
fontFamily: "sans-serif",
textTransform: "capitalize",
}}
labelPosition={115}
paddingAngle={5}
radius={30}
data={data}
animate
animationDuration={500}
animationEasing="ease-out"
/>;
And this my Clickhandler:
const handleSegment = (event: any, index: any) => {
const values = Object.values(SegementDataType).map((value, index) => ({
index,
value,
}));
setSegmentValue(values[index].value);
setStyles(segmentStyle);
setSelectedSegmentIndex(
index === selectedSegment ? undefined : index
);
};
And my Clickawaylistener is just a function to set initial values

Ok, honestly, I don't have a lot to work with (the link you posted on the comment is a not-working example).
Though, I managed to understand something from here: https://toomuchdesign.github.io/react-minimal-pie-chart/index.html?path=/story/pie-chart--full-option
Anyways, try to:
Wraps the <PieChart> component into an element (<div>, for instance).
Adds an event listener on that wrapper element.
In the listener, check if a path has been clicked or not. If not, you can deselect the item.
Something like this:
const divRef = useRef();
const handler = (e) => {
const divDOM = divRef.current;
const closestPath = e.current.closest('path');
if (closestPath != null && divDOM.contains(closestPath)) {
// Here, a segment has been clicked.
}
else {
// Here, a segment has NOT been clicked.
}
}
return (
<div onClick={handler} ref={divRef}>
<PieChart ... />
</div>
);
I also check that divDOM contains closestPath so that we are sure we are talking about a path belonging to the <PieChart>.
Though, this solution does not fix the problem that, INSIDE the <PieChart> component, the segment remains clicked. I don't think this can be fixed because of the implementation of the chart (it's a stateful component, unfortunately).
What you can try is to mimic a click on the selected path, but I don't think it will work

Related

trying to add two seperate functions, each one have a button targeting one component react JS

im trying to give the jsx element a background, and i have two functions, function one gives the background gray and the second function gives it green.. so far i managed to add the two functions to the jsx elemnt through onClick however, when clicking both buttons it only run the green function
const [onTwo, SetTwo] = React.useState(false);
function toggleTwo() {
SetTwo((prevstate) => !prevstate);
}
const [green, setGreen] = React.useState(false);
const color = {
backgroundColor: green ? "#94D7A2" : "transparent",
};
function checkAnswers() {
setGreen(true);
}
return (
<h2
className="answer-two"
style={{ ...stylesTwo, ...color }}
onClick={() => {
toggleTwo();
checkAnswers();
}}
>
{props.answertwo}{" "}
</h2>
);
you set the background color, but in your case, I guess you need to toggle this function toggleTwo on every click and change that function to
function toggleTwo(){
SetTwo(!onTwo)
}
then that state will interchange between true and false every time u click
here is my working example I change one of backgroundColor to color to confirm its working,
https://codesandbox.io/s/wizardly-mclean-vgiukl?file=/src/App.js

ReactJS, key property breaks CSS transition while adding class

When I'm adding key property to the following code it breaks my CSS transition by removing class 'active' after re-rendering.
Without a key property everything works great, can somebody explain why this happens?
handleClick function is adding class 'active' on clicked element:
const handleClick = (e) => {
const {currentTarget: btn} = e;
const getAllActive = selAll('.btn-filter.active');
setActiveFilter(getAllActive);
}
Component with onClick handler and key prop:
<Splide options={{
gap: '8px',
perMove: 1,
}}>
{_.map(taxonomyServiceData, ({name, id}) => {
return (
<SplideSlide key={_.uniqueId()}>
<FilterButton onClick={(e) => {
handleClick(e);
}} categoryId={id} buttonText={name}/>
</SplideSlide>
);
})}
</Splide>
Thanks in advance!
This documentation describes exactly what the issue is: Lists and Keys
TLDR: Keys should be given to the elements inside the array to give the elements a stable identity, and the key needs to be exactly the same for the item as it was before. Using _.uniqueId() will wipe out any previous keys that existed so React will 100% rerender it.
Another major issue your code has: you should not modify classes outside React unless you have absolutely no access to the thing you're trying to modify.
This link describes how to set class names in React: FAQ: Styling

React scroll to element (ref current is null) problem

I have a problem I'm not able to solve. The app got a component where a do looping array and making multiple elements off it. Then I want to make buttons in another component that will scroll to a specific element. (something similar to liveuamap.com when you click on a circle).
I tried the below solution, but got "Uncaught TypeError: props.refs is undefined". I could not find any solution to fix it.
The second question: is there a better or different solution to make scrolling work?
In app component I creating refs and function for scrolling:
const refs = DUMMY_DATA.reduce((acc, value) => {
acc[value.id] = React.createRef();
return acc;
}, {});
const handleClick = (id) => {
console.log(refs);
refs[id].current.scrollIntoView({
behavior: "smooth",
block: "start",
});
};
The refs I send to the article component as a prop where I render elements with generated refs from the app component.
{props.data.map((article) => (
<ContentArticlesCard
key={article.id}
ref={props.refs[article.id]}
data={article}
onActiveArticle={props.onActiveArticle}
activeArticle={props.activeArticle}
/>
))}
The function is sent to another component as a prop where I create buttons from the same data with added function to scroll to a specific item in the article component.
{props.data.map((marker) => (
<Marker
position={[marker.location.lat, marker.location.lng]}
icon={
props.activeArticle === marker.id ? iconCircleActive : iconCircle
}
key={marker.id}
eventHandlers={{
click: () => {
props.onActiveArticle(marker.id);
// props.handleClick(marker.id);
},
}}
></Marker>
))}
Thanks for the answers.
Ok so i found the solution in library react-scroll with easy scroll implementation.

Moving slider with Cypress

I've got a Slider component from rc-slider and I need Cypress to set the value of it.
<Slider
min={5000}
max={40000}
step={500}
value={this.state.input.amount}
defaultValue={this.state.input.amount}
className="sliderBorrow"
onChange={(value) => this.updateInput("amount",value)}
data-cy={"input-slider"}
/>
This is my Cypress code:
it.only("Changing slider", () => {
cy.visit("/");
cy.get(".sliderBorrow")
.invoke("val", 23000)
.trigger("change")
.click({ force: true })
});
What I've tried so far does not work.
Starting point of slider is 20000, and after test runs it goes to 22000, no matter what value I pass, any number range.
Looks like it used to work before, How do interact correctly with a range input (slider) in Cypress? but not anymore.
The answer is very and very simple. I found the solution coincidentally pressing enter key for my another test(date picker) and realized that pressing left or right arrow keys works for slider.
You can achieve the same result using props as well. The only thing you need to do is to add this dependency: cypress-react-selector and following instructions here: cypress-react-selector
Example of using {rightarrow}
it("using arrow keys", () => {
cy.visit("localhost:3000");
const currentValue = 20000;
const targetValue = 35000;
const increment = 500;
const steps = (targetValue - currentValue) / increment;
const arrows = '{rightarrow}'.repeat(steps);
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 20000)
.type(arrows)
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 35000)
})
#darkseid's answer helped guide me reach an optimal solution.
There are two steps
Click the slider's circle, to move the current focus on the slider.
Press the keyboard arrow buttons to reach your desired value.
My slider jumps between values on the sliders, therefore this method would work. (I am using Ion range slider)
This method doesn't require any additional depedency.
// Move the focus to slider, by clicking on the slider's circle element
cy.get(".irs-handle.single").click({ multiple: true, force: true });
// Press right arrow two times
cy.get(".irs-handle.single").type(
"{rightarrow}{rightarrow}"
);
You might be able to tackle this using Application actions, provided you are able to modify the app source code slightly.
Application actions give the test a hook into the app that can be used to modify the internal state of the app.
I tested it with a Function component exposing setValue from the useState() hook.
You have used a Class component, so I guess you would expose this.updateInput() instead, something like
if (window.Cypress) {
window.app = { updateInput: this.updateInput };
}
App: index.js
import React, { useState } from 'react';
import { render } from 'react-dom';
import './style.css';
import Slider from 'rc-slider';
import 'rc-slider/assets/index.css';
function App() {
const [value, setValue] = useState(20000);
// Expose the setValue() method so that Cypress can set the app state
if (window.Cypress) {
window.app = { setValue };
}
return (
<div className="App">
<Slider
min={5000}
max={40000}
step={500}
value={value}
defaultValue={value}
className="sliderBorrow"
onChange={val => setValue(val)}
data-cy={"input-slider"}
/>
<div style={{ marginTop: 40 }}><b>Selected Value: </b>{value}</div>
</div>
);
}
render(<App />, document.getElementById('root'));
Test: slider.spec.js
The easiest way I found assert the value in the test is to use the aria-valuenow attribute of the slider handle, but you may have another way of testing that the value has visibly changed on the page.
describe('Slider', () => {
it("Changing slider", () => {
cy.visit("localhost:3000");
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 20000)
cy.window().then(win => {
win.app.setValue(35000);
})
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 35000)
})
})
For whoever comes across this with Material UI/MUI 5+ Sliders:
First off, this github issue and comment might be useful: https://github.com/cypress-io/cypress/issues/1570#issuecomment-606445818.
I tried changing the value by accessing the input with type range that is used underneath in the slider, but for me that did not do the trick.
My solution with MUI 5+ Slider:
<Slider
disabled={false}
step={5}
marks
data-cy="control-percentage"
name="control-percentage"
defaultValue={0}
onChange={(event, newValue) =>
//Handle change
}
/>
What is important here is the enabled marks property. This allowed me to just click straight on the marks in the cypress test, which of course can also be abstracted to a support function.
cy.get('[data-cy=control-percentage]').within(() => {
// index 11 represents 55 in this case, depending on your step setting.
cy.get('span[data-index=11]').click();
});
I got this to work with the popular react-easy-swipe:
cy.get('[data-cy=week-picker-swipe-container]')
.trigger('touchstart', {
touches: [{ pageY: 0, pageX: 0 }]
})
.trigger('touchmove', {
touches: [{ pageY: 0, pageX: -30 }]
})

Material UI - Unblock scrolling when popover is opened

The scroll is blocked with Popover according to the new material-ui version doc.
When i open the popover, the scroll-bar of the web page suddenly disappeared and it's not the part of user experience in my opinion.
I wanna keep the scroll bar visible while popover is open.
I'm using Material-UI V3.8.1.
it can be fixed by using container props of Popover.
container props is a node, component instance or function that returns either.
The container will passed to the Modal component.
By default, it uses the body of the anchorEl's top-level document object, so it's simply document.body most of the time.
This default setting is making document removing scroll bar.
So i just used its direct parent for container instead of default setting and it solved the problem. :)
<Popover
open={...}
anchorEl={...}
anchorOrigin={...}
container={this.AnchorEl.parentNode}
>
Thanks
The property you are looking for is called disableScrollLock. But using this, does not recalculate the modals positioning and you will have a floating modal in your application. Instead, as described by a few others here, the Popper should be used.
To implement the correct behavior you can use Popper with Clickawaylistener
According to the docs, it looks like if you want to retain the scroll bar, then you should use Popper instead of Popover.
I stumbled upon this question when trying to fix the same problem. Improving upon #Tommy's answer, I've found a working solution:
const NewPopover = (props) => {
const containerRef = React.useRef();
// Box here can be any container. Using Box component from #material-ui/core
return (
<Box ref={containerRef}>
<Popover {...props} container={containerRef.current}>
Popover text!
</Popover>
</Box>
);
};
Just tried something now that seems to work.
Use a Popper instead I have done something like this.
<Popper
id={id}
open={popoverOpen}
anchorEl={anchorEl}
placement="top-start"
disablePortal={false}
modifiers={{
flip: {
enabled: false
},
preventOverflow: {
enabled: true,
boundariesElement: "scrollParent"
}
}}
>
Then to get the click away desired effect mount a "mousedown" effect check if the popover is open if it is then check the click is inside of the popper element. If not close the modal.
Something like this for the mousedown effect, note remember to dismount it or you'll get a leak.
React.useEffect(() => {
if (anchorEl) {
document.addEventListener("mousedown", handleClick)
} else {
document.removeEventListener("mousedown", handleClick)
}
// Specify how to clean up after this effect:
return function cleanup() {
document.removeEventListener("mousedown", handleClick)
}
}, [anchorEl])
const handleClick = event => {
if (!document.getElementById(id).contains(event.target)) {
setAnchorEl(null)
setCustomOpen(false)
}
}
Also just incase you are unsure theres another difference with Popper and Popover and that's just adding a Paper, but i don't need to explain how to do that.
I found another Solution that fits my case:
Just added a useEffect and attached a mouse wheel attempt to it close the popover:
function myPopover({/*..your props..*/}){
const [open,setOpen] = useState(flase);
//.... your rest of the code
function handleClose(){
setOpen(false);
}
useEffect(()=>{
if(open){
document.body.onwheel = handleClose;
document.body.addEventListener('touchstart', handleClose, false);
}
return ()=>{
document.body.onwheel = undefined;
document.body.removeEventListener('touchstart', handleClose,
false);
}
},[open])
return <Popover
open={open}
onClose={handleClose}
anchorEl={...}
anchorOrigin={...}
/*... your props */
>
}
I solved this with
html {
overflow: visible !important;
}
Since Material-ui adds style="overflow: hidden;" on the html tag.

Resources