When using functions as ponents you have the ability to use the useMediaQuery
hook from material-ui. However it no where shows you how to use this hook inside a class.
So I did some research and found out you can use it in a class by doing this:
import React from 'react';
import useMediaQuery from '@material-ui/core/useMediaQuery';
const withMediaQuery = (...args) => Component => props => {
const mediaQuery = useMediaQuery(...args);
return <Component mediaQuery={mediaQuery} {...props} />;
};
export default withMediaQuery;
However when adding it to the class like this:
export default withStyles(styles)(withMediaQuery(Main));
It gives me this error:
index.js:1 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
I really need to use the media query because some variables are dependent on them. This is the render method of the class which I would like to use the media query for.
render() {
const { classes, children } = this.props;
const isDesktop = useMediaQuery(theme => theme.breakpoints.up('lg'), {
defaultMatches: true,
});
const shouldOpenSidebar = isDesktop ? true : this.state.openSidebar;
return (
<div
className={cc({
[classes.root]: true,
[classes.shiftContent]: isDesktop,
})}>
<Topbar
onSidebarOpen={this.handleSidebarOpen}
/>
<Sidebar
onClose={this.handleSidebarClose}
open={shouldOpenSidebar}
variant={isDesktop ? 'persistent' : 'temporary'}
/>
<main className={classes.content}>
{children}
</main>
</div>
);
}
I've already tried wrapping the ponent, but then I wouldn't be able to use the variables
When using functions as ponents you have the ability to use the useMediaQuery
hook from material-ui. However it no where shows you how to use this hook inside a class.
So I did some research and found out you can use it in a class by doing this:
import React from 'react';
import useMediaQuery from '@material-ui/core/useMediaQuery';
const withMediaQuery = (...args) => Component => props => {
const mediaQuery = useMediaQuery(...args);
return <Component mediaQuery={mediaQuery} {...props} />;
};
export default withMediaQuery;
However when adding it to the class like this:
export default withStyles(styles)(withMediaQuery(Main));
It gives me this error:
index.js:1 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
I really need to use the media query because some variables are dependent on them. This is the render method of the class which I would like to use the media query for.
render() {
const { classes, children } = this.props;
const isDesktop = useMediaQuery(theme => theme.breakpoints.up('lg'), {
defaultMatches: true,
});
const shouldOpenSidebar = isDesktop ? true : this.state.openSidebar;
return (
<div
className={cc({
[classes.root]: true,
[classes.shiftContent]: isDesktop,
})}>
<Topbar
onSidebarOpen={this.handleSidebarOpen}
/>
<Sidebar
onClose={this.handleSidebarClose}
open={shouldOpenSidebar}
variant={isDesktop ? 'persistent' : 'temporary'}
/>
<main className={classes.content}>
{children}
</main>
</div>
);
}
I've already tried wrapping the ponent, but then I wouldn't be able to use the variables
Share Improve this question asked Jan 9, 2020 at 21:15 Ezrab_Ezrab_ 9837 gold badges21 silver badges49 bronze badges 6- Is there something special about your ponent that makes it difficult to convert from a class to a function ponent? – Ryan Cogswell Commented Jan 9, 2020 at 21:33
- @RyanCogswell I just prefer to keep my code consistent since all my other ponents are also classes. – Ezrab_ Commented Jan 9, 2020 at 21:34
- 1 You will find that more and more library APIs will be exposed only as hooks. It seems like you're just creating more work for yourself by trying to keep consistent in that fashion rather than gradually moving towards function ponents. – Ryan Cogswell Commented Jan 9, 2020 at 21:37
- 1 Hmm I'm not sure about that since I believe that classes will be the future of js. So I'd rather start using them so that I could easily add more plex things to classes such as abstraction and stuff. Thanks for your advice though! – Ezrab_ Commented Jan 9, 2020 at 21:41
- 1 Though they will continue to be supported, classes definitely are not the future of React ponents: reactjs/docs/…. – Ryan Cogswell Commented Jan 9, 2020 at 21:53
2 Answers
Reset to default 8You're not supplying the args
needed for useMediaQuery
, so Main
is passed as the args
, and a function that expects the ponent is returned. When React tried to render (call the function), the return value is another function, which is not value as a react child.
Call the function - withMediaQuery
and pass it the media queries, and then pass Main
to the returned function.
Example:
export default withStyles(styles)(withMediaQuery('(min-width:600px)')(Main));
Instead of limiting yourself to only one media query a better withMediaQuery HOC could be
import React from 'react'
import useMediaQuery from '@material-ui/core/useMediaQuery'
export const withMediaQuery = (queries = []) => Component => props => {
const mediaProps = {}
queries.forEach(q => {
mediaProps[q[0]] = useMediaQuery(q[1])
})
return <Component {...mediaProps} {...props} />
}
This would allow you to pass in multiple queries as an array of arrays. Each entry would be a prop name and then the query.
export default withStyles(styles)(withMediaQuery([
['isDesktop', theme => theme.breakpoints.up('lg'), {
defaultMatches: true
}]
]))
In your ponent you could then request the prop names directly in render
render() {
const { classes, children, IsDesktop = false } = this.props;
const shouldOpenSidebar = IsDesktop ? true : this.state.openSidebar;
return (
<div
className={cc({
[classes.root]: true,
[classes.shiftContent]: isDesktop,
})}>
<Topbar
onSidebarOpen={this.handleSidebarOpen}
/>
<Sidebar
onClose={this.handleSidebarClose}
open={shouldOpenSidebar}
variant={isDesktop ? 'persistent' : 'temporary'}
/>
<main className={classes.content}>
{children}
</main>
</div>
);
}