Material-UI custom JSS Styles removed on Component Re-render - reactjs

I have a temporary Drawer with Tabs inside. When the Drawer is open and the color theme is toggled, the custom styles for the component in the tab are removed, breaking the styling. If I close the Drawer or toggle between tabs, it fixes the issue. This is only happening in two of the tabs, not all of them. See the pictures below.
For simplicity, I'll show the code for the Check Results tab. The Cog icon is being stripped of its styling and defaulting to the left.
...
const styles: StyleRulesCallback = () => ({
buttonDiv: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'flex-end',
},
});
...
type Props = StateProps & DispatchProps & WithStyles<typeof styles>;
class CheckResultsPanel extends React.Component<Props> {
...
render() {
this.selectedKeys = [];
const tree = this.renderTree();
const { classes } = this.props;
return (
<div id="checkResultsPanel">
<div className={classes.buttonDiv}>
<IconButton onClick={this.designSettingsClick} aria-label="Design Settings">
<SettingsIcon />
</IconButton>
</div>
<Divider />
{tree}
</div>
);
}
The components in question are being imported to the Drawer like so:
<div className="right-sidebar-container">
<div id="loadCaseObjectTabs" className={classes.root}>
<AppBar position="static">
<Tabs value={this.state.topValue} onChange={this.topHandleChange} fullWidth>
<Tab label="Objects" />
<Tab label="Load Cases" />
</Tabs>
</AppBar>
{topValue === 0 ? <div className={classes.tabs}><NavigationPanel /></div> : undefined}
{topValue === 1 ? <div className={classes.tabs}><LoadCasesPanel /></div> : undefined}
</div>
<div id="checkResultsPropertiesTabs" className={classes.root}>
<AppBar position="static">
<Tabs value={bottomTabIndices[this.props.bottom]} onChange={this.bottomHandleChange} fullWidth>
<Tab label="Check Results" />
<Tab label="Properties" />
</Tabs>
</AppBar>
{this.props.bottom === 'checkResults' ? <div className={classes.tabs}><CheckResultsPanel /></div> : undefined}
{this.props.bottom === 'properties' ? <div className={classes.tabs}><PropertiesPanel /></div> : undefined}
</div>
</div>

We discovered the issue. Apologies for the vague example. The issue was caused by optimization we implemented. The component was only set to update if certain props were changed and the classes were not being passed through this logic. When the themes were toggled, it changed all the styling for the application but since the component did not update the styles were not being applied.
shouldComponentUpdate(nextProps: Props) {
...
if (this.props.classes !== nextProps.classes) return true;
...
}

Related

How to make a reusable Material UI tabs component in React

I am working on a React project, and I divided some of the pages into tabs using the MUI Tab component, so I can have multiple components in one page and render each component accordingly, so I have created the Tabs component. However, I can only see one first index tab.
Reusable tab MUI component:
export default function useTabs(props) {
const { children, value, index, label, ...other } = props;
const [selectedValue, setSelectedValue] = React.useState(0);
const handleChange = (event, newValue) => {
setSelectedValue(newValue);
};
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={selectedValue}
onChange={handleChange}
className={classes.tab}
textColor="primary"
indicatorColor="primary"
>
<Tab label={label} {...a11yProps(0)} className={classes.tabText} />
{/* <Tab className={classes.tabText} label={label} {...a11yProps(1)} /> */}
</Tabs>
</Box>
<TabPanel value={selectedValue} index={index}>
{children} // Rendering tab children here, but getting only the first component
</TabPanel>
</Box>
);
}
Here is how I am using it:
// Import the reusable component
import Tab from "../common/Tabs";
export default function JobsRecruitments() {
return (
<>
<Tab label="tab name" index={0}>
<MyComponent />
</Tab>
</>
)
}
I don't think it's a good idea to use children if you want reusable tabs. That's because your tabs have to be consistent: if you have 3 child nodes, you should also have 3 tab labels. If you're using children, you can easily lose track of that.
However, if you want to make a reusable tab component, I would suggest passing an array of objects containing the tab label and Component as a property to your custom Tab component. Here's an example of what I mean:
export default function BasicTabs({ tabs }) {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
{tabs.map(({ label }, i) => (
<Tab label={label} key={i} />
))}
</Tabs>
</Box>
{tabs.map(({ Component }, i) => (
<TabPanel value={value} index={i} key={i}>
{Component}
</TabPanel>
))}
</Box>
);
}
And then you can use this component like this:
import Tabs from "./MyTabs";
const tabs = [
{
label: "Tab 1",
Component: <div>Hello, I am tab 1</div>
},
{
label: "Tab 2",
Component: <div>Hello, I am tab 2</div>
},
{
label: "Tab 3",
Component: (
<div>
<h1>Tab with heading</h1>
<p>Hello I am a tab with a heading</p>
</div>
)
}
];
export default function App() {
return (
<div>
<Tabs tabs={tabs} />
</div>
);
}
Here's the Codesandbox with the full example

