whoMe
whoMe

Reputation: 247

How to set zustand state in a class component

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>
    );
  }
}

Upvotes: 12

Views: 12800

Answers (5)

Daniel P&#233;rez
Daniel P&#233;rez

Reputation: 1992

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'}
        </>
    );
}

Upvotes: 1

Rohan Chidurala
Rohan Chidurala

Reputation: 107

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;

Upvotes: 4

Jemi Salo
Jemi Salo

Reputation: 3751

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);

Upvotes: 17

Drew Reese
Drew Reese

Reputation: 203348

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>
  );
}

Edit how-to-set-zustand-state-in-a-class-component

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>

Upvotes: 3

Dennis Vash
Dennis Vash

Reputation: 53964

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>
    );
  }
}

Edit gracious-frost-9vdu3

Upvotes: 4

Related Questions