Maintaining default open/close behavior with custom ValueContainer in react-select - javascript

I need to customize a react-select dropdown a bit. Its a multi-select, and my goal is that when the user select many options, the selected options only take up as much horizontal space in the box as is available. Overflow should be handled with an ellipse.
Default behavior - overflow items wrap to next line and make box height grow:
Desired behavior - custom rendering, with ellipse
My tactic in accomplishing this is to use a custom component in the select for ValueContainer:
const ValueContainer = (props) => (
<div
{...props.innerProps}
style={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{props.selectProps?.value?.map((p) => p.label).join(", ")}
</div>
);
const MyComponent = () => {
// state logic here
return (
<Select
options={options}
value={value}
isMulti
onChange={(value) => setValue(value)}
components={{ ValueContainer }}
/>
);
This is indeed working, as you can see in this codesandbox.
However, I seem to have lost the default behavior. In the left example in the sandbox (default as shown above), when you click on any part of the dropdown (existing selection, empty space, dropdown indicator, etc), the dropdown opens. This is the behavior I want. In my custom example, clicking on the list does not open the dropdown - you have to click specifically on the tiny little dropdown arrow to get it to open. Likewise, when its open, the default behavior has a click-away handler, where clicking anywhere on the screen closes the menu. That behavior is also lost with this custom ValueContainer component.
How can I maintain this custom open/close click behavior, while using a custom ValueContainer component? There does not seem to be an innerRef on the props of a ValueContainer, as described in the docs. Is there a setOpen prop somewhere in these props that I'm missing? Is there a better way to achieve my desired effect?

Related

React-Select Creatable prevent user from entering additional options if length of selection is reached

Currently I have a Createable component that has a dropdown menu and users can also freely enter any new value. However I want to disable creating new values if the maximum number is reached.
Here I used a custom menulist component to hide the dropdown (There is still a visible small line here beneath the component tho). But the user can still type, in the input. Is there any way to disable this specifically and not disable the whole component since they should still be able to delete selections.
const CustomMenuList = (props: any) => value?.length === 5 ? null : <components.MenuList {...props} />;
Your logic is generally correct but you need to apply it to the Menu component which is the container for MenuList, that's why you still see something when you hide MenuList
import { components } from 'react-select';
const CustomMenu = ({ children, ...props }) =>
props.options.length === 5 ? null : (
<components.Menu {...props}>{children}</components.Menu>
);
<Select
components={{
Menu: CustomMenu,
}}
/>
As for disabling the input, just follow the same logic and override the input's disabled attribute when options reach 5.

Force expanded state of Material-UI Accordion

We use Accordions to display editable entities and want to lock modified entities in the expanded state.
Currently, we are forced to lift up expanded state into accordion wrapper, practically duplicating built-in functionality, to prevent controlled<->uncontrolled Accordeon component transition (which is prohibited by MUI).
Something like this:
const AccordionWrapper = ({isModified = false, ...otherProps}) => {
const [expanded, setExpanded] = useState(isModified);
return (
<Accordion
expanded={expanded}
onChange={(_, expanded) => {
setExpanded(expanded || isModified);
}}
...otherProps
/> );
}
Is there a better way to achieve it?
Alternatives we have considered:
<Accordion disabled={isModified}> + custom styles to make disabled accordion look not so disabled (grayish). This is a little daunting that there is (an unlikely) chance to lock component in a collapsed state cause we don't control it.
Send PR to allow controlled<->uncontrolled transition of expanded. In this case we could <Accorrdion expanded={isModified ? true : undefined}>. Actually, there is no strong technical problem with such transition (especially when/if https://github.com/mui-org/material-ui/pull/29237 gets merged)
Maybe add a new Accordion prop like "forceExpand" if MUI team will find it useful
PS. I hope MUI devs will see points 2 and 3 here on SO, cause these questions/suggestions probably are a non-issue and should not be in the MUI issue tracker.
If you want to add a custom expand toggle on MUI Accordion then you get the same with a property:-
defaultExpanded={Boolean}
So it will be like:-
<Accordion defaultExpanded={true}>
{...otherProps}
</Accordon>

React / JSX - remove menu items if sibling drop-down menu already has value

I have a simple Component / controlled function that lets the user choose an item from three different drop-down menus. I am currently tinkering with how to arrange the state/prop passing. The menu items for each drop-down menu are identical for each three, and they do not change. Still, there may need to be some kind of information passed at the event listner since the goal is to ensure there are no repeats in the users chosen menu items. In other words, when the user chooses something from one box, the other two boxes should remove that item from their choices (and again when the user chooses a second item). Here is the relevant code:
function SelectBox(props) {
const classes = useStyles();
const [strategy, setStrategy] = React.useState('');
const handleChange = event => {
setStrategy(event.target.value);
};
const textMap = {
1:'Top pick',
2:'Second pick',
3:'Third pick'
};
return (
<FormControl className={classes.formControl}>
<InputLabel id="demo-simple-select-helper-label">Strategy {props.selectBox.id}</InputLabel>
<Select
labelId="demo-simple-select-helper-label"
id="demo-simple-select-helper"
value={strategy}
onChange={handleChange}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{props.strategies.map(strategy => (
<MenuItem value={strategy}>{strategy}</MenuItem>
))}
</Select>
<FormHelperText>{textMap[props.selectBox.id]}</FormHelperText>
</FormControl>
);
}
export default SelectBox;
Thus far I managed to pass props id down to each select box, and that allowed me to set helper text accordingly.
Edit
As suggested, I lifted the state up so that my menu items are now populated dynamically instead of hardcoded as before. However, I'm not sure that lifting the state solves the sibling comparison problem. (Note: I left the pre-lifted state event handler in for convenience, can assume it's now a prop in this scope that is sourced from state in another file)
State from that file is:
state = {
selectBoxes: [
{id:1, strategies:['A','B', 'C','D','E']},
{id:2, strategies:['A','B', 'C','D','E']},
{id:3, strategies:['A','B', 'C','D','E']},
]
}
Question
How would I approach event listner logic for sibling drop-down menus to ensure there are no repeats of an item?

Prevent closing after click on Dropdown.Item in React-bootstrap

When I click on Dropdown.Item, Dropdown.Menu hides. I want to prevent this, i.e. leave Dropdown.Menu open after a click, and close it only if there was a click outside of Dropdown at all. I've found similar questions, but there were in original bootstrap using jQuery. How to implement this in react-bootstrap? Thanks
////
<Dropdown.Menu>
<Dropdown.Item>- Pending</Dropdown.Item>
<Dropdown.Item>- Completed</Dropdown.Item>
<Dropdown.Item>- Cancelled</Dropdown.Item>
</Dropdown.Menu>
////
Add autoClose="inside" to the Dropdown component.
By default, the dropdown menu is closed when selecting a menu item or clicking outside of the dropdown menu. This behavior can be changed by using the autoClose property.
By default, autoClose is set to the default value true and behaves like expected. By choosing false, the dropdown menu can only be toggled by clicking on the dropdown button. the inside makes the dropdown disappear only by choosing a menu item and the outside closes the dropdown menu only by clicking outside.
https://react-bootstrap.github.io/components/dropdowns/
You can make use of show prop of Dropdown. Using this you can manually hide and show the dropdown.
So what i did is i added dropdown props state variable to the Dropdown element and then using onToggle function i hide and show dropdown on particular conditions.
<Dropdown {...this.state.dropdownProps} onToggle={(isOpen, event) => this.onToggleFunction(isOpen, event)} />
Try this:
const [isShown, setIsShown] = useState(false);
const onToggleHandler = (isOpen, e, metadata) => {
if (metadata.source != 'select') {
setIsOpen(isOpen);
}
}
<Dropdown
show={isShown}
onToggle={(isOpen, e, metadata) => onToggleHandler(isOpen, e, metadata)}
>
*onSelect method can be used normally
For functional components, simply create the state:
const [ show, setShow ] = useState(false);
Then write your dropdown component like this:
<Dropdown show = {show}>
<Dropdown.Toggle onClick = {() => setShow(!show)}>Toggle Trigger.</Dropdown.Toggle>
</Dropdown>

Google Material's ExpansionPanel for ReactJS not expanding by default

I am building a ReactJS app using Google's Material-UI.
I have the following child class that is displayed in a Grid, after a search has been submitted. Depending if the search is one type or another, the ExpansionPanel inside this child class should be expanded or not expanded.
Here is the class that is being mapped in the parent component:
The expandByDefault boolean is passed from the parent class.
class SearchResults extends React.Component {
render () {
const { classes } = this.props;
const { batchAndContents } = this.props;
const { expandByDefault } = this.props;
return (
<div>
<ExpansionPanel defaultExpanded={expandByDefault} >
<ExpansionPanelSummary>
<Typography className={classes.heading}>Box {batchAndContents.sequenceCode}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<SearchResultsTable contents={batchAndContents.contents}/>
</ExpansionPanelDetails>
</ExpansionPanel>
</div>
)
}
}
Here is the render method for the parent class:
You can see in SearchResults, my custom class, I pass a prop named expandByDefault.
render () {
return (
<div>
. . . . .
. . . . .
. . . . .
<Grid container spacing={24} style={{padding: 24}}>
{this.state.searchResults.map((searchResult) => (
<Grid item key={searchResult.sequenceCode+searchResult.state} xs={12}>
<SearchResults batchAndContents={searchResult} expandByDefault={this.state.lastSearchType === "ContentBarcode"}/>
</Grid>
))}
</Grid>
</div>
)
}
I've tried several variations to get this to work, and I can't seem to understand what I'm missing.
What's interesting is that when I initially perform a search, and the ExpansionPanel's property defaultExpanded is set to true, it works. However if I don't refresh the page and perform another search with a different type, the results don't collapse back down when the type should cause that behavior.
Same behavior occurs if I initially perform the search and the ExpansionPanel defaultExpanded is set to false. It expands on click, and is default collapsed, however, when changing the search type to something that should cause default expanded panels, it doesn't work.
I appreciate any guidance.
I ran into the same problem. The property only seems to look at the value in the initial rendering.
If it's undefined or has a default value in the initial rendering then it will be used even when the value ofthis.props.expandByDefault is changed; its simply ignored.
You have to avoid rendering the component altogether until the prop has received the "correct" value if you want to use this feature.
It seems odd at first but it makes sense when you think about it. Once initially rendered the API doesn't want to risk overwriting actions taken by the user and accidentally close or open the dialog against one's will. It would probably lock the panel.
The defaultExpanded property only defines the default state of the component -- ie, whether or not to expand the panel when it is first rendered. Changes to this value later will not affect whether the panel is currently expanded or not.
To cause the panel to expand or collapse in response to changes in your app, you need to use the expanded property. From the docs:
If true, expands the panel, otherwise collapse it. Setting this prop enables control over the panel.
The problem here is that the keys that are on the <Grid item ... /> elements in the <Grid container /> parent view are not changing.
When a search is executed with a different type, the same data is being displayed, just displayed differently.
From my understanding, if react sees the same key with the same data, it doesn't need to re-render, even if there are states being passed as props to the children. In my case states are being passed as props to children in a <Grid item ... />.
As a resolution, I append the type of search to the key. So now when the search type changes, the key that holds the result does as well, and the children of the <Grid item ... /> are triggered to be re-rendered.
Sorry for not the best explanation, I have some practical experience with ReactJS, but I can not speak about it that well from an "under the hood" point of view.

Categories

Resources