Bootcamps are great at distilling a massive amount of information into a bite sized 15 week chunk to get you up to speed and ready to build entire web apps from scratch. However, what they aren't good at is affording you the time to really develop an application to its fullest potential. Of course real world work is a lot like that, time constraints and the pressure for a product mean cutting the full vision into something more manageable.
Nevertheless, I'd recently returned to one of my favorite projects I'd done during my time at a bootcamp, a project called Virtual Canvas, and spent another three weeks completely overhauling the styling and flow of pages so that users had a more seamless, professional, and overall better designed experience that showcased what I'd worked so hard on.
Along the way, I'd converted all my CSS styling to SCSS or SASS and rather than bundling all the styling into one index.scss or app.scss file to work with, I took a more modular approach to styling and included scss right next to my javascript components. And honestly, this vastly improved my file structure and pushed me to think more like in a React way. This leads me to my current tip for vastly improving your approach to React code...
Make it Inconvenient to Write Components
Let me explain. When I'd first began writing components in React, there was tendency not to treat components in a reusable way. It's almost as if you're writing new components whenever the need arises without heed towards whether you could actually accomplish more needs with one single component. Unfortunately, components are sometimes needless created for the sake of that moment rather than thoughtfully designed to scale or suit many different needs.
And when I'd chosen to bundle my js component with the accompanying sass file into an accurately named folder (ex. putting "Button.js" and "Button.scss" into a Button folder) it very much forced me to think of this component in a way that is reusable. Plus it adds wayyyy more steps to creating a component since you have to create several more files than just some javascript.
Also, this modular file structure is a visual reminder that this component holds its own insulated logic and is wholly different than the components written before it.
How About a Use Case
When refactoring my original code, there were several instances of form components appearing in different areas of my React app. One form component for signing in, one for logging in, and so on. Obviously shooting through a three week project means you have to cut some corners, but I thought this was a good opportunity to write a form component. However, there were many forms throughout my app, including signin, login, or create canvas forms all with different labels and inputs and internal state.
However, they all sorta had similar logic within them, just with different names. This led me to write this form component that will change the internal state, labels, and inputs given different props I write in:
import React, { useState } from "react"
import "./Form.scss"
const Form = props => {
const [data, setData] = useState({})
const renderInputs = () => {
return props.inputs.map(input => {
return (
<div className="field">
<label htmlFor={input.name} >{input.name}</label>
<input placeholder="" type="text" id={input.name} name={input.name} />
</div>
)
})
}
const renderPassword = () => {
if (props.hasOwnProperty("password")) {
return (
<div className="field">
<label htmlFor="password" >Password</label>
<input type="password" id="password" name="password"></input>
</div>
)
}
}
const handleFormChange = (event) => {
event.persist()
const {name, value} = event.target
setData({
...data,
[name]: value
})
}
const handleSubmit = (event) => {
event.preventDefault()
props.handleSubmit(data)
}
return (
<form className="form" onChange={handleFormChange} onSubmit={handleSubmit}>
{renderInputs()}
{renderPassword()}
<button type="submit" className="btn-secondary">{props.submitText}</button>
</form>
)
}
export default Form
I'll walk through from top to bottom now. We want to make each form controlled so this abstracted form component will start with an empty object as the internal state. The form will take in an "input" array as props as each of these array elements are objects that will construct each of our form inputs.
To render our input fields, we'll map through our input array and output label and input HTML tags and fill the HTML with things this input should describe (I used a name attribute and you can imagine that more specifications can be added to the input objects to specify the input HTML).
Also, since some of my forms required passwords and some didn't, I specified that this form component will accept a password prop that is a boolean. If the password prop is true then make a password field. Don't if false.
Here's what I thought was really cool. Since the state is currently an empty object, then how can state accurately change on to represent the changing form? Well, if we specify each input with name attribute, then the onChange event can pick up the correct input that's been changed completely abstracted away from what is actually in the form. The event will catch the current value and the name of the input, then log the name and the value into the state object. If the inputs were named the same at all then there would be foreseeable issues with this implementation but this can be easily avoidable.
Finally, the submit handler will just submit the data in state to a callback function we've passed down as props called "handleSubmit". This allows us to further abstract away specific implementation details in the form so that we can easily reuse the form across the app with little difficulty and enough customizability to suit our needs as the app scales.
Conclusion
I think practicing with limitations on how many components you can create and even going so far as using a file structure where it's inconvenient to write more components will up you React game significantly. It's a good exercise and forces you into a mindset that approaches scalability and reusability in a good way.
Top comments (6)
Hey Christian,
Good point about code-reusability. However, in my opinion, this is not a good approach. First, forms are unique and although you see a pattern of inputs, each input is very unique, with validation, different types of inputs, etc. Then eventually you will see yourself writing conditions and props for all the different scenarios that a form can have and then the props will get really complex and your component will be completely unusable and unmaintainable, and you will break it up in smaller pieces and be back at the original "issue" of writing a lot of components.
I would suggest you to look at form libraries like Formik (jaredpalmer.com/formik/) which offers reusability in the form logic but gives you the flexibility to render the UI elements as you wish.
I have experience first hand where generalizing a form component is a bad thing. especially if you are working against a spec or design. Each form have their own behaviour and logic that ends up making a mess in the general form component.
I second this, also I suggest looking react-hook-forms that make the same thing
Ahh Gotcha. I was going for more of an illustrative example to try to abstract away implementation details but didn't consider the fact of form validation. Thanks fellas!
Sure.
Also, I would like to add that having a big number of components is not bad. If you need to have many components, you will have them and that's just fine. What is bad is having unnecessary components. However, having fewer components can also be bad.
Instead of attaching to the number of components, you can think about one of the main principles of component philosophy. Is it breaking the single responsibility principle? Then it's time to break it up in smaller pieces. That's the basic thing and you can't go wrong with that ;) Large scale enterprise applications will have thousands of components.
Making it "inconvenient" for adding new components, in my opinion, is not beneficial for scalability. Create just the components that you need, not more, not less.
Hey Christian, and any others reading this, please note -
The "renderX()" functions as defined here will cause all of the inputs to unmount and remount an every render and can therefore be very costly.
This is due to the way React's diffing algorithm (reconciliation) works.
The snippets should either be inlined, memoized, or extracted to a separate function with props.
For more information, see the following discussions -
mobile.twitter.com/0xca0a/status/1...
stackoverflow.com/questions/523720...
I will also add that when mapping a list to components, you should always use the key prop.
Read here -
paulgray.net/keys-in-react/