I want to use functions like useMediaQuery in class based components.
How can I make achieve this ?
import React from 'react';
import clsx from 'clsx';
import { withStyles ,withTheme ,withMediaQuery } from '#material-ui/styles';
import { useMediaQuery } from '#material-ui/core'
class Main extends React.Component{
render(){
const { children ,classes,theme} = this.props;
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'),{
defaultMatches :true
});
return (
<div className ={clsx({
[classes.root] : true,
[classes.shiftContent] : isDesktop
})}>
<main>
{children}
</main>
</div>
);
}
}
You can wrap your useMediaQuery and the rest of the logic in another component:
const WithClasses = ({ children, theme, classes }) => {
const isDesktop = useMediaQuery(theme.breakpoints.up("lg"), {
defaultMatches: true
});
return (
<div
className={clsx({
[classes.root]: true,
[classes.shiftContent]: isDesktop
})}
>
{children}
</div>
);
};
Then use it in your layout like this:
class Main extends React.Component {
render() {
const { children, classes, theme } = this.props;
return (
<WithClasses theme={theme} classes={classes}>
<main>{children}</main>
</WithClasses>
);
}
}
Related
In my React project I am using a library of components:
<customcomponent />
However that library components does not support passing className. Therefore I can not use this method to apply style to the component like this:
<customcomponent className='mystyles' />
How to apply style to the component in such circumstance?
You would have to create a wrapper component that wraps your component with a div that accepts a className.
const DoesntSupportClassNameWrapper = (props) => {
const { className, style, ...rest } = props;
return (
<div className={className} style={style}>
<DoesntSupportClassName {...rest} />
</div>
);
};
Full example
Edit # CodeSandbox
App.jsx
import React from "react";
import PropTypes from "prop-types";
import { DoesntSupportClassName, SupportsClassName } from "./components";
import "./styles.css";
const styles = {
heading: {
color: "red"
}
};
const DoesntSupportClassNameWrapper = (props) => {
const { className, style, ...rest } = props;
return (
<div className={className} style={style}>
<DoesntSupportClassName {...rest} />
</div>
);
};
DoesntSupportClassNameWrapper.propTypes = {
className: PropTypes.string,
style: PropTypes.object
};
const App = () => {
return (
<div className="App">
<SupportsClassName text="Heading 1" className="heading" />
<DoesntSupportClassName
text="Heading 2"
className="heading"
style={styles.heading}
/>
<DoesntSupportClassNameWrapper text="Heading 3" className="heading" />
<DoesntSupportClassNameWrapper text="Heading 4" style={styles.heading} />
</div>
);
};
export default App;
DoesntSupportClassName.jsx
import React from "react";
import PropTypes from "prop-types";
const DoesntSupportClassName = (props) => {
const { text } = props;
return <h1>{text}</h1>;
};
DoesntSupportClassName.propTypes = {
text: PropTypes.string
};
export default DoesntSupportClassName;
SupportsClassName.jsx
import React from "react";
import PropTypes from "prop-types";
const SupportsClassName = (props) => {
const { text, className } = props;
return <h1 className={className}>{text}</h1>;
};
SupportsClassName.propTypes = {
className: PropTypes.string,
text: PropTypes.string
};
export default SupportsClassName;
I am working on a site that has a piece a global state stored in a file using zustand. I need to be able to set that state in a class component. I am able to set the state in a functional component using hooks but I'm wondering if there is a way to use zustand with class components.
I've created a sandbox for this issue if that's helpful:
https://codesandbox.io/s/crazy-darkness-0ttzd
here I'm setting state in a functional component:
function MyFunction() {
const { setPink } = useStore();
return (
<div>
<button onClick={setPink}>Set State Function</button>
</div>
);
}
my state is stored here:
export const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink }))
}));
how can I set state here in a class componet?:
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={
{
/* setPink */
}
}
>
Set State Class
</button>
</div>
);
}
}
A class component's closest analog to a hook is the higher order component (HOC) pattern. Let's translate the hook useStore into the HOC withStore.
const withStore = BaseComponent => props => {
const store = useStore();
return <BaseComponent {...props} store={store} />;
};
We can access the store as a prop in any class component wrapped in withStore.
class BaseMyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { setPink } = this.props.store;
return (
<div>
<button onClick={setPink}>
Set State Class
</button>
</div>
);
}
}
const MyClass = withStore(BaseMyClass);
Seems that it uses hooks, so in class you can work with the instance:
import { useStore } from "./store";
class MyClass extends Component {
render() {
return (
<div>
<button
onClick={() => {
useStore.setState({ isPink: true });
}}
>
Set State Class
</button>
</div>
);
}
}
Create a React Context provider that both functional and class-based components can consume. Move the useStore hook/state to the context Provider.
store.js
import { createContext } from "react";
import create from "zustand";
export const ZustandContext = createContext({
isPink: false,
setPink: () => {}
});
export const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink }))
}));
export const ZustandProvider = ({ children }) => {
const { isPink, setPink } = useStore();
return (
<ZustandContext.Provider
value={{
isPink,
setPink
}}
>
{children}
</ZustandContext.Provider>
);
};
index.js
Wrap your application with the ZustandProvider component.
...
import { ZustandProvider } from "./store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<ZustandProvider>
<App />
</ZustandProvider>
</StrictMode>,
rootElement
);
Consume the ZustandContext context in both components
MyFunction.js
import React, { useContext } from "react";
import { ZustandContext } from './store';
function MyFunction() {
const { setPink } = useContext(ZustandContext);
return (
<div>
<button onClick={setPink}>Set State Function</button>
</div>
);
}
MyClass.js
import React, { Component } from "react";
import { ZustandContext } from './store';
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={this.context.setPink}
>
Set State Class
</button>
</div>
);
}
}
MyClass.contextType = ZustandContext;
Swap in the new ZustandContext in App instead of using the useStore hook directly.
import { useContext} from 'react';
import "./styles.css";
import MyClass from "./MyClass";
import MyFunction from "./MyFunction";
import { ZustandContext } from './store';
export default function App() {
const { isPink } = useContext(ZustandContext);
return (
<div
className="App"
style={{
backgroundColor: isPink ? "pink" : "teal"
}}
>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<MyClass />
<MyFunction />
</div>
);
}
If you aren't able to set any specific context on the MyClass component you can use the ZustandContext.Consumer to provide the setPink callback as a prop.
<ZustandContext.Consumer>
{({ setPink }) => <MyClass setPink={setPink} />}
</ZustandContext.Consumer>
MyClass
<button onClick={this.props.setPink}>Set State Class</button>
This worked out pretty well for me.
:
import React, { Component } from "react";
import { useStore } from "./store";
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={
useStore.getState().setPink() // <-- Changed code
}
>
Set State Class
</button>
</div>
);
}
}
export default MyClass;
I like to create a high order component similar to redux connect:
function connectZustand(useStore, selector) {
return (Component) =>
React.forwardRef((props, ref) => <Component ref={ref} {...props} {...useStore(selector, shallow)} />);
}
eg:
import React, { Component } from 'react';
import create from 'zustand';
import shallow from 'zustand/shallow';
function connectZustand(useStore, selector) {
return (Component) =>
React.forwardRef((props, ref) => <Component ref={ref} {...props} {...useStore(selector, shallow)} />);
}
const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink })),
}));
class MyClass extends Component {
render() {
const { setPink } = this.props;
return (
<div>
<button onClick={() => setPink()}>Set State Class</button>
</div>
);
}
}
const MyClassWithZustand = connectZustand(useStore, (state) => ({ setPink: state.setPink }))(MyClass);
export default function Test() {
const isPink = useStore((state) => state.isPink);
return (
<>
<MyClassWithZustand />
{isPink ? 'Is Pink' : 'Is Not Pink'}
</>
);
}
I was looking for the answer why react component with Context Provider doesn't re-render but i couldn't find answer proper for me to understand why.
Moreover i want to mention Im using GatsbyJS.
Here's App.context.js:
const defaultValue = {
menu: false,
handleMenu: () => { },
}
const AppContext = createContext(defaultValue);
export default AppContext;
export { defaultValue };
Next, down below there's Provider element App.provider.js:
import AppContext, { defaultValue } from './App.context';
class AppProvider extends Component {
constructor(props) {
super(props);
this.state = defaultValue
}
handleMenu = () => {
if (this.state.menu) {
this.setState({
menu: false
})
} else {
this.setState({
menu: true
})
}
}
render() {
return (
<AppContext.Provider value={{
menu: this.state.menu,
handleMenu: this.handleMenu,
}}>
{this.props.children}
</AppContext.Provider>
);
}
}
export default AppProvider;
Then, I'm using this provider at the beginning of elements tree:
//Components
import Header from '../components/header';
import Footer from '../components/footer';
import MainWrap from '../components/mainWrap';
//Context
import AppProvider from '../context/App.provider';
const Layout = ({ children }) => {
return (
<AppProvider>
<MainWrap>
<Header />
{children}
<Footer />
</MainWrap>
</AppProvider>
);
}
export default Layout;
Here's MainWrap component:
//Styles
import wrapStyles from '../styles/wrapper.module.scss';
//Context
import AppContext from '../context/App.context';
const MainWrap = ({children}) => {
const {menu} = useContext(AppContext);
return (
<div className={menu?wrapStyles.wrap:wrapStyles.wrapActive}>{children}</div>
);
}
export default MainWrap;
When context value change, child components like MainPage re-render properly, but why component with Provider does not, so i can't instead of using next wrap component (MainPage) just put a div in component with Provider:
//Components
import Header from '../components/header';
import Footer from '../components/footer';
//Styles
import wrapStyles from '../styles/wrapper.module.scss';
//Context
import AppProvider from '../context/App.provider';
import AppContext from '../context/App.context';
const Layout = ({ children }) => {
const {menu} = useContext(AppContext);
return (
<AppProvider>
<div className={menu?wrapStyles.wrap:wrapStyles.wrapActive}>
<Header />
{children}
<Footer />
</div>
</AppProvider>
);
}
export default Layout;
I hope it will be understandable.
I am trying to learn React Context and got stuck. Need help.
App.js
import React from 'react';
import Header from './components/Header';
export const MyContext = React.createContext("Default");
class App extends React.Component {
render() {
return (
<MyContext.Provider value="dark">
<Header />
</MyContext.Provider>
);
}
}
export default App;
Header/index.js
import React, { Component } from 'react'
import { MyContext } from "./../../App";
class Header extends Component {
//static contextType = MyContext;
render() {
return (
<div>
{this.context}
</div>
)
}
}
Header.contextType = MyContext;
export default Header;
Got an error MyContext is not defined.
It works when i move Header class to App.js
What am i doing wrong? Tnx for your help
There are two ways to use context either use:
1. By using context consumer :
<MyContext.Consumer>
{
contextValue => {
return <div>
{value}
</div>
}
}
<MyContext.Consumer>
2. By assigning context to a object:
static contextType = MyContext;
render(){
const {value1,value2.......} = this.context
}
For more information about Context visit the React official page.
https://reactjs.org/docs/context.html
The provider only holds the the value for you(a bit like a store). It is the consumer that makes it available to your components.
Headerjs should look like this
// Header.js
import React, { Component } from 'react'
import { MyContext } from "./../../App";
class Header extends Component {
//static contextType = MyContext;
render() {
return (
<MyContext.Consumer>
{ value => {
return <div>
{value}
</div>
}}
<MyContext.Consumer>
)
}
}
// Header.contextType = MyContext; not needed for react v16+
export default Header;
To get more power out of Context i will suggest combining with Higher Order Components. for example if what you want is a theming system
you can do this.
import React from "react";
const themes = {
dark: {
background: "#333"
},
light: {
background: "#f5f5f9"
}
};
const { Provider, Consumer } = React.createContext(themes);
export const ThemeProvider = ({ children }) => {
return <Provider value={themes}>{children}</Provider>
};
export const withTheme = theme => {
return Component => props => <Consumer>
{themes => {
return <Component {...props} style={{ ...themes[theme]}} />;
}}
</Consumer>
};
in app.js
import Header from "./Header";
import { ThemeProvider } from './Theme'
class App extends React.Component {
render() {
return (
<ThemeProvider>
<Header />
</ThemeProvider>
);
}
}
and lastly Header.js
import React, { Component } from "react";
import { withTheme } from "./Theme";
class Header extends Component {
//static contextType = MyContext;
render() {
return <h1 style={{ ...this.props.style }}>Header</h1>;
}
}
export default withTheme("dark")(Header);
You can read MY article on using context for auth for more
I'm trying to add simple React Context to my app. I create Context in "./components/DataProvider.js" that looks like this:
import React, { Component } from 'react'
const DataContext = React.createContext()
class DataProvider extends Component {
state = {
isAddButtonClicked: false
}
changeAddButtonState = () => {
if( this.state.isAddButtonClicked ) {
this.setState({
isAddButtonClicked: false
})
} else {
this.setState({
isAddButtonClicked: true
})
}
}
render() {
return(
<DataContext.Provider
value={{
isAddButtonClicked: this.state.isAddButtonClicked,
changeAddButtonState: () => {
if( this.state.isAddButtonClicked ) {
this.setState({
isAddButtonClicked: false
})
} else {
this.setState({
isAddButtonClicked: true
})
}
}
}}
>
{this.props.children}
</DataContext.Provider>
)
}
}
const DataConsumer = DataContext.Consumer
export default DataProvider
export { DataConsumer }
Which then I added to "./pages/_app.js"
import App, { Container } from 'next/app'
import DataProvider from '../components/DataProvider'
class MyApp extends App {
render () {
const { Component, pageProps } = this.props
return (
<Container>
<DataProvider>
<Component {...pageProps} />
</DataProvider>
</Container>
)
}
}
export default MyApp
And consume it in "./components/AddPostButton.js".
import React, {Component} from 'react'
import { DataConsumer } from './DataProvider'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faPlus } from '#fortawesome/free-solid-svg-icons'
class AddPostButton extends Component {
render() {
return (
<div>
<DataConsumer>
{({ changeAddButtonState }) => (
<a onClick={changeAddButtonState}>
<FontAwesomeIcon icon={faPlus} color='#fff' />
</a>
)}
</DataConsumer>
</div>
)
}
}
export default AddPostButton
But I get this error "Cannot read property 'changeAddButtonState' of undefined". I'm using React 16.7 and NextJS 7.0.2. Don't know what is wrong.
The second question is should I use one Context for everything or just use them as Model in MVC pattern?
I fixed it by moving changeAddButtonState to Context Component state so my DataProvider.js now looks like this
import React, { Component } from 'react'
const DataContext = React.createContext()
class DataProvider extends Component {
state = {
isAddButtonClicked: false,
changeAddButtonState: () => {
if (this.state.isAddButtonClicked) {
this.setState({
isAddButtonClicked: false
})
} else {
this.setState({
isAddButtonClicked: true
})
}
}
}
render() {
return(
<DataContext.Provider
value={this.state}
>
{this.props.children}
</DataContext.Provider>
)
}
}
const DataConsumer = DataContext.Consumer
export default DataProvider
export { DataConsumer }
And then in AddButton component I changed code to look like this
import React, {Component} from 'react'
import { DataConsumer } from './DataProvider'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faPlus } from '#fortawesome/free-solid-svg-icons'
class AddPostButton extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<DataConsumer>
{(context) => (
<a onClick={context.changeAddButtonState}>
<FontAwesomeIcon icon={faPlus} color='#fff' />
</a>
)}
</DataConsumer>
</div>
)
}
}
export default AddPostButton