Using Grid with SortableJS

I am using SortableJS for React and I am trying to implement horizontal Grid, so that the final result would look like this:
I managed to do that, however the sort function does not work at all. I think it has something to do with the tag attribute. When I try tag={Grid} I get following error:
Sortable: el must be an HTMLElement, not [object Object]
Any ideas how can I implement Grid to the tag attribute? Here is my code:
<Grid container>
<Sortable options={{ fallbackOnBody: true, group: "items", handle: reorderHandle }} tag={Grid} onChange={handleChange}>
{items.map((item, index) =>
<Paper key={index} data-id={index} className={classes.item} elevation={0}>
<ButtonHelper {...getHandleClass(editable)} key={index} icon={
<Badge badgeContent={index + 1} color="default">
{editable && <ReorderIcon />}
</Badge>}
/>
<Grid item xs={4} key={index}>
<div className={classes.gridItem}>
<GalleryInput className={classes.image} style={{ width: '300px' }} label="image" source={`${source}[${index}].image`} />
<br></br>
<TextInput label="desc" source={`${source}[${index}].desc`} />
{editable && <ButtonHelper icon={<RemoveIcon />} onClick={handleRemove(index)} className={classes.left} />}
</div>
</Grid>
</Paper>
)}
</Sortable>
</Grid>
Thank you for any help.
Since you are using a custom component, the tag prop accepts it as a forwarRef component only.
So you need to update in that way.
Sample Snippet
// This is just like a normal component, but now has a ref.
const CustomComponent = forwardRef<HTMLDivElement, any>((props, ref) => {
return <div ref={ref}>{props.children}</div>;
});
export const BasicFunction: FC = props => {
const [state, setState] = useState([
{ id: 1, name: "shrek" },
{ id: 2, name: "fiona" }
]);
return (
<ReactSortable tag={CustomComponent} list={state} setList={setState}>
{state.map(item => (
<div key={item.id}>{item.name}</div>
))}
</ReactSortable>
);
};

Reusing multiple instances of react component with different props

