Delete dynamically added items [react-select] - react-select

I'm using the react-select control. The items displayed in the dropdown list are a combination of fixed items plus dynamically added items.
I'd like to be able to delete the dynamically generated items directly in the dropdown panel by adding an icon next to the label. When clicked this should remove the item.
I know the code to add/remove items programmatically. It's just a case of updating state. The thing I'm stuck on is how to add UI to the react-select dropdown panel and fire a click event when it's clicked on.

According to the docs you can replace the option component in react-select.
import React from 'react';
import Select from 'react-select';
const CustomOption = ({ innerProps }) =>
<div {...innerProps}>{/* your component internals */}</div>
class Component extends React.Component {
render() {
return <Select components={{ Option: CustomOption }} />;
}
}
That way you could add an icon <span onClick={() => this.deleteOption(optionId)}>×</span> to the CustomOption component and use css position: absolute etc. to get it where you want and style it, preferably through a className

Related

Testing visibility of React component with Tailwind CSS transforms using jest-dom

How can I test whether or not a React component is visible in the DOM when that component is hidden using a CSS transition with transform: scale(0)?
jest-dom has a .toBeVisible() matcher, but this doesn't work because transform: scale(0) is not one of the supported visible/hidden triggers. Per the docs:
An element is visible if all the following conditions are met:
it is present in the document
it does not have its css property display set to none
it does not have its css property visibility set to either hidden or collapse
it does not have its css property opacity set to 0
its parent element is also visible (and so on up to the top of the DOM tree)
it does not have the hidden attribute
if <details /> it has the open attribute
I am not using the hidden attribute because it interfered with my transition animations. I am using aria-hidden, but that is also not one of the supported triggers.
The simplified version of my component is basically this. I am using Tailwind CSS for the transform and the transition.
import React from "react";
import clsx from "clsx";
const MyComponent = ({isSelected = true, text}) => (
<div
className={clsx(
isSelected ? "transform scale-1" : "transform scale-0",
"transition-all duration-500"
)}
aria-hidden={!isSelected}
>
<span>{text}</span>
</div>
)
I could potentially check for hidden elements with:
toHaveClass("scale-0")
toHaveAttribute("aria-hidden", true)
But unlike toBeVisible, which evaluates the entire parent tree, these matchers only look at the element itself.
If I use getByText from react-testing-library then I am accessing the <span> inside the <div> rather than the <div> which I want to be examining. So this doesn't work:
import React from "react";
import { render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import { MyComponent } from "./MyComponent";
it("is visible when isSelected={true}", () => {
const {getByText} = render(
<MyComponent
isSelected={true}
text="Hello World"
/>
);
expect(getByText("Hello World")).toHaveClass("scale-1");
});
What's the best way to approach this?

Prevent re render of shared ListHeaderComponent

I am working on a social media app where i have a container component that has the following structure
<MyContainer>
<SelectionBar/>
{condition? <FlatListA header={header}/> : <FlatListB header={header}/>}
<MyContainer/>
the selection bar has buttons that determine which FlatList to display for the purpose of this question lets say messages FlatList vs posts FlatList
these two FlatLists have different listeners and data so they need to be their own component but they share the same ListHeaderComponent which is a feature similar to snapchat stories
the problem is when the user switches between two FlatLists the stories flicker because the component is re rendered because its two different FlatLists
the header needs to be inside the flatlist as a ListHeaderComponent because when the user scrolls down the stories should not stick to the top
is there any way to prevent this re rendering?
I've tried React.memo but that did not work
You can prevent re-rendering of same component by using React.memo
You can define your header component and pass it as a prop like:
import { memo } from "react";
import FlatListA from "./FlatListA";
import FlatListB from "./FlatListB";
const header = memo((props) => {
console.log("header render");
return <h1>this is header</h1>;
});
export default function App() {
return (
<div className="App">
<FlatListA header={header} />
<FlatListB header={header} />
</div>
);
}
and you can use it in your FlatList components like:
import { useState } from "react";
export default function FlatListA(props) {
console.log("flatlista render");
const [toggle, setToggle] = useState(false);
return (
<div>
<props.header />
FlatlistA {toggle}
<button onClick={() => setToggle(!toggle)}>toogle state</button>
</div>
);
}
You can take a look at this example codesandbox and click buttons to change state and see console outputs that it does not re-render header components.

Control Vue component from nested layout using Inertia JS

I'm using Inertia JS using Vue and nested Layouts.
My main layout looks something like this:
<template>
<div>
<app-bar title="App title" type="back|dismiss|sidebar">
<!-- Slot for icons in the top right corner -->
</app-bar>
<slot />
</div>
</template>
So, an AppBar component accepting a title, a link with a back icon, dismiss icon or sidebar icon, and a slot (optionally) to provide icon links relevant to the current page.
<script>
import Layout from '#/Pages/Messenger/Layout';
export default {
metaInfo: { title: 'Report new problem' },
layout: [Layout],
...
</script>
This is a Page that is nested in the Layout.
So my question is: what is the best/preferred way to control the props and slot of the AppBar from nested Pages?
A bit like as you would do using Blade templates in Laraval or as Vue Meta does for the document page title as seen in the example above.
Maybe this is not even the best approach, in that case also let me know :)
If you are trying to pass information from your child component to your parent component such as a title, you can use $emit.
Here is a article describing how: https://hvekriya.medium.com/pass-data-from-child-to-parent-in-vue-js-b1ff917f70cc
And another SO question: https://stackoverflow.com/a/52479745/4517964
The fast way I found to pass data to persistent layouts is by:
in the child
use this:
layout: (h, page) => { return h(Layout, () => page) }
instead of:
layout: Layout,
and in the parent layout you can access your child with this.$slots.default()[0]

How to add data-testid attribute to react-select components

Using react-testing-library, I wish to test a form implemented in React.
That form includes a React component of type react-select.
It is necessary to click a part of the react-select component that has no label, no text, etc. (E.g. the dropdown arrow).
Ordinarily, the react-testing-library way to do this is to add a 'data-testid' attribute to the item in question.
I've found that it's possible to give each part of the react-select a CSS class attribute, by providing the 'classNamePrefix' prop to the react-select component. Is there some way to do the same for data-testid attribute?
Note: I'm aware I can provide custom implementations of the components of react-select, but that seems like overkill to get one attribute in place.
First of all I'd question why there's no label on the Select as this wouldn't be classed as accessible for screen readers.
But, If you don't want a visible label you could always pass an aria-label prop to the Select and test it that way using getByLabelText.
<Select aria-label="Example Label" ... />
getByLabelText('Example Label')
If you really need to add a data-testid you could replace the specific components you want to add the data-testid too and add it. (See the docs for more info)
e.g.
// #flow
import React from 'react';
import EmojiIcon from '#atlaskit/icon/glyph/emoji';
import Select, { components } from 'react-select';
import { colourOptions } from './docs/data';
const DropdownIndicator = props => {
return (
<components.DropdownIndicator {...props}>
<span data-testid="DropdownIndicator">
<EmojiIcon primaryColor={colourOptions[2].color} />
</span>
</components.DropdownIndicator>
);
};
export default () => (
<Select
closeMenuOnSelect={false}
components={{ DropdownIndicator }}
defaultValue={[colourOptions[4], colourOptions[5]]}
isMulti
options={colourOptions}
/>
);
Codesandbox link
Before all react-select is just a select. In testing you should keep eyes in your components. react-select is a component outside your project, the test cases belong to their owner.
So in this cases I recommend to just mock the package in your benefit.
Here is an example of how to mock it:
jest.mock('react-select', () => ({ options, value, onChange }) => {
return (
<select data-testid="react-select-mock" defaultValue={value} onChange={onChange}>
{options.map(({ label, value }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
);
});

React-native display Flatlist Component between Parent Flatlist Items

Working on a react-native application where I've to list Custom Items in a FlatList. This custom items contains two buttons. Clicking on each of it will get some data regarding to that clicked item and will display another FlatList with custom items in between that clicked item and it's next item.
Here in this link I've drawn that screen looks. Clicking on button B2 will get a list and display in between Parent FlatList item See Screens Here
I've tried with the SectionList where I'm displaying my first list data in Headers of SectionList with Custom Component with two buttons. Clicking on any, will get another list of items and assigned those data to clicked section header data. Those data will be displayed as child items to selected Header section. But it didn't workout as expected so looking for some alternate way with FlatList.
Your main component will be like below:
export default class MainClass from React.Component{
render(){
<FlatList
data={items}
renderItem={({item}) => <ListItem/>}
/>
}
}
Your custom list item component will be like below:
export default class ListItem from React.Component{
render(){
<View>
<View>
//your card view which will contain B1 and B2 button
</View>
//below views will render conditionally
<FlatList/> //if B2 button clicked then show fetched list
<CalendarWidget/> //if B1 button clicked then show calendar data
</View>
}
}
Try to build your code like above solution. It should solve your problem.