Run CSSTransition inside TransitionGroup when any value changes - reactjs

I'm looking at the TransitionGroup documentation example, and I'm trying to update it to run the CSSTransition whenever the text value is updated for an existing item.
I have set up a codesandbox to show my current attempt.
const [items, setItems] = useState([
{ id: 1, text: 'Hello world!' },
{ id: 2, text: 'How are you?' },
]);
return (
<Container style={{ marginTop: '2rem' }}>
<ListGroup style={{ marginBottom: '1rem' }}>
<TransitionGroup className="todo-list">
{items.map(({ id, text }) => (
<CSSTransition
key={id}
timeout={500}
classNames="item">
<ListGroup.Item>{text}</ListGroup.Item>
</CSSTransition>
))}
</TransitionGroup>
</ListGroup>
<Button
onClick={() => {
setItems((items) => [
{ id: 1, text: 'Hello to you!' },
{ id: 2, text: 'How do you do?' },
]);
}}>
Update Items
</Button>
</Container>
);
When I click the button, the text content updates without the CSSTransition. I know this is because of the id not changing, and this is what key is set to, but how do I have TransitionGroup take into account the text value?
How do I keep the same id but update the text value with a transition?

Since key={id}, the key won't change when the text is updated, and the CSSTransition won't trigger because it doesn't get rerendered.
We can change the key to include the id and the text, so the component will always get rerendered when the text changes. We also want to add exit={false} to prevent us from seeing extra components while the new components are transitioning in. Here's what that should look like:
<CSSTransition
key={`${id}${text}`}
timeout={500}
classNames="item"
exit={false}
>
Here's a codesandbox example of this solution.

Related

React native chip selection not updating in View

`
export default function ChipsTest({ }) {
const interests = [{ name: 'ABC', selected: false, id:1, }, { name: 'XYZ', selected: false, id:2 }, { name: 'Bla bla', selected: false, id: 3 } ];
return ( <View style={styles.container}> {interests.map((interest, index) => ( <TouchableOpacity> <Chip onPress={() => { interest.selected = !interest.selected; interest.name = 'Ali'; console.log(interests);
}}
key={interest.id}
selected={ interest.selected}
> {interest.name} </Chip>
</TouchableOpacity>
))}
</View>
);
`
Hi, I am using react native chips on select I am updating value of selected item but its not reflecting in UI/View. I am able to see value updated in variable/ console. Can anyone help?
I would try to use useState hook to solve your problem.
Basically, React native will re-render your screen's UI if the state changes.
More here: https://felixgerschau.com/react-rerender-components/
Also, I'm not sure what <Chip/> component is. I would move onPress event listener to <TouchableOpacity onPress={...}> OR keep it in <Chip> and remove <TouchableOpacity>.
And then, I would wrap {interest.name} within <Text> {interest.name} </Text>
Good luck!

'Each child in a list should have a unique "key" prop' warning while it's not

Can't get rid of this warning nevertheless doing everything right.
The error exists both in ssr and non-ssr scheme (with NoSsr wrapper from material-ui)
I'm experienced with React and know about key prop and don't have such errors in other places of project. Also React extension shows that key props of two items are different.
Have no ideas how to eliminate it here.
My code (simplified, the full souce 300+ lines of code is here)
// index.tsx
export default function Index() {
return (
<Box display="flex">
{items.map((item) => (
<Item key={item.id} {...item} />
))}
</Box>
)
}
// items.ts
export const items = [
{
id: 1,
text: 'item 1',
},
{
id: 2,
text: 'item 2',
},
]
// Item.tsx
export const Item = ({ text }) => <Box>{text}</Box>;
react: 18.2.0
#mui/material: 5.10.1
next: 12.2.5
styled-components: 5.3.5
Also babel-plugin-styled-components is used
Your error says:
Check the render method of `UserTrackCard`.
It means that the error comes from this component, and not from its parent, as you seem to think. If you check the code of <UserTrackCard>, you can see the key prop is missing here:
<CardBody>
{goals.map(({ isMiniGoal, text, color }) => (
<CardBodyItem>
{isMiniGoal && <MiniGoalBadge mr={0.5} />}
<Typography variant="body2" color={color}>
{text}
</Typography>
</CardBodyItem>
))}
</CardBody>
Try this
export default function Index() {
return (
<Box display="flex">
{items.map((item, index) => (
<Item key={index} {...item} />
))}
</Box>
)
}

How to apply styles to TabPane in new Antd Tabs component

This is my old implementation of the Tabs component in Ant Design.
const tabList = [
{
key: "tab1",
label: "Tab1",
children: <Tab1 />,
},
{
key: "tab2",
label: "Tab2",
children: <Tab2 />,
},
];
<Tabs onChange={onTabChange} activeKey={selectedTab}>
{tabList.map((tab) => {
const { key, label, children } = tab;
return (
<Tabs.TabPane
key={key}
tab={label}
style={{ margin: "1.5rem auto 1.5rem" }}
>
{children}
</Tabs.TabPane>
);
})}
</Tabs>;
In the new version ( > 4.23.0 ) the boilerplate got reduced.
I can simply pass my tabList to my Tabs as a prop items.
The new code looks something like this.
<Tabs items={tabList} />
But I had an issue with styling.
I am adding top and bottom margins to all of my TabPane components.
To get that margin in the new implementation. I had to do something like this.
{
key: "tab1",
label: "Tab1",
children: <Tab1 style={{margin: "1.5rem 0 1.5rem"}} />,
},
Here I am facing two issues.
I need to add this for all my tabs in the tabList
I need to have a div in every component spreading the props that are passed above.
function Tab1(props) {
return <div {...props}>JSX for original Tab1</div>;
}
Is there a better way to do this?
Higher order component can solve this issue
Use a higher Order component like <TabPaneWrapper> or <TabChildrenWrapper>. This component does nothing but to wrap your children (<TabPane>) with a div and give the styles you require.
Component:
export function TabPaneWrapper({
children,
...props
}){
return (
<div style={{ margin: "1.5rem auto 1.5rem" }} {...props}>
{children}
</div>
);
}
Usage:
const tabList = [
{
key: "tab1",
label: "Tab1",
children: <TabPaneWrapper> <Tab1 /> </TabPaneWrapper>,
},
{
key: "tab2",
label: "Tab2",
children: <TabPaneWrapper> <Tab2 /> </TabPaneWrapper>,
},
];
If you have more tabs or use this tabs component in multiple places. You will find TabPaneWrapper to be repetitive. In such case,
You can create a custom Tabs component like <CustomTabs/> which takes the tabList mentioned in the question.
Instead of wrapping every tab in the tabList with <TabPaneWrapper/>. You can loop this list inside the <CustomTabs/> component to generate the tabList mentioned above and then pass it to the Ant Desing <Tabs/> component.

