Adding dynamic style to material ui Menu with makeStyles not working - reactjs

I'm trying to dynamically generate classes on the material ui Menu this way :
const useStyles = (props) => {
return makeStyles({
paper: props.style,
});
};
const StyledMenu = (props) => {
const classes = useStyles(props)();
return <Menu {...props} className={classes.paper} />;
};
render() {
const { state, fnsMenuBtn, fieldsMenuBtn, props } = this;
const { fnsMenuIsOpen, fieldsMenuIsOpen } = state;
return (
<StyledMenu
id="fnsMenuEl"
anchorEl={fnsMenuBtn}
keepMounted
open={fnsMenuIsOpen}
style={{ border: "1px solid blue" }}
onClose={(e) => {
vm.setState((state, props) => {
return {
fnsMenuIsOpen: !state.fnsMenuIsOpen,
};
});
}}
>
{Object.keys(formulajs).map((fnName) => (
<MenuItem onClick={(e) => {}} key={fnName}>
{fnName}
</MenuItem>
))}
</StyledMenu>
)
}
But the wanted style is never added to the menu
What's wrong ?
How to do it otherwise?

Insted using className try to use classes
const StyledMenu = (props) => {
const classes = useStyles(props)();
return <Menu {...props} classes={{paper: classes.paper}} />;
};

Related

Typescripting a ForwardedRef in MUI?

I am using a customized, forwarded checkbox so I can pass that as a component to a DataGrid.
const CustomCheckbox = ({
checkboxRef,
...props
}: {
checkboxRef: React.ForwardedRef<unknown>;
}) => (
<Checkbox
{...props}
ref={checkboxRef as React.RefObject<HTMLButtonElement>}
sx={{
color: "#363636",
"&.Mui-checked": {
color: "#363636"
}
}}
/>
);
const ForwardCustomCheckbox = forwardRef((props, ref) => {
return <CustomCheckbox {...props} checkboxRef={ref} />;
});
<DataGrid
...
components={{
BaseCheckbox: ForwardDarkCheckbox
}}
/>
I don't like the use of as React.RefObject<HTMLButtonElement> since I understand this as a kind of bypassing TypeScript.
I couldn't make it work without that.
Do you have any suggestions or is it considered 'okay' using as in this scenario?
Change checkboxRef type to React.ForwardRef<HTMLButtonElement>, as it seems this is the only element you wish to allow for this property
Use forwardRef<HTMLButtonElement> to explicitly define the type of reference this component accepts
const CustomCheckbox = ({
checkboxRef,
...props
}: {
checkboxRef: React.ForwardedRef<HTMLButtonElement>;
}) => (
<Checkbox
{...props}
ref={checkboxRef}
sx={{
color: '#363636',
'&.Mui-checked': {
color: '#363636',
},
}}
/>
);
const ForwardCustomCheckbox = forwardRef<HTMLButtonElement>((props, ref) => {
return <CustomCheckbox {...props} checkboxRef={ref} />;
});

Update Child state from Parent using Context in React

