ReactJS, key property breaks CSS transition while adding class - reactjs

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

Related

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.

React Typescript: different clickhandler on specific parts of same div

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

Deleting Note From Array

Current I am working on a note app using React.Js and I had the functionality for saving notes but then I decided to add the functionality to remove the note when it is clicked. I wrote some code however, now it won't work. I can't add notes display them. Usually when you click on the button is was suppose to add the note which it was doing before I tried adding the deleting the note functionality. There is also no error in anywhere. I have been trying to solve this the past few days. Here is my code:
The problem is that the id is undefined in your NoteItem component. You are only passing the title to the component. You should pass the note object and then you can have access to the id and title. Suggested changes below:
NotesList
const NotesList = (props) => {
return props.data.map((note) => {
return (
<NoteItem
note={note}
key={note.id}
onDelete={props.onDeleteItem}
>
</NoteItem>
);
})
}
and change NoteItem component to access note as an object. (You could also pass title/id individually as props as well.
NoteItem:
const NoteItem = props => {
const deleteHandler = () => {
props.onDelete(props.note.id);
}
return (
<h3 onClick={deleteHandler}>{props.note.title}: {props.note.id}</h3>
)
}

What is the best approach for adding class and styles without re-render - reactjs

I am building a slider and need assistance with the "best" way of implementing the feature. I have a Slider Component which receives children of SliderItems. I clone the children in Slider Component and add props. When the user clicks next or previous button I use a state isAnimating to determine if the slider is moving and add/remove styles based on isAnimating state but it was causing a re-render of slider items. I need to add animating class without causing a re-render to the enter slide items. Is there a way to implement such feature?
SliderContainer.js
<Slider totalItems={totalItems} itemsInRow={itemsInRow} enableLooping={true} handleSliderMove={handleSliderMove}>
{items.map((item) => {
return <SliderItem key={\`${item.id}\`} data={item} />;
})}
</Slider>
Slider.js
const onSliderControlClick = (direction) => {
const [newIndex, slideOffset] = sliderMove(direction, lowestVisibleIndex, itemsInRow, totalItems);
setisAnimating(true); //Causes rerender
movePercent.current = slideOffset();
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
setisAnimating(false);
setHasMovedOnce(true);
setLowestVisibleIndex(newIndex());
});
}, 750);
};
<div ref={sliderContent} className={`slider-content`} style={getReactAnimationStyle(baseOffset)}>
React.Children.map(children, (child, i) =>
React.cloneElement(child, {
key: child.props.video.id,
viewportIndex: properties.viewportIndex,
viewportPosition: properties.viewportPosition,
})
);
})
</div>
use node-sass library. And make styles.modules.scss file for styling, write down css classes. And conditionally you can apply classes on any element like this.
for example -
className={props.count > 2 ? classes.abc : classes.xyz}

When to use React createFragment?

I'm rendering a ListView in React Native, managed to fix this React warning but I don't understand how and why it works:
Warning: Any use of a keyed object should be wrapped in React.addons.createFragment(object) before being passed as a child.
// This array generates React Fragment warning.
var data1 = [{name: "bob"}, {name:"john"}]
// This array works, no warnings.
var data2 = [React.addons.createFragment({name: "bob"}),
React.addons.createFragment({name: "john"})]
// This also works, no warnings.
var data3 = ["bob", "john"]
class Listings extends React.Component {
constructor(props) {
super(props)
ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
this.state = {
dataSource: ds.cloneWithRows(data),
}
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>} />
)
}
}
What is a React Fragment? When is it needed in React? Why does a keyed object cause this warning?
I am dividing my answer in 2 sections to cover the 2 main points I see in your question.
Section 1: What is a React Fragment?
In order to explain what a React Fragment is, we need to take step back and talk about React Keys.
Keys help React identify which items have changed, are added, or are removed. They should be given to the elements inside an array to give the elements a stable identity (uniqueness).
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys. Here's a practical example:
render() {
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
return todoItems;
}
It's important to note that the todoItems is actually an array of <li> elements.
Knowing that, let's move on to why you receive a warning in your use-case.
In most cases, you can use the key prop to specify keys on the elements you're returning from render, just like the example above. However, this breaks down in one situation: if you have two sets of children that you need to reorder, there's no way to put a key on each set without adding a wrapper element.
Here's the classical example from the recently updated React docs:
function Swapper(props) {
let children;
if (props.swapped) {
children = [props.rightChildren, props.leftChildren];
} else {
children = [props.leftChildren, props.rightChildren];
}
return <div>{children}</div>;
}
The children will unmount and remount as you change the swapped prop because there aren't any keys marked on the two sets of children.
To solve this problem, you can use the createFragment add-on to give keys to the sets of children. Follow the enhanced example, using the createFragment here.
Section 2: But why you are getting this warning?
Anyways, the error you're getting error happens simply because you try to interpolate a JavaScript object (rather than a JSX element or string) into some JSX.
Although the error message suggests using createFragment to fix this, the reason is because you're interpolating a variable into some JSX that is not a string or JSX element, but is in fact some other kind of object!
Such a misleading warning in your use-case, isn't it? :-)
You can import Fragment from 'react' as:
import React, { Fragment } from "react";
Then update your render function as:
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => {
<Fragment key={passsKey}>
<Text>{rowData}</Text>
</Fragment>
}}
/>
)}

Resources