react-popper incorrect position on mount

I have built a custom tree view in React, and each item contains a dropdown which is positioned using Popper. Since the child elements are not visible on render, Popper is not positioning the dropdown correctly, for example:
When the tree is open on mount (i.e the children are visible), the positioning is correct:
Each level in the tree is rendered via a CategoryNavItem component, which essentially looks like this:
<div className={ className.join(' ') }>
<div className={ `collection-nav_item-link depth${depth}` } style={{ paddingLeft: `${paddingLeft}px`}}>
<Link to={ linkTo } onClick={() => { setIsOpen(!isOpen) }}>
<i className="collection-nav_item-link_icon"></i>
<span className="collection-nav_item-link_text">{ category.name }</span>
</Link>
<Dropdown
toggleClassName="btn-icon-white btn-sm"
toggleContent={ <Icon name="ellipsis-h" />}
position="bottom-end"
size="small"
items={[
{ text: 'Edit category' },
{ text: 'Add subcategory', onClick: (() => { dispatch(openAddSubcategory(category)) }) }
]} />
</div>
{ children }
</div>
The Dropdown component is where we use Popper, and it works well everywhere else. The visibility of a CategoryNavItem is handled via the component's state in React.
Is there any way to trigger Popper's update() method programmatically in React? We should force update when toggling the item's visibility.
It turns out we just need to expose the update property from the usePopper hook, and then call it when setting the dropdown's visibility, for example:
const { styles, attributes, update } = usePopper(referenceElement, popperElement, {
placement: placement,
modifiers: [
{ name: 'arrow', options: { element: arrowElement } },
{ name: 'offset', options: { offset: [ 0, 3 ] } }
]
});
And similarly:
const toggleDropdown = (e) => {
e.preventDefault();
e.stopPropagation();
setVisible(!visible);
update();
};
According to the docs, you can manually update the propper instance so that it recomputes the tooltip position:
Manual update
You can ask Popper to recompute your tooltip's position by running instance.update().
This method will return a promise, that will be resolved with the updated State, from where you will optionally be able to read the updated positions.
const state = await popperInstance.update();
When clicking on your item visibility toggle, you could add your popper manual update, like the line of code above.
Here is the reference.

Create separate state for similar react children object in an array

I have created a small social media app where there is a like button to like the post. When I press like on first button, second post's like changes color too which is state based.
Here is the code:
Likes.js
<LikedPanel
posts_id={posts_id}
person_id={person_id}
toggleLikePost={this.toggleLikePost}
liked={this.state.liked}
/>
const LikedPanel = ({posts_id, person_id, liked, toggleLikePost}) => (
<Panel onClick={() => toggleLikePost(posts_id, person_id)}>
{liked
?
<Fragment>
<LikeIcon nativeColor={indigo[500]}/>
<LikedText>Like</LikedText>
</Fragment>
:
<Fragment>
<LikeIcon/>
<LikeText>Like</LikeText>
</Fragment>
}
</Panel>
);
toggleLikePost = (posts_id, person_id) => {
// make api call and then setState
this.setState(liked: res.data.liked);
};
Update
Posts.js
const Posts = ({ feed, deletePost }) => (
<Card style={{ margin: '25px 0'}} square>
<ContentMedia post={feed.post} deletePost={deletePost} />
<Like liked={feed.post.likes.length > 0} posts_id={feed.post.id} person_id={feed.post.person_id} />
<Comments post_id={feed.post.id} comments={feed.post.comments} />
</Card>
);
Newsfeed.js
<div style={{ maxWidth: '500px', margin: '0 auto', paddingTop: '20px'}}>
{this.renderPosts(this.state.feed)}
</div>
renderPosts = (feeds) =>
feeds.map(feed => <Posts key={feed.post.id} feed={feed} deletePost={this.deletePost} />);
It loads fine when I refresh. But when I click like on any one post, all the other like icon gets toggled and vice versa. I see that it uses the same state for all elements. Is there a way to tell react to have its own self-contained state for every post's like panel?
Instead of storing a boolean value liked that it used by all the like buttons to determine if they should be toggled you can store an array of the liked posts. Then you can add liked post id to that array and detect in your LikedPanel if the current post id is in the liked array.
<LikedPanel
posts_id={posts_id}
person_id={person_id}
toggleLikePost={this.toggleLikePost}
liked={this.state.liked.indexOf(posts_id) !== -1 }
/>
toggleLikePost = (posts_id, person_id) => {
// make api call and then setState
this.setState(prevState => {
// add post id to liked array only if it's not added yet
if (prevState.liked.indexOf(posts_id) === -1) {
return {liked: [...prevState.liked, posts_id]};
}
});
};

Resources