Custom layout in SimpleForm component on react-admin - reactjs

I want to create a custom two-column-grid layout on my react-admin project on Edit and Show pages. I want to display selectboxes and the imageupload area on the left column, and the text inputs on the right column by using only one <SimpleForm>.
Simply like this
If I use a div or a <Card> component under <SimpleForm> and <EditController> components, I receive an error.
Warning: React does not recognize the `basePath` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom
attribute, spell it as lowercase `basepath` instead. If you
accidentally passed it from a parent component, remove it from the DOM
element.
Is there any way to create a layout without this error?

I solved it with creating another component with using divs, <Grid/> etc, and used that component in <SimpleForm> component.
import {withStyles} from '#material-ui/core/styles';
import React from 'react';
import {
EditController,
SimpleForm,
TextInput,
SelectInput,
Title,
} from 'react-admin';
import Grid from '#material-ui/core/Grid';
import Card from '#material-ui/core/Card';
import Poster from "../customField/Poster";
import {EditToolbar} from '../toolbar/CustomToolbar'
import {EditActions} from '../toolbar/CustomActions'
const editStyles = {
root: {display: 'flex', alignItems: 'flex-start', width: '100%'},
form: {flexGrow: 9},
};
class CardEdit extends React.Component {
constructor(props) {
super(props);
this.state = {
refresh: false
};
}
render() {
const FormDiv = withStyles(editStyles)(({children, classes, ...props}) => {
return (
<div className={classes.root}>
<div className={classes.form}>
<Grid container spacing={24}>
<Grid item xs={6}>
<TextInput source="name" fullWidth />
</Grid>
<Grid item xs={6}>
<TextInput source="card_id" fullWidth />
</Grid>
</Grid>
</div>
</div>
)
}
)
return (
<EditController {...this.props}>
{({resource, record, redirect, save, basePath, version}) => {
return (
<div>
<Title defaultTitle="sample"/>
<Card>
<div style={{ margin: '20px 20px 0 0' }}>
<EditActions
basePath={basePath}
resource={resource}
data={record}
hasShow
hasList
/>
</div>
{record && (
<SimpleForm
basePath={basePath}
redirect={redirect}
resource={resource}
record={record}
save={save}
version={version}
toolbar={<EditToolbar/>}
>
<FormDiv record={record} />
</SimpleForm>
)}
</Card>
</div>
)
}}
</EditController>
)
}
}
export default withStyles(editStyles)(CardEdit);

Actually, this could be done a little bit easier in case you don't need any custom styles and what not.
In order to get rid of the basePath error, just sanitize the props passed to the Material UI Grid Component:
const SanitizedGrid = ({basePath, ...props}) => {
return (
<Grid {...props} />
);
};
Then use it in place of a normal Grid:
export default props => (
<SimpleForm {...props}>
<SanitizedGrid container spacing={16}>
<Grid item xs>
<TextInput source="name" />
</Grid>
</SanitizedGrid>
</SimpleForm>
);

