简体   繁体   中英

Using Stateful React classes in typescipt

I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.

Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;

export default withStyles(useStyles)(HeaderBar)

Which exports the class separately with the Styles( withStyles(useStyles) useStyles as the defined styles) And the class( HeaderBar ). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!

Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7 And where i try to call it: https://pastebin.com/PVxhKFHJ

My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.

Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).

What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.

You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.

import Button, {ButtonProps as MaterialButtonProps} from "@material-ui/core/Button";

type ButtonState = (button: HTMLButtonElement) => void;

type BarButtonProps = {
  label: string;
  callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>

const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
  return (
    <Button 
      color="inherit" // place first so it can be overwritten by props
      onClick={(e) => callback(e.currentTarget)}
      {...props}
      >
      {label}
    </Button>
  );
};

Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen . If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).

Since it's now a function component, we can get the CSS classes from a material-ui hook.

const useStyles = makeStyles({
  root: {
    flexGrow: 1
  },
  menuButton: {
    marginRight: 0
  },
  title: {
    flexGrow: 1
  }
});

const HeaderBar: FunctionComponent = ({ children }) => {

  const classes = useStyles();

  return (
    <div className={classes.root}>
      <AppBar position="static">
        <Toolbar>
          <HeaderMenu classes={classes} />
          <Typography variant="h6" className={classes.title}>
            <BarButton
              callback={() => renderModule(<HomePage />)}
              style={{ color: "white" }}
              label="Sundt Memes"
            />
          </Typography>
          {children}
        </Toolbar>
      </AppBar>
    </div>
  );
};

Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.

I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.

I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule , we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule .

interface LayoutProp {
  handle: ReactElement<any, any>;
}

export default function Layout(props: LayoutProp) {
  // use a state to respond to an asynchronous response from AuthVerifier
  // could start with a third state of null or undefined when we haven't gotten a response yet
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  // You might want to put this inside a useEffect but I'm not sure when this
  // needs to be re-run. On every re-render or just once?
  AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));

  return (
    <div>
      <HeaderBar>
        {isLoggedIn ? (
          <BarButton
            label="Log Out"
            callback={() => new CookieManager("session").setCookie("")}
          />
        ) : (
          <>
            <BarButton
              label="Log In"
              callback={() => renderModule(<LogInPage />)}
            />
            <BarButton
              label="Sign Up"
              callback={() => renderModule(<SignUpPage />)}
            />
          </>
        )}
      </HeaderBar>
      {props.handle}
    </div>
  );
}

I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM