One day, you have to review the login Component so the consumer can decide if the user will log in using username or email. A quick fix to achieve that is to create a component with a boolean prop in case the consumer prefers email:
import React from "react";
import PropTypes from "prop-types";
function Login({ usingEmail }) {
return (
<div className="Login">
<form>
<p>
<label>{usingEmail ? "Email:" : "Username:"}</label>
<input type={usingEmail ? "email" : "text"} />
</p>
<p>
<label>Password:</label>
<input type="password" />
</p>
<p>
<input type="submit" value="Log In" />
</p>
</form>
</div>
);
}
Login.propTypes = {
usingEmail: PropTypes.bool,
};
export default Login;
Now imagine that one day, users can also log in with phone number. Now you have a problem.
The boolean flag isn't extensible to support three variants, and following the same strategy, we would get contradicting boolean props. The consumer of the component would be capable of configuring the Component with a username and phone login, for example.
import React from "react";
import PropTypes from "prop-types";
function Login({ usingEmail, usingPhoneNumber }) {
return (
<div className="Login">
<form>
<p>
<label>
{usingEmail ? "Email:" : usingPhoneNumber ? "Phone" : "Username:"}
</label>
<input
type={usingEmail ? "email" : usingPhoneNumber ? "tel" : "text"}
/>
</p>
<p>
<label>Password:</label>
<input type="password" />
</p>
<p>
<input type="submit" value="Log In" />
</p>
</form>
</div>
);
}
Login.propTypes = {
usingEmail: PropTypes.bool,
usingPhoneNumber: PropTypes.bool,
};
export default Login;
Contracts with boolean flags are complex and deliver a bad UX to the consumer.
It complicates component signature, yelling that this component does more than one thing. It does one thing if the flag is "True" and another if the flag is "False". In the example, the worst is that the consumer doesn't know what to expect when both props are "True".
A simple solution would be to prefer Enums over booleans. A boolean is extensible and describes a clear intention.
import React from "react";
import PropTypes from "prop-types";
const USER_IDENTIFIFICATION_TYPES = {
USERNAME: "username",
EMAIL: "email",
PHONENUMBER: "phone",
};
function Login({ userIdentificationType }) {
const shouldUseEmail =
userIdentificationType === USER_IDENTIFIFICATION_TYPES.EMAIL;
const shouldUsePhone =
userIdentificationType === USER_IDENTIFIFICATION_TYPES.PHONENUMBER;
return (
<div className="Login">
<form>
<p>
<label>
{shouldUseEmail ? "Email:" : shouldUsePhone ? "Phone" : "Username:"}
</label>
<input
type={shouldUseEmail ? "email" : shouldUsePhone ? "tel" : "text"}
/>
</p>
<p>
<label>Password:</label>
<input type="password" />
</p>
<p>
<input type="submit" value="Log In" />
</p>
</form>
</div>
);
}
Login.propTypes = {
userIdentificationType: PropTypes.oneOf(
Object.values(USER_IDENTIFIFICATION_TYPES)
),
};
Login.defaultProps = {
userIdentificationType: USER_IDENTIFIFICATION_TYPES.USERNAME,
};
export default Login;
As you can see, we fix the problem of the contract, but this component is doing too much.
Besides contract complexity, boolean props are a symptom that the component may be a God Component, doing too much.
If you notice that you are in the presence of a God Component, you should split the component.
In this login component example, you could create three components, for instance, to encapsulate inner details, something like:
import React from "react";
import PropTypes from "prop-types";
function Login({ children }) {
return (
<div className="Login">
<form>
<p>{children}</p>
<p>
<label>Password:</label>
<input type="password" />
</p>
<p>
<input type="submit" value="Log In" />
</p>
</form>
</div>
);
}
Login.propTypes = {
children: PropTypes.node,
};
export default Login;
import React from "react";
import Login from "./Login";
function UsernameLogin() {
return (
<Login>
<label>Username:</label>
<input type="text" />
</Login>
);
}
export default UsernameLogin;
import React from "react";
import Login from "./Login";
function EmailLogin() {
return (
<Login>
<label>EmailLogin:</label>
<input type="email" />
</Login>
);
}
export default EmailLogin;
import React from "react";
import Login from "./Login";
function PhoneNumberLogin() {
return (
<Login>
<label>Phone:</label>
<input type="tel" />
</Login>
);
}
export default PhoneNumberLogin;
This way, your components will do one thing and do it well.
Hope that this was useful! To get more tips like this, follow me on Twitter (@gsferreira) and let's keep in touch!