So I have a child component that I want to render multiple instances of in a parent container component. Passing in different props to each so they display differently.
What is happening is that they are both being rendered with the last instance of the props in the script being read into both instances. Thus the both components below end up with placeHolder==='Describe yourself'
Is there a work around for this so that they will each be injected with their props in turn exclusively?
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
buttonName='Update'
modalOpen={this.modalOpen}
placeHolder="New picture url"
change={this.handlePicture}
label='URL'
/>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
buttonName='Update'
modalOpen={this.modalOpen}
placeHolder='Describe yourself'
label='Bio'
change={this.handleBio}
/>
ButtonMode
class ButtonMode extends Component {
constructor(props){
super(props)
this.state = {
input:''
}
this.handleInput = this.handleInput.bind(this);
this.handle = this.handle.bind(this);
}
handleInput(val){
this.setState({input:val})
};
handle() {
this.props.change(this.state.input);
};
render(){
const { classes } = this.props;
return (
<div>
<Button
className={classes.button}
onClick={this.props.modalOpen}
>Update
</Button>
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={this.props.open}
onClose={this.props.handleClose}
>
<div className={classes.paper}>
<TextField
id="filled-textarea"
label={this.props.label}
placeholder={this.props.placeHolder}
multiline
className={classes.textField}
onChange={(e)=>{this.handleInput(e.target.value)}}
rows= '4'
/>
<Button
onClick={this.handle}
className={classes.button}
color="secondary">Submit</Button>
</div>
</Modal>
</div>
)
}
}
Then I used it like that
class UserCard extends Component {
constructor(props){
super(props);
this.state = {
tempPro:'',
open: false,
profilePicture:''
}
this.modalOpen = this.modalOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handlePicture = this.handlePicture.bind(this);
}
// componentDidMount(){
// const {userId, profilePic} = this.props;
// this.setState({profilePicture:profilePic});
// // axios.get(`/api/profile/${userId}`).then(res=>{
// // let {profilePic} = res.data[0];
// // this.setState({profilePic})
// // })
// }
handlePicture(val){
this.props.changePic(val);
this.setState({open:false});
};
handleBio(val){
this.setState({open:false});
};
handleClose(){
this.setState({open: false});
};
modalOpen(){
this.setState({open:true});
};
render() {
const { classes } = this.props;
const {stories} = this.props;
let storyShow = stories.map((story,id) => {
return(
<div value={story.story_id}>
<h3>{story.title}</h3>
<ul className={classes.background}>
<li>{story.description}</li>
<li>{story.is_complete}</li>
</ul>
</div>
)
});
return (
<div className={classes.rootD}>
<Grid container>
<Grid className={classes.itemFix} >
<Card className={classes.card}>
<CardMedia
className={classes.media}
image={this.props.proPic}
title={this.props.userName}
/>
<div>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
modalOpen={this.modalOpen}
placeHolder="New picture url"
change={this.handlePicture}
label='URL'
/>
</div>
<CardHeader
className={classes.titles}
title={this.props.userName}
subheader="Somewhere"
/>
<CardHeader className={classes.titles} title='Bio' />
<CardContent className={classes.background}>
<Typography className={classes.bio} paragraph>
{this.props.bio}
</Typography>
</CardContent>
<div>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
modalOpen={this.modalOpen}
placeHolder='Describe you how you want'
label='Bio'
change={this.handleBio}
/>
</div>
</Card>
</Grid>
<Grid className={classes.itemFixT}>
<Card className={classes.card}>
<CardContent>
<CardHeader
className={classes.titles}
title='Works'/>
<Typography paragraph>
<ul>
{storyShow}
</ul>
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</div>
);
}
}
UserCard.propTypes = {
classes: PropTypes.object.isRequired,
};
function mapStateToProps(state){
const {userId, profilePic} = state;
return {
userId,
profilePic
}
}
export default connect(mapStateToProps,{})(withStyles(styles)(UserCard));
I had a similar issue where I was trying to pass different functions to the children components. I had a UploadFile component that contained an <input/> and a <Button/> from material-ui, and I wanted to reuse this component multiple times throughout a page, as the user has multiple files to upload, and in order to save the files, I needed callback functions in the main page.
What I had to do, was give each child component <UploadFile/> in my case, and <ButtonMode/> in your case, a unique id passed in as a prop, since otherwise, the top level page cannot tell each reference to the child component apart from any others.
The code of the child component:
function UploadFile({ value, handleFile }) {
const classes = useStyles();
return (
<>
<input
accept=".tsv,.fa,.fasta"
className={classes.input}
id={value}
type="file"
style={{ display: 'none' }}
onChange={e => handleFile(e.target.files[0])}
/>
<label htmlFor={value}>
<Button
variant="contained"
color='default'
component="span"
startIcon={<CloudUploadIcon />}
className={classes.button}>
Upload
</Button>
</label>
</>
);
}
The usage of this component in the parent (handleFile is the function I am passing in and is defined above in the parent component):
<UploadFile value='prosite' handleFile={handlePrositeFile} />
<UploadFile value='pfam' handleFile={handlePfamFile} />
I spent an embarrassingly long time on a similar issue. I tried all sorts of JS debugging and even re-read the entire concept of closure :)
This is was my culprit: <TextField id="filled-textarea" ... />
i.e. the id is static. If we have multiple instances of the same id on one page, we have a problem.
Make id dynamic, e.g. <TextField id={this.props.label} ... />
I was using the same state for both modals and in each instance of handleOpen() it was only ever opening the last instance of modal in the script.

