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.
Related
So I need a ReferenceField to access data from another table. Since I am doing this often i extracted this in a custom component
CustomField.tsx
const CustomField = (props: any) => {
const record = useRecordContext();
return (
<ReferenceField
record={record}
source="someId"
reference="table"
link="show"
label="some label"
{...props}
>
<TextField source="name" />
</ReferenceField>
);
};
now when i use the component:
<CustomField/>
everithing is working fine, data is displayed fine, except no label is shown.
So I am expecting some form of label at the top but no signs.
If I do it without creating a custom field, everything is working just fine, the label and sorting is there. But when I extract the code into a separate component it doesn't seem to work.
Looks like the attributes lose their default values and behavior when extracted to a separate component.
My current workaround
<OrganisationField label="Some label" sortBy="something" />
that is fine, it works but it's not practical (and it's annoying) to do this everytime I or someone else wants to use the component, since that should already be defined inside it.
When you say "no label is shown", I assume that's when you use your custom Field inside a Datagrid.
Datagrid inspects its children for their label prop to display the column header. To make your label inspect-able, declare it as defaultProp:
CustomField.defaultProps = {
label: "someId"
}
This is explained in the react-admin "writing a custom field" documentation: https://marmelab.com/react-admin/Fields.html#writing-your-own-field-component
I am trying to add an indeterminate state to row checkboxes based on selection status of other checkboxes inside a detail panel. To do this I am creating a custom checkbox component and doing some logic to see if indeterminate should be true, the only problem is the checkbox needs access to the row id it is associated with to do that check. Thus far I've found nothing to pass anything other than the given CheckboxProps given by MUI, which contains no row information. There is something called componentsProps where I can pass other props to a component, but I've yet to find a way to pass the particular row id to its associated checkbox. Does anyone know of a solution to this?
.
.
.
const customCheckbox = (props: CheckboxProps) => {
return <Checkbox {...props} indeterminate={someArray.includes(theRowIdThisCheckboxIsUsedIn)} />
};
<DataGridPro
{...data}
components={{
BaseCheckbox: customCheckbox,
}}
/>
hi when you use custom checkbox component you cant use onSelectionModelChange to give you rows id you must write their logic too
I'm trying to create a custom layout component, so I can design my Show page better.
I'm not sure why some things work and some don't. My plan is to use the Material-UI <Grid> component. I know that the Grid component doesn't pass the props, then I'm extending to my own:
export default function OrderShow(props) {
return (
<Show {...props}>
<CustomGrid>
<TextField source="shortId" label="Short ID" />
</CustomGrid>
</Show>
);
}
My CustomGrid component, which is just a draft yet, is cloning its children to pass the props:
function CustomGrid(props) {
return React.Children.map(props.children, (child) => React.cloneElement(child, props));
}
When I use it, the TextField receives the source, and renders it correctly. However the label is gone. I'm not sure why this happens.
Another problem is that when I try to use the CustomGrid with a ReferenceField, it doesn't work at all:
<Show {...props}>
<CustomGrid>
<ReferenceField source="user" reference="users" label="Name" link="show">
<FunctionField render={(record) => `${record.firstName} ${record.lastName}`} />
</ReferenceField>
</CustomGrid>
</Show>
I can see that the props are passed until the ReferenceField, but it's lost before reaching the FunctionField:
Is there a better way to customize the SimpleShowLayout and continue using the TextField and other components from react-admin?
Yes, there is a way. Simply put react-admin's SimpleShowLayout is a little bit more coplex then just passing the props to the children fields. That's why you have to recursively iterate until you reach a field and perform the stuff the react-admin does.
Finally I have decided to share some privately developed components in a new package I am building. I have added layouts which work both with Box and Grid material-ui's components. You can have a look here: ra-compact-ui
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.
Let's say I've got a Tooltip component that shows & hides depending on whether there is text data for it or not:
{this.state.tooltipText && (
<Tooltip
text={this.state.tooltipText} />
)}
If I wanted to transition this component in and out, I might instead include a boolean on prop and let the component manage transitioning out itself.
<Tooltip
on={this.state.tooltipText !== null}
text={this.state.tooltipText} />
With this setup, though, I lose the text prop right when the transition out should begin. There is very clearly never a state where on is false and I still have access to the text prop.
Is there a good way to handle this scenario?
Should the component keep an internal record of the previous state of tooltipText?
Or is it best to use two properties to track the state of the tooltip (tooltipOn and tooltipText) and never erase this.state.tooltipText?
Should the component keep an internal record of the previous state of tooltipText?
Yes. Otherwise the parent would have to decide when the transition starts / ends, which is not his authority.
Actually componentWillReceiveProps is best for this as you can access the current & next props:
componentWillReceiveProps(nextProps) {
if(this.props.text && !nextProps.text) {
this.fadeOut();
} else if(!this.props.text && nextProps.text) {
this.setState({ text: nextProps.text });
this.fadeIn();
}
}
Then the parent just has:
<Tooltip text={this.state.tooltipText} />