Reactjs material ui prevent re-render tabs on enter - javascript

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.

Related

When does React re-render the parent component?

I'm currently learning React, and trying to get a sense of how components re-render. I have this parent component which renders three items. Each item just renders an <li>
function App() {
console.log("Parent Rerendered");
return (
<div>
<ul>
<Item1 />
<Item2 />
<Item3 />
</ul>
</div>
);
}
Item2 is a bit different because it also renders an "x" that will un-render the component when it's clicked:
function Item2() {
const [visible, setVisible] = useState(true);
const makeInvisible = () => {
setVisible(false);
};
console.log("Item 2 Rerendered");
return visible ? (
<div>
<li>
Second Item <span onClick={makeInvisible}>X</span>
</li>
</div>
) : null;
}
When I test this in my browser and click the "x", I can see from the console that Item2 gets re-rendered. However, none of the other components get re-rendered including the parent component. However the parent component does change, so how does this happen without re-rendering it.
If that's a bit confusing, here's an illustration of the initial state, my expectations, and reality. What am I misunderstanding about how React re-renders components?
A component rerenders when it sets state, or when its parent rerenders1. App has no state and no parent, so it will never rerender. It doesn't need to though. React saves the virtual DOM from the previous render, so it still knows that App is supposed to be a div surrounding a ul surrounding an Item1, Item2, and Item3. If the Item2 rerenders, and returns a null instead of a div, react will update the part of the real DOM that the Item2 is responsible for, by removing the div. The rest of the page remains intact
1) or if a context it consumes changes, or in a class component when you call forceUpdate. But for most cases, it's just state and parent that matter.
Instead of clicking onto <Item2 /> go to the Dev-tools -> Explorer -> select the Element and press delete. The view will also change, the gap will close, without react being involved at all.
React is responsible to update the DOM, the layout is done by the browser. So when <Item2 /> decides it wants to be rendered as null instead of a div>li (??? invalid markup ) and therefore removes the respective DOM-nodes, the browser will update the layout.
And the parent component has nothing to do with all that.

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: 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.

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.

React.js onShow event handler

I have a tabbed menu on my page, which is basically a bunch of divs that get shown/hidden depending on the tab that's clicked. I want to be able to refetch the data in a React Component in one of the divs once it appears.
componentDidMount fires at the very beginning, so it's not an option. It would be nice to just have an HTML event onShow similar to onClick but nothing like that seems to exist.
Any suggestions?
I have a Tabs component but in my case, all tabs content are mounted but not displayed. When I click on a tab, I just change the visibility with css
But to refetch data, you should make a component for the containers that fetch the data in his componentDidMount so, in the top level if you switch which one renders, the componentDidMount will trigger.
Put some code if you want some help with your case
Per the docs, componentDidUpdate is probably what you're looking for. It's called after props and/or state has changed and the component has re-rendered (not called for the initial render).
That said, it's probably simpler to just handle the data fetching in the onClick handler itself or better yet, fetch the data once in the parent component and pass down the relevant slice of data to the tabs and then show / hide as appropriate.
Hey You have to separate out the tab as component and render those component based on tab changed let me give one example over here.
Suppose I have one Page called MainPage in that Page I have Tab Control which Contain the Three tab First,Second and Third.
first you have to create a three Component like
var First=React.createClass({ // First Tab Content goes here });
var Second=React.createClass({// Second Tab Content goes here});
var Second=React.createClass({// Third Tab Content goes here});
Now your MainPage Should look like
var MainPage=React.createClass({
onClick:function(e)
{
if($(e.target).attr('id')=="1")
{
React.render(<First />,document.getElementById('tab-content'));
}
else if($(e.target).attr('id')=="2")
{
React.render(<Second />,document.getElementById('tab-content'));
}
if($(e.target).attr('id')=="3")
{
React.render(<Third />,document.getElementById('tab-content'));
}
},
render:function()
{
return(
<div>
<div>
//Three Tabl goes here
<div id="1" onClick={this.HandleTab}>Tab 1</div>
<div id="2" onClick={this.HandleTab}> Tab 2</div>
<div id="3" onClick={this.HandleTab}>Tab 3</div>
</div>
//your Container Div
<div id="tab-content">
</div>
</div>
)
}
})

Categories

Resources