Search code examples
reactjsoopinheritancecomposition

How to transform inheritance into composition in React (example)?


I have two forms: <Login /> and <Register />. Both have username and password and the register form also has a name field. Also there is a login button and a register button. The common code they share though is just the username and password so I want to refactor my code to have a component <UsernamePassord /> and use it in both <Login /> and <Register />.

What I have done so far (pseudocode):

class UsernamePassword extends React {
    constructor() {
        this.state = {username: "", password: ""}
    }

    render() {
        return <div>
            <input username onChange => setState(username: e.value)
            <input password onChange => setState(password: e.value)
        </div>
}

class Login extends UsernamePassword {

     render() {
        return <>
           { super.render() }
           <button onClick => this.state.username && this.state.password && signIn() />
}

class Register extends UsernamePassword {
     constructor() {
           this.state = {
               ...this.state,
               name: ""
            }
     }
     render() {
        return <>
           <input name onChange => setState(name: e.value)
           { super.render() }
           <button onClick => this.state.username && this.state.password && this.state.name && signUp() />
}

I don't like this code at all. It seems really difficult to scale. How can this be done cleaner using composition ?


Solution

  • There are a number of ways to handle this. You could create a component that accepts props (in this case, username and password) and handles the manipulation of values. It subsequently fires an onChange event of some sort that notifies the parent of the change and the parent can manage the state. Alternatively, you could manage the state in the component and just create an event handler prop for alerting parent components of the state. I made a working example of the second scenario based off of your code (changing class-based components for functional ones for simplicity):

    import React, { useState } from "react";
    import "./styles.css";
    
    const UsernamePassword = ({ onChange }) => {
      const [username, setUsername] = useState("");
      const [password, setPassword] = useState("");
    
      const handleChange = () => onChange({ username, password });
    
      return (
        <div>
          <input value={username} onChange={(e) => setUsername(e.target.value)} />
          <input value={password} onChange={(e) => setPassword(e.target.value)} />
          <button onClick={handleChange}>Click me!</button>
        </div>
      );
    };
    
    const Login = () => {
      const onChange = (value) => console.log("Login", value);
    
      return (
        <>
          <h2>Login</h2>
          <UsernamePassword onChange={onChange} />
        </>
      );
    };
    
    const Register = () => {
      const onChange = (value) => console.log("Register", value);
    
      return (
        <>
          <h2>Register</h2>
          <UsernamePassword onChange={onChange} />
        </>
      );
    };
    
    export default function App() {
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <h2>Start editing to see some magic happen!</h2>
          <Register />
          <Login />
        </div>
      );
    }