Tabs Component including Cards crashing

I have to build a web application using React & Material-UI frameworks.
I use a tab component to display mutliple cards (includiing charts) inside. The first tab works pretty good, but when I want to switch to the second tab, there is no displaying inside the tab. Worst, I can't return to the first tab ; the Tabs Component is completely dead.
You can find my code here :
class NavTabs extends React.Component {
state = {
value: 0,
};
handleChange = (value) => {
this.setState({ value });
};
render() {
const { classes } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs fullWidth value={value} onChange={this.handleChange}>
<Tab label="FINANCIAL" />
<Tab label="OPERATIONAL" />
<Tab label="PLANNING" />
</Tabs>
</AppBar>
{value === 0 && <TabContainer>
<div className={classes.card_up}>
<CashWindow />
<SalesWindow />
</div>
<div className={classes.card_up}>
<CostsWindow />
<MarginsWindow />
<ContingenciesWindow />
</div>
<Button className={classes.button_more}>Details</Button>
</TabContainer>}
{value === 1 && <TabContainer>
<div className={classes.card_up}>
<OpportunitiesWindow />
<CriticalIssuesWindow />
</div>
<div className={classes.card_up}>
<QualityWindow />
</div>
<Button className={classes.button_more}>Details</Button>
</TabContainer>}
{value === 2 && <TabContainer>
<div className={classes.card_up}>
<ResourcesWindow />
</div>
</TabContainer>}
</div>
);
}
}
I don't have any error with the others components, because when I am trying to move components of the second tab into the first, it works. My conclusion is that the problem come from the Tabs Component.
Does anyone know how to fix this ?
Maybe I am doing wrong, I am a beginner in React programming.

ReactJS - Rendering a specific component based on scenario

I'm trying to render a specific component based on stage scenario of the page. I'm using a varriable "transitComponent" to render one of three components - a circular progress (wait) or one of two buttons once the response is received.
Any suggestions?
render() {
const { classes } = this.props;
if (this.state.stage==1){ transitComponent = CircularProgress};
if (this.state.stage==2){ transitComponent = CancelButton };
if (this.state.stage==3){ transitComponent = OKButton };
return (
<div align="center">
<br />
<Button align="center" variant="contained" color="primary" onClick= {this.handleOpen}>Create Profile</Button>
<Modal aria-labelledby="simple-modal-title" aria-describedby="simple- modal-description" open={this.state.open} onClose={this.handleClose}>
<div style={getModalStyle()} className={classes.paper}>
<Typography variant="title" id="modal-title" align="center">
{this.state.message}
</Typography>
{transitComponent}
</div>
</Modal>
</div>
);
}
You are assigning CircularProgress, CancelButton or OKButton to a temporary variable transitComponent, depending on the current state.stage. That's OK, the only part you got wrong is how you render that component.
Since transitComponent is a component like any other, you don't render it with curly braces, but as a component, so <transitComponent /> would be the proper way.
One more thing: Since React naming convention requires you to name components capitalized, you should name it TransitComponent and render it as <TransitComponent />.
And don't forget to declare TransitComponent with a let statement!
Updated code example:
render() {
const { classes } = this.props;
let TransitComponent;
if (this.state.stage==1){ TransitComponent = CircularProgress};
if (this.state.stage==2){ TransitComponent = CancelButton };
if (this.state.stage==3){ TransitComponent = OKButton };
return (
<div align="center">
<br />
<Button align="center" variant="contained" color="primary" onClick={this.handleOpen}>Create Profile</Button>
<Modal aria-labelledby="simple-modal-title" aria-describedby="simple-modal-description" open={this.state.open} onClose={this.handleClose}>
<div style={getModalStyle()} className={classes.paper}>
<Typography variant="title" id="modal-title" align="center">
{this.state.message}
</Typography>
<TransitComponent />
</div>
</Modal>
</div>
);
}

Resources