I'm trying to implement a simple menu with ASP.net core 2.1, Typescript 3.2.1, material-ui 3.8.3 and React 16.7.0. When I run the application it crashes on the line with useState with the error TypeError: react__WEBPACK_IMPORTED_MODULE_1___default.a.useState is not a function.
import React from 'react';
import { Link } from 'react-router-dom';
import { withStyles } from '#material-ui/core/styles'
import IconButton from '#material-ui/core/IconButton'
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
import MenuIcon from '#material-ui/icons/Menu'
function TopbarMenu(props: any) {
const { classes } = props
const [anchorEl, setAnchorEl] = React.useState(null); // Crashes here. Compiled line becomes: var _React$useState = react__WEBPACK_IMPORTED_MODULE_1___default.a.useState(null),
function handleClick(event: any) {
console.log(event.currentTarget)
setAnchorEl(event.currentTarget);
}
function handleClose() {
setAnchorEl(null);
}
return (
<div>
<IconButton onClick={handleClick} className={classes.menuButton} color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Menu id="simple-menu" anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem onClick={handleClick}><Link to={'/orderform'}>Orderform</Link></MenuItem>
<MenuItem onClick={handleClick}><Link to={'/products'}>Products</Link></MenuItem>
<MenuItem onClick={handleClick}><Link to={'/customers'}>Customers</Link></MenuItem>
<MenuItem onClick={handleClick}><Link to={'/licenses'}>Expiring Licenses</Link></MenuItem>
</Menu>
</div>
);
}
const styles = {
menuButton: {
marginLeft: -12,
marginRight: 20,
},
}
export default withStyles(styles)(TopbarMenu)
What am I missing here?
I'm following material-ui's documentation found here: https://material-ui.com/demos/menus/#simple-menu
Edit
Side notes:
Always use Hooks at the top level of your React function.
See rules of hooks
//Not that bad
const { classes } = props
const [anchorEl, setAnchorEl] = React.useState(null);
//Preferably (in my opinion)
const [anchorEl, setAnchorEl] = React.useState(null);
const { classes } = props
Answer
React Hooks are available from React 16.7.0-alpha, reactconf2018. Now currently available in React v16.8.0-alpha.0.
Try this
const [anchorEl, setAnchorEl] = React.useState<Element|null>(null);
Related
I'm trying to override the existing styling for a button in MUI. This is my first time using MUI, and I installed my project with the default emotion as a styling engine. I tried to use the css() method as specified here : The css prop however it doesn't seem to be working, even on the example case provided.
I would have tried to add a custom css file to handle :select, but I'm using state in my component to change from selected to not selected.
import * as React from "react";
import Avatar from "#mui/material/Avatar";
import ListItemText from "#mui/material/ListItemText";
import ListItemButton from "#mui/material/ListItemButton";
import { useState } from "react";
import { css } from "#emotion/react";
const ProfileInfo = ({ userCredentials, userPicture }) => {
const [selected, setSelected] = useState(false);
return (
<ListItemButton
selected={selected}
onClick={() => setSelected((prev) => !prev)}
css={css`
::selection {
color: #2e8b57;
}
:focus {
color:#2e8b57;
}
:active {
color:#2e8b57
}
`}
>
<Avatar
alt={userCredentials}
src={userPicture}
sx={{ width: 24, height: 24 }}
/>
<ListItemText primary={userCredentials} sx={{ marginLeft: 3 }} />
</ListItemButton>
);
};
export default ProfileInfo;
Working examples on Code Sandbox
I have made a similar example using Code Sandbox, which you can find here https://codesandbox.io/s/amazing-gagarin-gvl66n. I show a working implementation using:
The css prop
The sx prop
Using the css prop
There's two things you need to do to make your code work using Emotion's css prop.
Add the following lines, which is also in the example, at the top of the file where you are using the css prop. This will tell your app how to handle the css prop.
/* eslint-disable react/react-in-jsx-scope -- Unaware of jsxImportSource */
/** #jsxImportSource #emotion/react */
Target the classes Material UI provides for the List Item Button component. For example, if I want to style the List Item Button when selected is true, I would target the .Mui-selected class.
I am assuming you wanted to style the background color of the List Item Button rather than the color. Styling the color changes the font color. However, if you wanted to change the font color, you can just change each instance of background-color to color.
Putting it altogether:
/* eslint-disable react/react-in-jsx-scope -- Unaware of jsxImportSource */
/** #jsxImportSource #emotion/react */
import * as React from "react";
import Avatar from "#mui/material/Avatar";
import ListItemText from "#mui/material/ListItemText";
import ListItemButton from "#mui/material/ListItemButton";
import { useState } from "react";
import { css } from "#emotion/react";
const ProfileInfo = ({ userCredentials, userPicture }) => {
const [selected, setSelected] = useState(false);
return (
<ListItemButton
selected={selected}
onClick={() => setSelected((prev) => !prev)}
css={css`
&.Mui-selected {
background-color: #2e8b57;
}
&.Mui-focusVisible {
background-color: #2e8b57;
}
:hover {
background-color: #2e8b57;
}
`}
>
<Avatar
alt={userCredentials}
src={userPicture}
sx={{ width: 24, height: 24 }}
/>
<ListItemText primary={userCredentials} sx={{ marginLeft: 3 }} />
</ListItemButton>
);
};
export default ProfileInfo;
Alternative: Using the SX prop
The sx prop can be used to override styles with all Material UI components. You are already using this for the Avatar and ListItemText components in your example.
Using the sx prop, the equivalent code would be:
import * as React from "react";
import Avatar from "#mui/material/Avatar";
import ListItemText from "#mui/material/ListItemText";
import ListItemButton from "#mui/material/ListItemButton";
import { useState } from "react";
const ProfileInfo = ({ userCredentials, userPicture }) => {
const [selected, setSelected] = useState(false);
return (
<ListItemButton
selected={selected}
onClick={() => setSelected((prev) => !prev)}
sx={{
"&.Mui-selected": {
backgroundColor: "#2e8b57"
},
"&.Mui-focusVisible": {
backgroundColor: "#2e8b57"
},
":hover": {
backgroundColor: "#2e8b57"
}
}}
>
<Avatar
alt={userCredentials}
src={userPicture}
sx={{ width: 24, height: 24 }}
/>
<ListItemText primary={userCredentials} sx={{ marginLeft: 3 }} />
</ListItemButton>
);
};
export default ProfileInfo;
It looks like MUI components doesn't use standard CSS rules but instead has a defined set of CSS rules you can modify https://mui.com/material-ui/api/list-item/#props.
I´m looking for way to load static texts into storybook via next-translate.
My code looks like this, but it´s loading my locale files, but not writing them properly.
This is storybook preview.js:
import '../src/styles/global/global.scss';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/core/styles';
import theme from '../src/utils/theme';
import I18nProvider from 'next-translate/I18nProvider';
import commonCS from '../locales/cs/common.json';
export const decorators = [(Story) => themeDecorator(Story)];
const themeDecorator = (Story) => {
console.log(commonCS.homepage_title);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<I18nProvider lang={'cs-CS'} namespaces={{ commonCS }}>
<Story />
</I18nProvider>
</ThemeProvider>
);
};
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: { expanded: true },
};
And this is my storybook storie:
import React from 'react';
import HeaderContact from './HeaderContact';
import I18nProvider from 'next-translate/I18nProvider';
import useTranslation from 'next-translate/useTranslation';
import commonCS from '../../../locales/cs/common.json';
export default {
title: 'HeaderContact',
component: HeaderContact,
};
export const Basic = () => {
const { t } = useTranslation('common');
return (
<HeaderContact
link="mailto:info#numisdeal.com"
text={t('homepage_title')}
/>
);
};
My local file common.json:
{
"homepage_title": "Blog in Next.js",
"homepage_description": "This example shows a multilingual blog built in Next.js with next-translate"
}
And my translate config i18n.json
{
"locales": ["cs", "en", "de"],
"defaultLocale": "cs",
"pages": {
"*": ["common"]
}
}
I would be very glad for some help.
Thanks!
Roman
Here is the solution.
preview.js
import '../src/styles/global/global.scss';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/core/styles';
import theme from '../src/utils/theme';
import I18nProvider from 'next-translate/I18nProvider';
import commonCS from '../locales/cs/common.json';
export const decorators = [(Story) => themeDecorator(Story)];
const themeDecorator = (Story) => {
console.log(commonCS.homepage_title);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<I18nProvider lang={'cs'} namespaces={{ common: commonCS }}>
<Story />
</I18nProvider>
</ThemeProvider>
);
};
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: { expanded: true },
};
Storie:
import React from 'react';
import HeaderContact from './HeaderContact';
export default {
title: 'HeaderContact',
component: HeaderContact,
};
export const Basic = () => {
return <HeaderContact link="mailto:info#numisdeal.com" />;
};
Component:
import React from 'react';
import AlternateEmailIcon from '#material-ui/icons/AlternateEmail';
import useTranslation from 'next-translate/useTranslation';
import styles from './HeaderContact.module.scss';
export interface IHeaderContact {
link: string;
text?: string;
}
export default function HeaderContact(props: IHeaderContact) {
const { link, text } = props;
const { t } = useTranslation('common');
const preklad = t('homepage_title');
return (
<a href={link} className={styles.headerLink}>
<AlternateEmailIcon fontSize="small" />
<span>
{/* {text} */}
{preklad}
</span>
</a>
);
}
I am using Next.js with Material-UI as the framework.
I have Layout component that wraps the contents with Material-UI <Container>.
I would like to override the style of Layout that limits the width of background, so that the background would extend to the full screen.
components/Layout.js
import { Container } from '#material-ui/core';
export default function Layout({ children }) {
return <Container>{children}</Container>;
}
pages/_app.js
import Layout from '../components/Layout';
...
<Layout>
<Component {...pageProps} />
</Layout>
...
pages/index.js
export default function App() {
return (
<div style={{ backgroundColor: "yellow" }}>
Home Page
</div>
)
}
Using Layout component comes in handy in most cases but sometimes I do want to override the certain styles of Layout from the child component.
In this case, how do I override the style of Layout component that puts the limit on maxWidth?
I tried to add {width: '100vw'} inside the style of pages/index.js, but did not work.
Any help would be appreciated.
Link to the SandBox
Using React Context is how I've solved this issue.
context/ContainerContext.js
import React from 'react';
const ContainerContext = React.createContext();
export default ContainerContext;
components/Layout.js
import React, { useState } from 'react';
import { Container } from '#material-ui/core';
import ContainerContext from '../context/ContainerContext';
export default function Layout({ children }) {
const [hasContainer, setHasContainer] = useState(true);
const Wrapper = hasContainer ? Container : React.Fragment;
return (
<ContainerContext.Provider value={{ hasContainer, setHasContainer }}>
<Wrapper>{children}</Wrapper>
</ContainerContext.Provider>
);
}
pages/index.js
import { useContext, useLayoutEffect } from 'react';
import ContainerContext from '../context/ContainerContext';
export default function App() {
const { setHasContainer } = useContext(ContainerContext);
// Where the magic happens!
useLayoutEffect(() => {
setHasContainer(false);
}, []);
return (
<div style={{ backgroundColor: "yellow" }}>
Home Page
</div>
);
}
I'm writing some simple reusable component for our React(with MaterialUI) application.
The problem is, that i want to allow different styles of this same reusable component, to be customized via props, by the consuming component.
This is some of the code:
import { withStyles } from '#material-ui/core';
const styles = theme => ({
image: {
maxHeight: '200px'
}
});
render() {
const classes = this.props.classes
return (
<div>
...
<img className={classes.image} src={this.state.filePreviewSrc} alt="" />
...
</div>
);
}
Let's say, i want to allow the programmer to customize the appearance of classes.image. Can the hard-coded image class be overwritten somehow?
Is using withStyles api is even the correct approach, for creating components whose appearance can be customized by the consuming component/programmer?
There are three main approaches available for how to support customization of styles:
Leverage props within your styles
Leverage props to determine whether or not certain classes should be applied
Do customization via withStyles
For option 3, the styles of the wrapping component will be merged with the original, but the CSS classes of the wrapping component will occur later in the <head> and will win over the original.
Below is an example showing all three approaches:
ReusableComponent.js
import React from "react";
import { withStyles } from "#material-ui/core/styles";
const styles = {
root: props => ({
backgroundColor: props.rootBackgroundColor
? props.rootBackgroundColor
: "green"
}),
inner: props => ({
backgroundColor: props.innerBackgroundColor
? props.innerBackgroundColor
: "red"
})
};
const ReusableComponent = ({ classes, children, suppressInnerDiv = false }) => {
return (
<div className={classes.root}>
Outer div
{suppressInnerDiv && <div>{children}</div>}
{!suppressInnerDiv && (
<div className={classes.inner}>
Inner div
<div>{children}</div>
</div>
)}
</div>
);
};
export default withStyles(styles)(ReusableComponent);
index.js
import React from "react";
import ReactDOM from "react-dom";
import { withStyles } from "#material-ui/core/styles";
import ReusableComponent from "./ReusableComponent";
const styles1 = theme => ({
root: {
backgroundColor: "lightblue",
margin: theme.spacing(2)
},
inner: {
minHeight: 100,
backgroundColor: "yellow"
}
});
const Customization1 = withStyles(styles1)(ReusableComponent);
const styles2 = {
inner: {
backgroundColor: "purple",
color: "white"
}
};
const Customization2 = withStyles(styles2)(ReusableComponent);
function App() {
return (
<div className="App">
<ReusableComponent>Not customized</ReusableComponent>
<Customization1>Customization 1 via withStyles</Customization1>
<Customization2>Customization 2 via withStyles</Customization2>
<ReusableComponent rootBackgroundColor="lightgrey" suppressInnerDiv>
Customization via props
</ReusableComponent>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I'm experiencing an issue using Meteor's createContainer with React Router v4. I've used it successfully with v3, but when I try to set up routing with v4 it loads the Main route and then won't render anything else. If I change App to a functional stateless component and bypass the data layer, the routing works fine, so I know it's something in there.
App.jsx:
import React from 'react'
import {Navigation} from './Navigation'
import {Grid, MenuItem} from 'react-bootstrap'
import {LinkContainer} from 'react-router-bootstrap'
import { createContainer } from 'meteor/react-meteor-data'
import {Products} from '../api/products'
import {Main} from './Main'
class App extends React.Component {
render () {
const {ready, products} = this.props;
if (!ready) return <h1>Loading...</h1>
const vendorsList = [...new Set(products.map(item => item.vendor).filter(i => !!i))]
const vendors = vendorsList.map((item, index) => <LinkContainer key={index} to={`/vendors/${item}`}><MenuItem eventKey={(index+1) / 10 + 4}>{item}</MenuItem></LinkContainer>)
return (
<div>
<Navigation vendors={vendors} />
<Grid>
<Main products={products} />
</Grid>
</div>
)
}
}
export default createContainer(({params}) => {
const handle = Meteor.subscribe('products');
return {
ready: handle.ready(),
products: Products.find({}, {sort: {name: 1}}).fetch()
};
}, App);
Navigation.jsx:
import React from 'react'
import {NavLink} from 'react-router-dom'
import {Navbar, Nav, NavItem, NavDropdown, MenuItem} from 'react-bootstrap'
import {LinkContainer} from 'react-router-bootstrap'
export const Navigation = ({vendors}) => (
<Navbar>
<Navbar.Header>
<LinkContainer to='/'><Navbar.Brand>IM 0.1</Navbar.Brand></LinkContainer>
</Navbar.Header>
<Nav>
<NavDropdown eventKey={1} title='Products' id='basic-nav-dropdown'>
<LinkContainer to='/products'><MenuItem eventKey={1.1}>Products List</MenuItem></LinkContainer>
<LinkContainer to='/products/new'><MenuItem eventKey={1.2}>Enter New Product</MenuItem></LinkContainer>
</NavDropdown>
<NavItem eventKey={2}>Inventory</NavItem>
<NavDropdown eventKey={3} title='Invoices' id='basic-nav-dropdown'>
<MenuItem eventKey={3.1}>Enter New Invoice</MenuItem>
<MenuItem divider />
<MenuItem eventKey={3.2}>Manage Invoices...</MenuItem>
</NavDropdown>
<NavDropdown eventKey={4} title='Vendors' id='basic-nav-dropdown'>
{vendors}
<MenuItem divider />
<MenuItem eventKey={(vendors.length + 1)/10 + 4}>Vendors List...</MenuItem>
</NavDropdown>
</Nav>
</Navbar>
)
main.js:
import React from 'react'
import { Meteor } from 'meteor/meteor'
import { render } from 'react-dom'
import {BrowserRouter} from 'react-router-dom'
import App from '../imports/ui/App'
Meteor.startup(() => {
render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('render-target'));
});
I know I'm missing something that's probably super basic, and it's driving me nuts. Thanks.
Try wrapping App.jsx with withRouter
Also note that Meteor createContainer function has been replaced by withTracker
import React from 'react'
import { withTracker } from 'meteor/react-meteor-data';
import { withRouter } from 'react-router-dom';
...
class App extends React.Component {
...
}
export default withRouter(withTracker(({params}) => {
...
})(App));