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