Material UI: Menu refactoring - reactjs

was trying to refactor my menu, which uses List from Material UI.
The unrefactored menu looks like this
<SelectableList id="menu" value={location}>
<ListItem primaryText="Encoding" primaryTogglesNestedList={true} value="/encoding" nestedItems={[
<ListItem primaryText="Base32" containerElement={<Link to="/base32"/>} value="/base32"/>,
<ListItem primaryText="Base32Hex" containerElement={<Link to="/base32Hex"/>} value="/base32Hex"/>,
<ListItem primaryText="Base64" containerElement={<Link to="/base64"/>} value="/base64"/>,
and there is clearly a lot of boilerplate, so I decided to create a custom component, which would handle the duplicities in the code
import React from 'react';
import {ListItem} from 'material-ui/List'
import {Link} from 'react-router'
const MenuItem = ({anchor, ...other}) => (
<ListItem containerElement={<Link to={anchor} />} value={anchor} {...other} key={"item-"+ anchor}/>
);
export default MenuItem;
The problem is, that when I used it
<MenuItem primaryText="Base32" anchor="/base32" />
The menuItem ceased to be selectable. Further more, to recall the value from SelectableList id="menu" value={location} (to expand the menu, when page is reloaded), I had to to add value tag to MenuItem as well (duplicity back).
How should I handle this refactoring?
Update
JSFiddle (simplified example): https://jsfiddle.net/1vk8wzoc/27/

Ok, so looking at your JSFiddle and the Material UI source, it seems like they reject children without certain properties:
https://github.com/callemall/material-ui/blob/master/src/List/makeSelectable.js#L18
extendChild(child, styles, selectedItemStyle) {
if (child && child.type && child.type.muiName === 'ListItem') {
....
So your component is never receive the styles to indicate that its selected.
I would do one of two things here:
Possibly raise a PR with the library to support HOC's for
Listitems
Or, use React.cloneElement which should copy all
the required stuff across so it appears to be the right element to
the makeSelectable function

Related

React MUI <Tab> component "Expected an element type that can hold a ref."

I am working with React MUI Tabs and am running across:
Warning: Failed prop type: Invalid prop component supplied to ForwardRef(ButtonBase). Expected an element type that can hold a ref. Did you accidentally provide a plain function component instead?`
I have read many threads, Including This One -- And surrounding my element in a <div> or a <Box> does not do the trick.
I have also read This Documentation Which suggests that it's "just a warning" to me even though I am not trying to pass a ref.
Can anyone find what is wrong with the following (if anything):
<Tab label="Project Info" {...a11yProps(0)} component={() => (
<Box onClick={() => setValue(0)}>
<Icon sx={{color: ywpBlue}} title="Grid View">info</Icon>
<Typography sx={{fontSize:"20px"}} mt={-3}>
Project Info
</Typography>
</Box>
)}/>
I have also tried:
<Tab label="Project Info" {...a11yProps(0)} component={props =>
<Box {...props} onClick={() =>
To no avail.
The <Tabs> display fine, and function as expected .. I just HATE seeing red "Warnings" in my DevTools Console.
The Tab component doesn't have a prop component. Under the hood, Tab is using ButtonBase, hence when it is being provided component it complains. If you want to just add an Icon and style, you should follow the docs on customizing tabs here. You can provide an Icon, and center the label and styled like so:
import * as React from "react";
import Tabs from "#mui/material/Tabs";
import Tab, { TabProps } from "#mui/material/Tab";
import PhoneIcon from "#mui/icons-material/Phone";
import { styled } from "#mui/material";
const CustomTab = styled(Tab)<TabProps>(({ theme }) => ({
"&.MuiButtonBase-root": {
fontSize: "20px",
marginTop: -3
}
}));
export default function IconTabs() {
return (
<Tabs>
<CustomTab icon={<PhoneIcon />} label="phone" />
</Tabs>
);
}
If you need more control, you could consider using TabUnstyled in the base library and you will have complete control of the components show in the base docs

React-Select conflicting with MUI Accordion

I'm stumped. I've done a number of Google searches as well as looked at react-select and Material UI Accordion docs. No one seems to have encountered this issue before, which surprises me, as these are very popular component libraries.
I've been using the <CreatableSelect> component on its own, no problem, with isSearchable and isMulti properties activated. Behavior is as expected. When I type in the box it auto-filters the dropdown list of options accordingly.
However, things get glitchy only when I put this component into the content pane of an MUI <Accordion> component: the typing action of the user after two characters causes the select widget to lose focus, the page scrolls to some unknown anchor point, and the user's typed text does not remain in the select box, and the dropdown list of options doesn't even appear. This glitch occurs even if there are only a small number of options (e.g. 5-10) in the options list.
Does anyone have any experience with the interactivity of these two components? Is there some component property toggle I am missing? I am guessing that the Accordion is responding to keystrokes in a way that is overriding the CreatableSelect's behavior.
import MuiAccordion from "#mui/material/Accordion";
import MuiAccordionSummary from "#mui/material/AccordionSummary";
import MuiAccordionDetails from "#mui/material/AccordionDetails";
import ExpandMoreIcon from "#mui/icons-material/ExpandMore";
import CreatableSelect from "react-select/creatable";
const FilterBox = ({.... various props .... }) => {
const Accordion = styled(MuiAccordion)(({ theme }) => ({
}));
const AccordionSummary = styled(MuiAccordionSummary)(({ theme }) => ({
}));
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
}));
return (
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel-candidates-content"
id="panel-candidates-header"
>Search Items
</AccordionSummary>
<AccordionDetails>
<CreatableSelect
instanceId={label}
aria-label={label}
styles={selectCustomStyles}
options={showOptions}
isSearchable
isMulti
value={value}
filterOption={handleFilterOption} />
</AccordionDetails>
</Accordion>
)
}
Upon further investigation, I figured out that the problem is the styled() function from MUI legacy styling. When I switched over to the newer sx prop paradigm, the issue went away. I still have no idea why styled() would interfere with user interaction. But given that this approach is deprecated, I'm not going to bother to figure that out! Just switch over to MUI "system" and you're good to go.
I have same problem, but i used the Select from Material-UI. That problem appear when i set props disablePortal like "False" for Select. Just try use Select from Material-UI with portal or just portal.
<Select
MenuProps={{
disablePortal: true,
}}>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>

How to use override Button using Box component in Material-UI?

I've been trying to understand and write code on the Box component in material-UI. (https://material-ui.com/components/box/#box)
I've been trying to override a Button component the two ways it describes in the documentation, but I have no idea how. When I run the code segment using both methods, the button appears but no color change. Then when I try to add an extra Button underneath the clone element code segment I get an error saying 'Cannot read property 'className' of undefined'.
<Box color="primary" clone>
<Button>Click</Button>
<Button>Click</Button>
</Box>
When I add a Button component underneath in the second render props way, the first button just disappears from the DOM completely.
<Box color="secondary">
{props => <Button {...props} > Click </Button>}
<Button color="secondary">Click</Button>
</Box>
Would appreciate an explanation of how overriding underlying DOM elements work.
There are a few issues with the code you've shown in your question.
primary and secondary are not valid colors within the palette. They are valid options for the color prop of Button, but here you are trying to reference colors within the theme's palette object. For this purpose, you need primary.main and secondary.main (which is what Button uses when you specify <Button color="primary">).
Box only supports a single child when using the clone property and it only supports a single child when using the render props approach. In both of your examples you have two children.
Here is the Material-UI source code that deals with the clone option:
if (clone) {
return React.cloneElement(children, {
className: clsx(children.props.className, className),
...spread,
});
}
This is creating a new child element that combines the className generated by Box with any existing class name on the child. It gets at this existing class name via children.props.className, but when there are multiple children then children will be an array of elements and will not have a props property so you get the error:
Cannot read property 'className' of undefined
Here is the Material-UI source code that deals with the render props approach:
if (typeof children === 'function') {
return children({ className, ...spread });
}
When you have more than one child, then typeof children === 'function' will not be true and it won't use the render props approach. In this case, both children just get normal react rendering and trying to render a function doesn't render anything.
Below is a working example that fixes all of these problems by using a single Button child in the clone case and a single function child in the render props case (a function that then renders two Button elements).
import React from "react";
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
export default function App() {
return (
<>
<Box color="primary.main" clone>
<Button>Click</Button>
</Box>
<Box color="secondary.main">
{props => (
<>
<Button {...props}> Click </Button>
<Button color="secondary">Click</Button>
</>
)}
</Box>
</>
);
}

React tabs - switching destroys component, need to maintain the same component

I am trying to make a multi-tabbed SPA with React and Material-UI. I use code from this demo as an example: https://codesandbox.io/s/qlq1j47l2w
It appears that if I follow the aforementioned example, I end up returning new instance of the component with tab contents each time I navigate between the tabs:
<Tabs
value={value}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
scrollable
scrollButtons="auto"
>
<Tab label="Tab 1" />
<Tab label="Tab 2" />
</Tabs>
</AppBar>
{value === 0 && <Tab1Contents/>}
{value === 1 && <Tab2Contents/>}
As Tab1Contents is a form, I would like its internal state to be retained, instead of loading a new instance of the component, which the code above appears to do.
What is the best way to get React to use only one instance of the component and 'memorise field values'?
EDIT
I have added Redux to the example, but the store corresponding to the form within the Tab is destroyed the moment I switch away. Is there any other way to implement tabs in React that would hide the tab contents, instead of destroying them and re-creating them from scratch each time I navigate away?
The solution to my problem was quite simple! If you don't want to destroy the component (remove it from DOM), you can simply hide it!
Instead of:
{value === 0 && <Tab1Contents/>}
Use:
<div style={{ display: value === 0? 'block': 'none'}}>
<Tab1Contents/>
</div>
It was already mentioned that you have to prevent your component from being removed from the DOM. The easiest solution with MUI5 TabPanel is in fact to just replace
{value === index && children}
with
{children}
that means your Tabpanel would look like that:
import * as React from "react";
const TabPanel= ({ children, value, index, ...other }) => {
return (
<div
role="tabpanel"
hidden={value !== index}
id={`tabpanel-${index}`}
aria-labelledby={`tab-${index}`}
{...other}
>
{children}
{/* {value === index && children} // This was removed */}
</div>
);
};
export default TabPanel;
No additional logic necessary as the hidden prop already takes care of the visibility aspect. This way your components should maintain their state!
You would need to persist the state between the tab changes. I prototyped a form using React forms documentation for Tab1Container and as you play around with it, the value will disappear
https://codesandbox.io/s/material-demo-ojwhr
What you ideally need to use something like Redux, which will use a store to keep the information even between the state changes like Tab clicks.
Hope this helps!

Is it possible to set up material-ui AppBar/ToolBar to have a horizontal tab menu layout?

Looking for the way to create a horizontal navigation menu for the web application using material-ui components. I'd like to start with something very similar to the menu at the react documentation website:
Since it's a quite common menu look, I was searching for the example of such configuration but was unable to found it either at material-ui docs or here at SO (there is a similar solution, but it is not exactly what's needed).
How to do this?
My solution for this was to place a ToolbarGroup component within AppBar's iconElementRight property.
// Defining a stateless, functional component, MyNavLinks.
// This component contains your navigation links.
const MyNavLinks = () => (
<ToolbarGroup>
<FlatButton label="Dashboard" containerElement={<Link to="dashboard"/>}/>
<FlatButton label="Settings" containerElement={<Link to="settings" />}/>
<FlatButton label="Profile" containerElement={<Link to="profile" />}/>
</ToolbarGroup>
);
// Another stateless, functional component, MyAppBar.
// Here we are setting the iconElementRight property of Material UI's AppBar
// to the component defined above.
const MyAppbar = () => (
<AppBar
title="Brand"
iconElementRight={<MyNavLinks />}
/>
);
Result:

Resources