Force expanded state of Material-UI Accordion - javascript

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>

Related

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

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?

React: Is there a way to bypass the disabled tab and peek into the content behind it on client side?

I think my question is more related to how client-side JavaScript works than specifically a React problem but I thought of this question when I am building a React Tab component.
Here is the Tab component I was building.
Live demo: https://codesandbox.io/s/busy-noyce-6kt0p
const Tabs = ({ data, tabPosition = 'up', disabled }) => {
const [activeIndex, setActiveIndex] = useState(0)
const tabList = (
<div data-tab-list>
{data.map((tab, index) => {
const isActive = activeIndex === index
const isDisabled = disabled.includes(index)
return (
<div
data-tab
key={index}
className={isDisabled ? 'disabled' : isActive ? 'active' : ''}
onClick={isDisabled ? undefined : () => setActiveIndex(index)}
>
{tab.label}
</div>
)
})}
</div>
)
const tabPanel = <div data-tab-panels>{data[activeIndex].content}</div>
return (
<div data-tabs>
{tabPosition === 'up' ? [tabList, tabPanel] : [tabPanel, tabList]}
</div>
)
}
export default Tabs
As you can tell, we can disable a tab or tabs by providing this disabled prop, which is an array of numbers that indicates the indices of the tabs.
If I do this
<Tabs data={tabData} tabPosition="up" disabled={[2]} />
Then the third tab will be disabled and not clickable so that the user won't be able to see the content behind the tab, i.e. the tab panel.
However I was thinking the other day that, in fact this is all client side JavaScript code we are sending to the user's browser, so the user already has all the info in the code. It is just that React or JavaScript that got shipped prevents him/her from clicking the disabled tab. But is it true that in theory the user can peek into the content since all the code is already in the browser? I imagine if he/she set some sort of breakpoint that fires when the click happens, and modify the code locally when he/she would be able to discover the content behind the disabled tab. However I am not sure how I can achieve that.
Yes. It's possible to tinker with it and change the behavior. In your specific case, for example, you could open the React developer tools, select the Tabs component, and change the disabled prop to whatever they want and the component would re-render accordingly.
And as Robin points out in his comment, a user can simply inspect the content that's already being rendered.
As a general rule you should not rely on javascript for security purposes. If you don't want a user to access something you shouldn't be sending it to the browser.
Open the developer mode by F12 or inspect element in Browser. Go to the target element and modify the content of the code (remove disable in your case). In short you should never rely on Frontend or JS to web app security or to handle these cases place a backend validation as well.

Reactjs material ui prevent re-render tabs on enter

I have built a material-ui based UI containing tabs in reactJS. Anytime a tab is selected, the content under that tab reloads which is causing a major performance issue because I am displaying an iFrame under one those tabs. I tried to use React.memo to ensure that the screen does not re-render because there is no change in the props but it still does.
Here is the code -
Code sandbox link
Is there a way, that once the iFrame is loaded for the first time and I switch between tabs, the iframe does not get re-rendered again?
You check if the value of the current tab equals the index of the current tab, and only if they are equal you display the content.
Instead - just keep the content and have the Typography component control the visibility of it's content (which you already have, using the hidden inside the Typography component.
<Typography
component="div"
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{<Box p={3}>{children}</Box>}
</Typography>
Note that the content will be rendered, so if you have a lot of content inside/requests to the backend/etc - all of those will be part of your DOM, even if you don't see them.
TypeScript solution:
Wrap the tab display component in a memo() function call
Wrap the display component with the control logic (<div style={...})
Tab change won't re-render the DOM anymore; size of tab content naturally flows by tab display component size. Flushing/update can be triggered by state changes in tab display component.
Example code:
const SomeTabDisplayComponent = memo(() => {
return <div>Whatever the tab displays</div>
})
const getVisibilityStyle = (hiddenCondition: boolean): any => {
if (hiddenCondition) {
return {
visibility: 'hidden',
height: 0,
};
}
return {
visibility: 'visible',
height: 'inherit',
};
};
<div style={getVisibilityStyle(value !== index)}>
<SomeTabDisplayComponent />
</div>
This solution has many advantages over the prev. answer. It doesn't use another component; doesn't mis-use "Typography" (<p> tag is not meant to include block content! Doing this is bad for SEO and accessibility etc.; violates web standards.); It doesn't depend on Typography's it's internal "hidden" implementation (if that one changes in the future, the upper solution will break) and it gives full control over when the re-render/DOM flush happens.

React checkbox state management with react-bootstrap or material-ui?

I am working on a project. I would using checkboxes on a component and would like to show some products which have some features. Features will be defined by user's check. I would like to know how to manage checkbox and state infos together.
I have done this project with <List> and <ListGroup>.I listed a category list in a companent. And when I select one of them(wtih onClick) I can see products which have same CategoryId on my Json-Server.(managed by my_reducer)
I researched on internet and material-ui is more usefull at checkbox management, I think. What do you think? Should I use material-ui instead of react-bootstrap for checkbox?Using both material and bootstrap at the same project will effect my web-page's loading speed? Or does it occurs any other problems?
I'm open with any idea. You can share your idea with me or you can send me guide, docs, examples about it.
ps: Please do not add github or offical pages of checkbox usage(both react and material-ui:) Because I already know them and couldn't find answers for my questions.
Thx.
Since you do not provide any code, it is hard to see what you are trying to achieve and what does "more useful at checkbox management" mean in this context. You mention <List> and <ListGroup>, but how are you using the checkbox, or are you simulating a checkbox with actionable ListGroup items?
I do not think it is a good idea to mix two user interface libraries. You would end up with inconsistent look and feel, and there could be conflicts with styles etc. Adding a new dependency will increase the loading time based on the size of the dependency. If you like material-ui better, it would make sense to migrate the whole project use it.
Here is the code try this may be it works ...and it helps.
class ShowFeatures extends React.Component {
constructor(props) {
super(props);
this.state = {
showfchr: false
};
}
toggleChange = () => {
this.setState({
showfchr: !this.state.showfchr,
});
}
render() {
return (
<React.Fragment>
<div>
<input type="checkbox" checked={this.state.showfchr} onChange={this.toggleChange}
/>
Show Me Some Features!
</div>
{ this.state.showfchr ? (
<div> Your All Feature Will Show here</div>
):(
<div>here is defaullt</div>
)
</React.Fragment>
);
}
}

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