As another way, I've just worked out (thanks to Alexander's answer) a nice generic way to add any custom HTML content to a react-admin form:
import React, { Fragment } from 'react';
import { SimpleForm } from 'react-admin';
const CustomContent = ({ basePath, record, resource, children }) => (
<Fragment>
{children}
</Fragment>
);
export const MyForm = (props) => (
<SimpleForm>
<CustomContent>
<h3>Custom Content</h3>
<p>I can now add standard HTML to my react admin forms!</p>
</customContent>
</SimpleForm>
);
You get the basePath prop (which you probably don't want), but the record and resource props might be useful to your custom content (if you switch the code to use a render prop)

Related

How to make Chakra UI box/flex item to take 100% width

I'm having a problem with the Chakra UI box or flex items (not sure which one is causing the problem). I can't make them take 100% width on tablets and mobiles. The only way I managed to make it work is by defining VW width but that's not responsive on all devices.
How it looks now:
how it should look:
Code (the part of code that's not working is the first ternary option):
enter code here
import React from 'react'
import { Box, Flex, useMediaQuery } from '#chakra-ui/react'
import Section from 'components/section/Section'
import HoursCellList from 'components/schedule/hours/HoursCellList'
import DaysCellList from './days/DaysCellList'
import EventsComponent from 'components/schedule/events/EventsComponent'
import MobileDaysCellList from './days/MobileDaysCellList'
import MobileEventsFlex from 'components/schedule/events/MobileEventFlex'
interface Props {}
const ScheduleComponent = (props: Props) => {
const [isSmallScreen] = useMediaQuery('(max-width: 1350px)')
const [mobileClickedDay, setMobileClickedDay] = useState<number>(0)
return (
<Section isGray heading="Schedule" id="schedule">
<Box py={['2', '4', '8']}>
{isSmallScreen ? (
<Flex justifyItems="stretch">
<Box>
<HoursCellList isMobile={isSmallScreen} />
</Box>
<Flex flexDirection="column" bg="#fff" flex="1">
<MobileDaysCellList clickedDay={mobileClickedDay} onClick={setMobileClickedDay} />
<MobileEventsFlex clickedDay={mobileClickedDay} />
</Flex>
</Flex>
) : (
<>
<HoursCellList isMobile={isSmallScreen} />
<Flex w="1200px" bg="#fff">
<DaysCellList />
<EventsComponent />
</Flex>
</>
)}
</Box>
</Section>
)
}
export default ScheduleComponent
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
All other page components are in Section component as children, and they work fine, just this one is causing the problem...
Section component code:
import React from 'react'
import SectionHeading from './SectionHeading'
import { Box, Center, BoxProps } from '#chakra-ui/react'
interface Props {
heading?: string
subHeading?: string
isGray?: boolean
id?: string
children: React.ReactNode
}
type SectionProps = Props & BoxProps
const Section = ({ children, heading = '', subHeading = '', isGray = false, id = '', ...props }: SectionProps) => {
return (
<Center>
<Box
bg={isGray ? 'primaryBg' : 'secondaryBg'}
color="primaryText"
zIndex="0"
w="100%"
pt={['47px', '56px', '70px']}
pb={['12', '16', '24']}
minHeight="100vh"
d="flex"
alignItems="center"
{...props}
id={id}
>
<Box maxWidth="1366px" mx="auto" px={['15px', '43px', '83px']}>
<Box>
<SectionHeading heading={heading} subHeading={subHeading} />
{children}
</Box>
</Box>
</Box>
</Center>
)
}
export default Section
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I can't believe that the problem is such a stupid thing haha,
Problem is fixed by adding w='100%' in Section component next to maxWidth='1366px'

How do I set the onClick in the navbar of material-ui?

Below is the code for creating the navigation bar/header for an application. I have used material-ui.
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import AccountCircle from "#material-ui/icons/AccountCircle";
import ShoppingCartOutlinedIcon from "#material-ui/icons/ShoppingCartOutlined";
const styles = (theme) => ({
root: {
width: "100%",
},
flex: {
flex: 1,
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
});
class Nav extends Component {
render() {
const { classes } = this.props;
return (
<AppBar position="static" elevation={0}>
<Toolbar>
<IconButton
className={classes.menuButton}
color="contrast"
onClick={this.props.toggleDrawer}
>
<MenuIcon />
</IconButton>
<Typography className={classes.flex} type="title" color="inherit">
Pizza Shop
</Typography>
<div>
<IconButton color="contrast" onClick={this.props.cart}>
<ShoppingCartOutlinedIcon />
</IconButton>
<IconButton color="contrast" onClick={this.props.login}>
<AccountCircle />
</IconButton>
</div>
</Toolbar>
</AppBar>
);
}
}
Nav.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Nav);
I'm new to 'props' and I'm not sure what this.props.cart or this.props.login will do. I want to create a functionality where when I click on the above two icons, I'm directed to some another component, but I'm not sure how to do it. Please help me to understand it.
Props are just parameters that the parent component sent to the children component. In your example this.props.cart and this.props.login are functions ( I am not sure about the cart, but it is used as a function ). From your example, when you click on the icons, you will call cart and login functions sent from the parent.
The answer to your question "How do you set onClick" is you already doing it on the login function/method. So you need to look at the parent component implementation.
Below I wrote a much more readable example, so feel free to look
import React from 'react'
class ChildrenComponent extends React.Component {
render() {
// This is doing the same thing, just we call the function / method in little different way
// return <div onClick={() => { this.props.onShoppingSubmit() }}>Aaa</div>
return <div onClick={this.props.onShoppingSubmit}>Aaa</div>
}
}
class ParentComponent extends React.Component {
handleShoppingSubmit = () => {
// Do what every that function needs to do
console.log('Hi from parent')
}
render() {
return (
<div>
<ChildrenComponent onShoppingSubmit={this.handleShoppingSubmit} />
</div>
)
}
}

Prevent Chip component to send a REST request

I have the following code:
import React from 'react';
import PropTypes from 'prop-types';
import Chip from '#material-ui/core/Chip';
import withStyles from '#material-ui/core/styles/withStyles';
const styles = {
root: {
margin: 4,
},
};
function CustomChipField({ root, classes, record, onClick }) {
return (
<Chip className={classes.root} label={`${record.name}`} onClick={onClick} />
);
}
CustomChipField.propTypes = {
classes: PropTypes.shape({}).isRequired,
record: PropTypes.shape({}),
onClick: PropTypes.func.isRequired,
};
CustomChipField.defaultProps = {
record: {},
};
export default withStyles(styles)(CustomChipField);
What is it? It is a custom Chip component inheriting material-ui's chip.
But what I haven't figured out yet is why it sends REST request when I click it.
The example of such a request: http://localhost:3000/#/users/{"name"3A"whatever_name"}
I have an onClick prop overriden, and it was my attempt to override it but it doesn't do anything.
I use this component in the SingleFieldList of react-admin, and maybe the problem in react-admin but I use custom Chip component directly inherited from material-ui.
The code from react-admin:
export const UserList = props => (
<List {...props}>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="username" />
<ArrayField source="some_array">
<SingleFieldList>
<CustomChipField
source="name"
size="small"
clickable={true}
onClick={handleClick}
/>
</SingleFieldList>
</ArrayField>
</Datagrid>
</List>
);
And once again - onClick prop doesn't work.
So the question is: how to whether prevent Chip component sending a REST-request, whether to customize it.
This worked for me:
<SingleFieldList linkType={false}>
<CustomChipField />
</SingleFieldList>

Not able to upload Image with ImageField in EDIT mode for react-admin

Using React Admin I am creating a dashboard for one of my clients and I have a requirement where I have to add the products of the client, out of the many fields there is one Image field too where I have to upload an image which serves in the API and the product is created with CREATE of react-admin.
// create product
import React, { useState} from "react";
import {
SimpleForm,
Create,
ImageField,
ImageInput,
} from "react-admin";
import Grid from "#material-ui/core/Grid";
import { ThemeProvider } from "#material-ui/styles";
import customTheme from "../../customTheme";
const CreateProduct = props => {
const classes = useStyles();
return (
<ThemeProvider theme={customTheme}>
<Create resource="products" basePath="/products">
<SimpleForm>
<Grid
container
spacing={2}
justify="space-between"
>
<Grid item xs={10}>
<ImageInput
source="data.pictures"
label="Images"
accept="image/png, image/jpg, image/jpeg"
maxSize={5000000}
placeholder={
<p>
Upload Image
<span >
*File size should not exceed 5MB
</span>
</p>
}
>
<ImageField source="src" title="images" />
</ImageInput>
</Grid>
</Grid>
</SimpleForm>
</Create>
</ThemeProvider>
);
};
export default CreateProduct;
Once a product is created I need to EDIT that product too, and with the same respect, I need to update the Image too.
//Edit Product
import React, { useState} from "react";
import {
SimpleForm,
Create,
ImageField,
ImageInput,
} from "react-admin";
import Grid from "#material-ui/core/Grid";
import { ThemeProvider } from "#material-ui/styles";
import customTheme from "../../customTheme";
const PreviewImage = ({ record }) => (
<img width={30} src={record} alt="Image Preview" />
);
const EditProduct = props => {
const classes = useStyles();
return (
<ThemeProvider theme={customTheme}>
<Edit {...props}>
<SimpleForm>
<Grid
container
spacing={2}
justify="space-between"
>
<Grid item xs={10}>
<ImageInput
source="data.pictures"
label="Images"
accept="image/png, image/jpg, image/jpeg"
maxSize={5000000}
placeholder={
<p>
Upload Image
<span >
*File size should not exceed 5MB
</span>
</p>
}
>
//<ImageField source="src" title="images" />
<PreviewImage />
</ImageInput>
</Grid>
</Grid>
</SimpleForm>
</Edit>
</ThemeProvider>
);
};
export default EditProduct;
The issue with EditProduct is I am not able to fetch the image from the record which is a URL with the help of ImageField used inside ImageInput and in order to achieve that I've created a separate component PreviewImage which fetched the image from the record and render it in img tag, but I would like to upload the new image too to the edit product form.
And I'm not able to achieve that with current documentation present in react-admin.
If anyone is aware of how I could achieve this EDIT functionality through react-admin, please post your solutions.
it works for me
const PreviewImage = ({ record, source }) => {
if (typeof (record) == "string") {
record = {
[source]: record
}
}
return <ImageField record={record} source={source} />
}
....
<ImageInput source="preview">
<PreviewImage source="src" />
</ImageInput>

Material-ui svg-icons inside another element doesnt align to the center

I'm using the material ui library in my react project, and I have come across a strange issue, when I try to use svg icons inside a button-icon, the icom doesn't align to the center.
for example:
<ListItem key={product.id}
primaryText={product.title}
leftAvatar={<Avatar src={product.img}/>}
rightIcon={<IconButton><RemoveIcon/></IconButton>}/>
for this code I will get the following result:
And for this code:
<ListItem key={product.id}
primaryText={product.title}
leftAvatar={<Avatar src={product.img}/>}
rightIcon={<RemoveIcon/>}/>
I will get the following result :
My question is, how do i get to the result of my second example, but that the icon will we inside another element?
This is kind of late but I recently had the same issue and solved it by wrapping the IconButton component in a custom component and extending the css. You may have to change some other CSS to make it align perfectly but this worked for my use case.
import React, { PropTypes, Component } from 'react';
import IconButton from 'material-ui/IconButton';
const CustomIconButton = (props) => {
const { style } = props;
const additionalStyles = {
marginTop: '0'
};
return(
<IconButton {...props } style={{ ...style, ...additionalStyles }} iconStyle={{ fontSize: '20px' }}/>
);
};
CustomIconButton.PropTypes = {
// listed all the props that IconButton requires (check docs)
};
export default PPIconButton;
This is what a simplified usage of this custom IconButton looks like:
const deleteIconButton = (deleteFunc) => {
return <CustomIconButton
touch={true}
tooltip="Delete"
tooltipPosition="top-right"
onTouchTap={deleteFeed}
iconClassName="fa fa-trash"
/>;
};
class MyList extends Component {
render() {
return (
<div>
<List>
<ListItem value={ i } primaryText="My List Item" rightIcon={ deleteIconButton(() => this.props.deleteFeed(i) } />
) }
</List>
</div>
);
}
}
Passing the styles down to the inner element worked for me:
return <SvgIcon style={this.props.style} />
check this code, working fine for me
import React from 'react';
import List from 'material-ui/List';
import ListItem from 'material-ui/List/ListItem';
import Delete from 'material-ui/svg-icons/action/delete';
const MenuExampleIcons = () => (
<div>
<List style={{width:"300px"}}>
<ListItem primaryText="New Config" leftIcon={<Delete />} />
<ListItem primaryText="New Config" rightIcon={<Delete />} />
</List>
</div>
);
export default MenuExampleIcons;

Resources