Schema-Driven React for More Efficient Development
Nick Scialli
August 30, 2020
I find React to be quite enjoyable front-end tech. However, sometimes I feel like I’m repeating the same old boilerplate in a component with many similar/repeated elements. In this post, I’ll show you how I like to streamline React development at times using schemas that define the components I’m using.
Note that I’ll be using React with Typescript in this post, but this all works just as well without the type defintions!
An Example: A Long Form of Textareas
Let’s say we’re developing some kind of survey application and we want to ask many long-form questions. We know that we’ll need a lot of textareas to ask these questions, and we want to store the answers in an answers
object.
Our survey will ask about a bunch of different food preference questions. Our example questions will be:
- Tell us about your ideal hamburger
- Tell us about your ideal pizza
- What is your favorite food memory and why?
Of course, we could go ahead and write out each of these questions as their own textareas. Lets see how that might look.
import React, { useState } from 'react';
const initialAnswers = {
hamburger: '',
pizza: '',
memory: '',
};
function App() {
const [answers, setAnswers] = useState(initialAnswers);
return (
<form>
<div>
<label htmlFor="hamburger">Tell us about your ideal hamburger</label>
<br />
<textarea
id="hamburger"
value={answers.hamburger}
onChange={(e) => {
setAnswers({
...answers,
hamburger: e.target.value,
});
}}
/>
</div>
<div>
<label htmlFor="pizza">Tell us about your ideal pizza</label>
<br />
<textarea
id="pizza"
value={answers.pizza}
onChange={(e) => {
setAnswers({
...answers,
pizza: e.target.value,
});
}}
/>
</div>
<div>
<label htmlFor="memory">
What is your favorite food memory and why?
</label>
<br />
<textarea
id="memory"
value={answers.memory}
onChange={(e) => {
setAnswers({
...answers,
memory: e.target.value,
});
}}
/>
</div>
</form>
);
}
This is perfectly fine, and it works. But what if we have ~20 questions like this? We’re going to get pretty tired replicating these components. This is where a schema approach really shines.
Using a Schema to Simplify Repetitive Component Development
Instead of the apporach above, let’s make some optimizations that will make development less tedious. First, we’re going to make a single change handler that can handle changes for any of the fields in our form. Next, we’re going to create an array of objects that each represent a field in our form and we will use that to generate all our questions.
First, let’s create that consistent onChange
handler.
import React, { useState } from 'react';
const initialAnswers = {
hamburger: '',
pizza: '',
memory: '',
};
type Answers = typeof initialAnswers;
function App() {
const [answers, setAnswers] = useState(initialAnswers);
const onChange = <K extends keyof Answers>(key: K, value: Answers[K]) => {
setAnswers({
...answers,
[key]: value,
});
};
return <></>;
}
Next, we can add in an array of fields to iterate over.
import React, { useState } from 'react';
const initialAnswers = {
hamburger: '',
pizza: '',
memory: '',
};
type Answers = typeof initialAnswers;
type Field = {
key: keyof Answers;
label: string;
};
const fields: Field[] = [
{ key: 'hamburger', label: 'Tell us about your ideal hamburger' },
{ key: 'pizza', label: 'Tell us about your ideal pizza' },
{ key: 'memory', label: 'What is your favorite food memory and why?' },
];
function App() {
const [answers, setAnswers] = useState(initialAnswers);
const onChange = <K extends keyof Answers>(key: K, value: Answers[K]) => {
setAnswers({
...answers,
[key]: value,
});
};
return <></>;
}
Finally, we can iterate over the array rather than typing out every textarea
component.
import React, { useState } from 'react';
const initialAnswers = {
hamburger: '',
pizza: '',
memory: '',
};
type Answers = typeof initialAnswers;
type Field = {
key: keyof Answers;
label: string;
};
const fields: Field[] = [
{ key: 'hamburger', label: 'Tell us about your ideal hamburger' },
{ key: 'pizza', label: 'Tell us about your ideal pizza' },
{ key: 'memory', label: 'What is your favorite food memory and why?' },
];
function App() {
const [answers, setAnswers] = useState(initialAnswers);
const onChange = <K extends keyof Answers>(key: K, value: Answers[K]) => {
setAnswers({
...answers,
[key]: value,
});
};
return (
<form>
{fields.map(({ key, label }) => (
<div key={key}>
<label htmlFor={key}>{label}</label>
<br />
<textarea
id={key}
value={answers[key]}
onChange={(e) => {
onChange(key, e.target.value);
}}
/>
</div>
))}
</form>
);
}
export default App;
And there we have it! Now we can add as many fields to our array as we need and new textareas will be automagically added to the app.
Nick Scialli is a senior UI engineer at Microsoft.