I have a few buttons and "view all" button. The individual buttons load the coresponding data of that index or will show all the data by clicking the "view all" button. Problem I am running into is when I click my "view all" button in the parent it's not updating the state in the child component. On mounting it works as normal but on event handler in the "view all" it doesn't update. Any thoughts on where I am going wrong here?
JS:
...
const Context = createContext(false);
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ id, styles, discription }) => {
const { activeViewAll, handleChange } = useContext(Context);
const [toggleThisButton, setToggleThisButton] = useState();
const handleClick = () => {
setToggleThisButton((prev) => !prev);
handleChange(discription, !toggleThisButton);
};
return (
<>
<Avatar
className={toggleThisButton && !activeViewAll ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(toggleThisButton)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(false);
useEffect(() => {
setActiveViewAll(true);
setSelected([...data]);
}, []);
const handleChange = (val, action) => {
let newVal = [];
if (activeViewAll) {
selected.splice(0, 3);
setActiveViewAll(false);
}
if (action) {
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log("action", action);
setSelected(newVal);
};
const handleViewAll = () => {
console.log("all clicked");
setActiveViewAll(true);
setSelected([...data]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</Context.Provider>
);
}
Codesanbox:
https://codesandbox.io/s/72166087-forked-jvn59i?file=/src/App.js:260-3117
Issue
The issue seems to be that you are mixing up the management of the boolean activeViewAll state with the selected state.
Solution
When activeViewAll is true, pass the data array as the selected prop value to the ToggleContainer component, otherwise pass what is actually selected, the selected state.
Simplify the handlers. The handleViewAll callback only toggles the view all state to true, and the handleChange callback toggles the view all state back to false and selects/deselects the data item.
function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]); // none selected b/c view all true
const [activeViewAll, setActiveViewAll] = useState(true); // initially view all
const handleChange = (val, action) => {
setActiveViewAll(false); // deselect view all
setSelected(selected => {
if (action) {
return [...selected, val];
} else {
return selected.filter(v => v !== val)
}
});
};
const handleViewAll = () => {
setActiveViewAll(true); // select view all
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected} // pass all data, or selected only
/>
</div>
</Context.Provider>
);
}
In the ToggleContainer don't use the array index as the React key since you are mutating the array. Use the element value since they are unique and changing the order/index doesn't affect the value.
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update
Since it is now understood that you want to not remember what was previously selected before toggling activeViewAll then when toggling true clear the selected state array. Instead of duplicating the selected state in the children components, pass the selected array in the context and computed a derived isSelected state. This maintains a single source of truth for what is selected and removes the need to "synchronize" state between components.
const ToggleItem = ({ id, styles, description }) => {
const { handleChange, selected } = useContext(Context);
const isSelected = selected.includes(description);
const handleClick = () => {
handleChange(description);
};
return (
<>
<Avatar
className={isSelected ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(isSelected)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update the handleChange component to take only the selected value and determine if it needs to add/remove the value.
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(true);
const handleChange = (val) => {
setActiveViewAll(false);
setSelected((selected) => {
if (selected.includes(val)) {
return selected.filter((v) => v !== val);
} else {
return [...selected, val];
}
});
};
const handleViewAll = () => {
setActiveViewAll(true);
setSelected([]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange, selected }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={d}>
<ToggleItem id={id} styles={classes} description={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected}
/>
</div>
</Context.Provider>
);
}

Customise Select Menu in React

UPDATED!
I'm creating a wrapper for dropdown menu and use it in several components so I need to make such a Menu generic. The problem is I do not understand how to pass external variables into such a component.
My component:
const SelectOptionsPaginated = ({
alignment, minWidth, width,
rowData,
column
}) => {
..........
const Menu = (props) => {
const {options} = props;
const dropdownContainer = useRef(null);
const [maxMenuHeight, setMaxMenuHeight] = useState(300)
const [dropDownStyle, setDropDownStyle] = useState({
position: "absolute",
minWidth: `${minWidth ? minWidth + "px" : "100%"}`,
maxWidth: `${width}px`,
maxHeight: `${maxMenuHeight}px`,
top: `32px`
})
const getDropdownPosition = (elem) => {
setDropDownStyle({
...dropDownStyle,
...getDropdownAlignment(elem, setMaxMenuHeight, gridId, options, true)
})
};
useEffect(() => {
const optionsList = dropdownContainer.current
if (!optionsList) return
getDropdownPosition(optionsList)
}, [options])
return (
<div
className="dropdown-container"
ref={dropdownContainer}
style={dropDownStyle}
>
<components.Menu {...props} >
{props.children}
</components.Menu>
</div>
)
}
............
return <AsyncPaginate
additional={defaultAdditional}
isMulti={isMulti}
value={value}
loadOptions={loadOptions}
onChange={handleChange}
escapeClearsValue
isClearable
styles={getStylesForSelectorEditor(width, minWidth, newAlignment)}
components={{Menu}}
/>
such variables as minWidth, width should be passed externally to Menu.
I tried something like:
...............
return <AsyncPaginate
additional={defaultAdditional}
isMulti={isMulti}
value={value}
loadOptions={loadOptions}
onChange={handleChange}
escapeClearsValue
isClearable
styles={getStylesForSelectorEditor(width, minWidth, newAlignment)}
// pseudocode
components={{<Menu width={100}/>}} or
components={{Menu(100)}}
/>
but it doesn't work.
I tried to google but didn't find clear information. I'm new in react so will appreciate any help.
Did you meant something like that?
const AsyncPaginate= (props) => {
const {components} = props;
return (
<>
{components}
</>
)
}
const Menu = () => {
return (
<>
something...
</>
)
}
const App = () => {
return (
<>
<AsyncPaginate components={<Menu />}></AsyncPaginate>
</>
)
}

How to display Material-ui Alert based on the response of axios.post reactjs

Currently, using default alert which is alert(response.data.result). To make my site more beautiful, I want to use Material-ui Alert. https://material-ui.com/components/alert/
My issue is I have no idea on how to call it from const.
Here's my code.
function Test(){
const saveData=async() => {
await axios.post('/API', passedParams)
.then(response => {
if(response.data.success === true)
{
alert(response.data.result)
}
else
{
alert(response.data.result)
//<Alert severity='error'>{response.data.result}</Alert> tried to use this but nothing displayed
}
}).catch(error=>{
alert(error)
})
//content of modal
cosnt bodyInsert = (
<div>
...fields
<Button onClick={()=>saveData()}>Save</Button>
</div>
)
return(
<div>
<Modal
open = {modalInsert}
onClose = {openCloseModalInsert}>
{bodyInsert}
</Modal>
</div>
)
}
export default Test;
Hoping for your consideration. thank you.
function Test(){
const saveData=async() => {
const [alert, setAlert] = useState(false);
const [alertContent, setAlertContent] = useState('');
await axios.post('/API', passedParams)
.then(response => {
if(response.data.success === true)
{
setAlertContent(response.data.result);
setAlert(true);
}
else
{
setAlertContent(response.data.result);
setAlert(true);
}
}).catch(error=>{
alert(error)
})
//content of modal
cosnt bodyInsert = (
<div>
...fields
<Button onClick={()=>saveData()}>Save</Button>
</div>
)
return(
<div>
{alert ? <Alert severity='error'>{alertContent}</Alert> : <></> }
<Modal
open = {modalInsert}
onClose = {openCloseModalInsert}>
{bodyInsert}
</Modal>
</div>
)
}
I think you can not do it that way. Use a state for it.
const [showAlert, setShowAlert] = useState(null);
const saveData=async() => {
await axios.post('/API', passedParams)
.then(response => {
if(response.data.success === true)
{
alert(response.data.result)
}
else
{
alert(response.data.result)
setShowAlert(response.data.result);
}
}).catch(error=>{
alert(error)
})
// Your return
return showAlert && <Alert severity='error' onClose={() => setShowAlert(null)} > { showAlert } </Alert>
You can use Collapse component collapse doc
const alertComponent = (<Alert severity='error'>{alertContent}</Alert>);
<Collapse in={alert}>{ alertComponent }</Collapse>
We can use <Alert> and <Dialog> components to create an alert in the react application
Example:
import React, { useCallback } from 'react'
import { makeStyles } from '#material-ui/core/styles'
import Alert from '#material-ui/lab/Alert'
import IconButton from '#material-ui/core/IconButton'
import CloseIcon from '#material-ui/icons/Close'
import Dialog from '#material-ui/core/Dialog'
const useStyles = makeStyles(theme => ({
root: {
'& > * + *': {
marginTop: theme.spacing(2),
},
width: '100%',
},
}))
export default function TransitionAlerts(props) {
const classes = useStyles()
return (
<div className={classes.root}>
<Dialog open={props.open}>
<Alert
action={
<IconButton
aria-label='close'
color='inherit'
size='small'
onClick={
useCallback(() => props.closeAlert({msg: '', open: false }))
}
>
<CloseIcon fontSize='inherit' />
</IconButton>
}
>
{props.msg}
</Alert>
</Dialog>
</div>
)
}

Using React Hooks reference with Class and Function

I've been out of the React game for awhile. Come back and I'm trying to implement the Material UI library which has been rewritten with Hooks.
It seems to be extremely confusing + spagetti code in my eyes.
I simply want to reference a function so I can toggle the drawer, how can I do this?
// Old class
export default class DefaultContainer extends Component<ViewProps, any> {
render() {
return (
<View>
<MainAppBar
onPress={() => this.onMenuPressed()}
/>
{this.props.children}
<MainDrawer
ref={'drawer'}
/>
</View>
);
}
onMenuPressed = () => {
// TODO The bit that isn't working
(this.refs['drawer'] as Drawer).handleToggle()
}
}
Now the new material UI drawer
// New Drawer (3x more code now..)
const useStyles = makeStyles({
list: {
width: 280,
},
fullList: {
width: 'auto',
},
})
type Props = {
}
function MainDrawer(props: Props, ref: any) {
const classes = useStyles();
const [state, setState] = React.useState({
left: false,
});
const toggleDrawer = () => (
event: React.KeyboardEvent | React.MouseEvent,
) => {
if (
event.type === 'keydown' &&
((event as React.KeyboardEvent).key === 'Tab' ||
(event as React.KeyboardEvent).key === 'Shift')
) {
return;
}
setState({ ...state, left: true });
};
const inputRef = useRef();
useImperativeHandle(ref, () => {
toggleDrawer()
});
const sideList = () => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer()}
onKeyDown={toggleDrawer()}
>
<List>
<ListItem button key={'drawer_item'}>
<ListItemIcon><GroupIcon /></ListItemIcon>
<ListItemText primary={'Test Item'} />
</ListItem>
</List>
</div>
);
return (
<div>
<Button onClick={toggleDrawer()}>Open Left</Button>
<Drawer open={state.left} onClose={toggleDrawer()}>
{sideList()}
</Drawer>
</div>
);
}
export default forwardRef(MainDrawer);
I'm struggling to understand why you need to invoke a function from inside MainDrawer rather than just leveraging the use of props e.g.
Container
export default function DefaultContainer(props: ViewProps) {
const [drawerOpen, setDrawerOpen] = React.useState(false);
// assuming it's a toggle?
const toggleMenu = React.useCallback(() => setDrawerOpen(open => !open));
return (
<View>
<MainAppBar onPress={toggleMenu} />
{this.props.children}
<MainDrawer open={drawerOpen} />
</View>
)
}
MainDrawer
function MainDrawer(props: Props) {
const [open, setOpen] = React.useState(props.open);
...
const toggleDrawer = React.useCallback(() => setOpen(open => !open));
return (
<div>
<Button onClick={toggleDrawer}>Open Left</Button>
// use prop to determine whether drawer is open or closed
<Drawer open={open} onClose={toggleDrawer}>
{sideList()}
</Drawer>
</div>
);
}

Resources