React implementation of Material Design with presentational components - reactjs

Is it possible to use Material UI with stateless components, or is state a requirement?
I intended to implement Popovers, and from what I gathered from the official code example is that it's state-depenedent.

It depends on the Material UI component.
Some are ideal, and are recommended, as stateless components. In fact, many of the examples in the Material UI documentation use stateless components. For example, a <Badge /> component:
const BadgeExampleSimple = () => (
<div>
<Badge
badgeContent={4}
primary={true}
>
<NotificationsIcon />
</Badge>
<Badge
badgeContent={10}
secondary={true}
badgeStyle={{top: 12, right: 12}}
>
<IconButton tooltip="Notifications">
<NotificationsIcon />
</IconButton>
</Badge>
</div>
);
Or an <Icon />:
const HomeIcon = (props) => (
<SvgIcon {...props}>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</SvgIcon>
);
Other components require them to be stateful to manage state like open.
And this is the case for the <Popover /> component unfortunately.
export default class PopoverExampleSimple extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
}
...
}
So, to answer your questions:
Yes, it's possible to use a stateless component if the component does not require state
No, it's not a requirement to use a stateful component, unless the component requires state

Related

material-ui v4.0.1 warning "Expected an element that can hold a ref"

I upgraded to material-ui v4.0.1 and I see that Modals now require a forwarded ref. I'm having some trouble implementing a fix for this using class components and Dialogs.
I tried using React.createRef() as well as React.forwardRef((props, ref) => (...) in a few places but I can't figure out how to resolve this warning.
In my parent component I render a custom component
<ApolloFormDialog />
In ApolloFormDialog I render essentially:
<Dialog ...>
{title}
{subtitle}
{form}
</Dialog>
The full warning is Warning: Failed prop type: Invalid prop 'children' supplied to 'Modal'. Expected an element that can hold a ref. Did you accidentally use a plain function component for an element instead?
However I am using class components currently. Migrating to use function components is not an option right now as my app is rather large.
I have tried adding a ref to ApolloFormDialog as
<ApolloFormDialog ref={React.createRef()} />
as well as wrapping ApolloFormDialog's class with:
export default React.forwardRef((props, ref) => <ApolloFormDialog ref={ref} {...props}/>)
and then adding that ref to the dialog as
<Dialog ref={this.props.ref} />
but the warning still persists, and I'm not sure where to go from here.
My issue didn't actually have to do with Dialog, but with the prop TransitionComponent on Dialog.
I switch between two types of transitions in my ApolloFormDialog depending on if the screen is below a certain breakpoint, which was being called as function components:
<Dialog
open={open}
onClose={onRequestClose}
classes={{
paper: classnames(classes.dialogWidth, classes.overflowVisible),
}}
fullScreen={fullScreen}
TransitionComponent={
fullScreen ? FullscreenTransition : DefaultTransition
}
>
{content}
</Dialog>
FullscreenTransition and DefaultTransition come from a file and are defined as follows:
import React from 'react'
import Fade from '#material-ui/core/Fade'
import Slide from '#material-ui/core/Slide'
export function DefaultTransition(props) {
return <Fade {...props} />
}
export function FullscreenTransition(props) {
return <Slide direction='left' {...props} />
}
export function FullscreenExpansion(props) {
return <Slide direction='right' {...props} />
}
Changing these functions to the following fixed my issue:
import React from 'react'
import Fade from '#material-ui/core/Fade'
import Slide from '#material-ui/core/Slide'
export const DefaultTransition = React.forwardRef((props, ref) => (
<Fade {...props} ref={ref} />
))
export const FullscreenTransition = React.forwardRef((props, ref) => (
<Slide direction='left' {...props} ref={ref} />
))
export const FullscreenExpansion = React.forwardRef((props, ref) => (
<Slide direction='right' {...props} ref={ref} />
))
This was a relatively hard issue to solve on my end, so I'm going to leave this question up just in case someone else runs into a similar issue somewhere down the road.
I have had the same problem with "#material-ui/core/Tooltip" wrapping a new functional component. Even if the component was wrapped in a div inside its own code.
<!-- "Did you accidentally use a plain function component for an element instead?" -->
<Tooltip>
<NewFunctionalComponent />
</Tooltip>
<!-- Wrapped in a new div, devtools won't complain anymore -->
<Tooltip>
<div>
<NewFunctionalComponent />
</div>
</Tooltip>
<!-- No more warnings! -->
Wrapping my component to another div inside material-ui transition componet (like Slide, Fade etc) solves my issue.
Example code:
From
<Slide direction='Right' in = {isSideNavOpen} mountOnEnter unmountOnExit>
<MyComponent />
</Slide>
To
<Slide direction='Right' in = {isSideNavOpen} mountOnEnter unmountOnExit>
<div><MyComponent /></div>
</Slide>`
React.forwardRef fixes the issue for me, too.
To ensure that the React.forwardRef solution works with Typescript, you must implement the following as per the documentation in Material-UI:
const Transition = React.forwardRef<unknown, TransitionProps>(function Transition(props, ref) {
return <Slide ref={ref} {...props} />;
});
See the source code in their demo here.
I was getting the same error
expected an element that cand hold a ref
I solved by adding a <Box></Box> surrounding my children component.
From
<Modal open={open} onClose={handleClose}>
<DetailsCard />
</Modal>
To
<Modal open={open} onClose={handleClose}>
<Box>
<DetailsCard />
</Box>
</Modal>
I had a similar problem and I solved it by wrapping my functional component in a div.
In my case the problem ocurred with a Tooltip component wrapping a custom functional component.
<Tooltip tittle={...}>
<CustomFC />
</Tooltip>
And the solution was
<Tooltip tittle={...}>
<div>
<CustomFC />
</div>
</Tooltip>

How to pass a function as props to a component of Material-UI for React?

I wanted to make an Appbar which will contain a SearchBar. for this I was using Material-UI for React.
I have a file in my component section which loads the Appbar
Here is the appbar.js
function SearchAppBar(props) {
const { classes, placeholder, writeInput } = props;
return (
<div className={classes.root}>
<AppBar position="fixed">
<Toolbar>
<IconButton
className={classes.menuButton}
color="inherit"
aria-label="Open drawer"
>
<MenuIcon />
</IconButton>
<Typography
className={classes.title}
variant="h6"
color="inherit"
noWrap
>
My Weather
</Typography>
<div className={classes.grow} />
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<InputBase
placeholder={placeholder}
classes={{
root: classes.inputRoot,
input: classes.inputInput
}}
onChange={this.writeInput}
/>
</div>
</Toolbar>
</AppBar>
</div>
);
}
SearchAppBar.propTypes = {
classes: PropTypes.object.isRequired,
placeholder: PropTypes.string,
writeInput: PropTypes.func
};
export default withStyles(style_appbar)(SearchAppBar);
After that, I imported this file to another file named searchbar.js.
Why I did that?
Well, so that I can control the input with the component state and the props
Here is the searchcbar.js
import React, { Component } from "react";
import AppBar from "./appbar";
export default class Search extends Component {
constructor(props) {
super(props);
this.state = { term: "" };
this.onInputChange = this.onInputChange.bind(this);
}
onInputChange(event) {
console.log(event.target.value);
this.setState({ term: event.target.value });
}
render() {
return (
<AppBar
placeholder="Search forecast for your favorite cities..."
value={this.state.term}
writeInput={this.onInputChange}
/>
);
}
}
And then I imported this file to my app.js.
Before diving deep, I wanted to see if everything works, so I put a console log but I am not getting any console log.
Rather I am having the error,
Cannot read property 'writeInput' of undefined in appbar.js:46:29
writeInput is the function which I was sending to the Materila-UI's component as a props!
The link of the sandbox of the code is here, you can check the output too.project's sandbox
ps. It maye take some time for the sandbox to boot up!
So, can I not send a function as props to the Material-UI's component? If not what's the alternate way to resolve this issue?
You can send a function to functional component and access it. Don’t use this when calling writeInput. Since you are assigning const { writeInput} = props; you have to call writeInput or if you don’t assign like this then you can call props.writeInput
PFB corrected code
<InputBase
placeholder={placeholder}
classes={{
root: classes.inputRoot,
input: classes.inputInput
}}
onChange={writeInput}
/>

How to show a loading spinner until component loads in React

My question is not as simple as the title. I'm trying to show a loading component for 1 second and then replace that component with the actual data.
Here is my code:
const TopNavigationAuth = () => (
<Container>
<Menu.Item
name="landing"
content="Landing"
as={Link}
to={routes.LANDING}
/>
<Menu.Item name="home" content="Home" as={Link} to={routes.HOME} />
<Menu.Menu position="right">
<Menu.Item
name="account"
content="Account"
as={Link}
to={routes.ACCOUNT}
/>
<UserSection />
<Menu.Item
name="signout"
content={
<Button
content="Sign Out"
onClick={authFunctions.doSignOut}
primary
/>
}
/>
</Menu.Menu>
</Container>
);
So here I have the <UserSection /> component which essentially just holds the user's picture and name (for now). I would like to load that component after 1 or 2 seconds but until then I would like to show a spinner instead.
I'm using semantic ui react for my app and they have a handy spinner which looks like this:
const LoaderExampleInlineCentered = () => <Loader active inline='centered' />
Can I please have some guidance with this?
You can conditionally render one of the two components, Loader Or UserSection.
this.state.profileExist === true ? <UserSection /> : <Loader />
Then initialize profileExist as a False in componentDid mount, then use setTimeout to set it to true
componentDidMount() {
this.setState({profileExist: false})
setTimeout(() => {
this.setState({profileExist: true})
}, 1000);
}

Best way to validate a component passed as a prop

What is the best way to validate proptypes that are going to be stateless components , other than Proptypes.func ?
Let's say I have a NestedDashboardItem component that is going to receive an Icon component as a prop . I don't want to pass it as a child and use it from props.children because I have use cases where that'll be overkill like passing an array of child nested Links and such .
consider :
<DashboardItem
to="/route"
icon={someIconComponent}
text="sometext"
/>
and in the DashboardItem definition I want to validate this icon prop
const DashboardItem = ({ text, icon, to, classes }) => {
const Icon = icon;
return (
<ListItem
button
component={Link}
to={to}
activeClassName="active"
className={classes.root}
>
<ListItemIcon className={cx(classes.dashboardIcon_wrapper)}>
<Icon className={cx(classes.icon)} />
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
);
};
I used PropTypes.element and it complained , so now what I'm asking is what is the best way to validate the icon prop , other than PropTypes.func ?
There is no better way. Use PropTypes.func.

Convert class component (with local state) to stateless functional component & container

I am new to react-redux and currently working on a project where we have a mandate from our software architecture team to write all our components as stateless functional components (introduced in React 0.14) and whenever we need to pass them a piece of the redux state we should use containers.
My questions are:
Is this pattern applicable for every component and how? Even for class based components that have their own "state" (not redux state)? Is it possible for all class components to be rewritten as stateless functional components plus a container?
The reason I am asking is more specific actually:
The team has decided to use reactstrap-tabs to implement tab functionality inside our components. The tabs will live inside a parent component and inside each tab a different child component will be displayed. From the documentation it seems that the parent tabbed component should be implemented as a class based component (handling this.state). Is it possible to rewrite this as a stateless functional component and a container?
Any help would be really appreciated since I am stuck on this. Here is the sample code...
import React from 'react';
import { TabContent, TabPane, Nav, NavItem, NavLink, Card, Button, CardTitle, CardText, Row, Col } from 'reactstrap';
import classnames from 'classnames';
import MyFirstChildComponent from '/components/MyFirstChildComponent';
import MySecondChildContainer from '/containers/MySecondChildContainer';
export default class TabbedParent extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
activeTab: '1'
};
}
toggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
render() {
return (
<div>
<Nav tabs>
<NavItem>
<NavLink
className={classnames({ active: this.state.activeTab === '1' })}
onClick={() => { this.toggle('1'); }}
>
MyFirstTab
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({ active: this.state.activeTab === '2' })}
onClick={() => { this.toggle('2'); }}
>
MySecondTab
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1">
<Row>
<Col sm="12">
<MyFirstChildComponent />
</Col>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<Col sm="6">
<MySecondChildContainer />
</Col>
</Row>
</TabPane>
</TabContent>
</div>
);
}
}
It sounds like your architecture team wants you to keep state centralized in a Redux store, and you are asking if you can use components which maintain their own state. There's obviously a conflict there, but you can achieve what you want with the tabs by keeping the tab state in the store, using containers to update the tabs when they're toggled, and dispatching actions then the tabs are selected.
If you can't make changes to the structure of the store for your UI, then I suppose you could only make design and component choices that would work with the data available to you in the store. I'm not sure how inflexible your architecture team is.
A simple implementation of your example might look like this:
import MySecondChildContainer from '/containers/MySecondChildContainer';
const TabbedParent = ({activeTab, clickHandler}) =>
<div>
<Nav tabs>
<NavItem>
<NavLink
className={classnames({ active: activeTab === '1' })}
onClick={() => clickHandler('1')}
>
MyFirstTab
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({ active: activeTab === '2' })}
onClick={() => clickHandler('2')}
>
MySecondTab
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={activeTab}>
<TabPane tabId="1">
<Row>
<Col sm="12">
<MyFirstChildComponent />
</Col>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<Col sm="6">
<MySecondChildContainer />
</Col>
</Row>
</TabPane>
</TabContent>
</div>
const mapStateToProps = (state) => ({
activeTab: state.activeTab,
});
const mapDispatchToProps = dispatch => ({
clickHandler(id) { dispatch(TabClicked(id)) },
});
export connect(mapStateToProps, mapDispatchToProps)(TabbedParent);

Resources