DEV Community

Cover image for Tips and Strategies for Writing Clean and Maintainable JSX Code
Rowsan Ali
Rowsan Ali

Posted on

Tips and Strategies for Writing Clean and Maintainable JSX Code

Writing clean and maintainable code is crucial for long-term project success, especially when working with JSX in React applications. Below are some essential tips and strategies, accompanied by code examples, to help you write better JSX.
Follow me on X

1. Keep Components Small and Focused

Divide your application into small, manageable components that do one thing and do it well. This makes your code easier to understand and test.

// Good
function UserProfile({ user }) {
  return (
    <div>
      <UserAvatar user={user} />
      <UserInfo user={user} />
    </div>
  );
}

// Avoid
function UserProfile({ user }) {
  // A large component that handles rendering everything related to the user
}
Enter fullscreen mode Exit fullscreen mode

2. Use Descriptive Component Names

Choose clear and descriptive names for components to indicate what they do or represent.

// Good
function EmailInputField() {
  // ...
}

// Avoid
function InputField() {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

3. Destructure Props for Clarity

Destructure props in functional components to make it clear which properties the component expects and uses.

// Good
function Greeting({ name, message }) {
  return <h1>{`Hello, ${name}! ${message}`}</h1>;
}

// Avoid
function Greeting(props) {
  return <h1>{`Hello, ${props.name}! ${props.message}`}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

4. Keep JSX Readable with Proper Indentation

Just like HTML, properly indented JSX is crucial for readability. Use a consistent indentation style.

// Good
return (
  <div>
    <h1>Title</h1>
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  </div>
);

// Avoid
return <div><h1>Title</h1><ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul></div>;
Enter fullscreen mode Exit fullscreen mode

5. Abstract Conditional Logic

Move complex conditional logic outside of your JSX to keep the template clean.

// Good
function WelcomeBanner({ user }) {
  const isLoggedIn = user != null;
  return (
    <div>
      {isLoggedIn ? <LoggedInBanner user={user} /> : <LoggedOutBanner />}
    </div>
  );
}

// Avoid
function WelcomeBanner({ user }) {
  return (
    <div>
      {user != null ? <LoggedInBanner user={user} /> : <LoggedOutBanner />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

6. Use Comments Wisely

Inline comments can clarify the purpose of complex parts of the template, but avoid over-commenting obvious things.

// Good
function ComplexComponent() {
  // Fetching data from the API and filtering for active users
  // ...

  return (
    // Render only if there are active users
    {activeUsers.length > 0 && (
      <UserList users={activeUsers} />
    )}
  );
}

// Avoid
// This is a div
// ...
Enter fullscreen mode Exit fullscreen mode

7. Avoid Inline Styles

Prefer external stylesheets or styled-components over inline styles for better separation of concerns and reusability.

// Good
import "./Button.css";

function Button({ label }) {
  return <button className="primary-button">{label}</button>;
}

// Avoid
function Button({ label }) {
  return <button style={{ backgroundColor: 'blue', color: 'white' }}>{label}</button>;
}
Enter fullscreen mode Exit fullscreen mode

8. Prop Types and Default Props

Use propTypes for type-checking and defaultProps to define default values for props.

import PropTypes from 'prop-types';

function UserProfile({ name, age }) {
  // ...
}

UserProfile.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
};

UserProfile.defaultProps = {
  age: 30,
};
Enter fullscreen mode Exit fullscreen mode

9. Functional Over Class Components

Whenever possible, use functional components with hooks. They are more concise and easier to test.

// Good
function App() {
  const [count, setCount] = useState(0);
  // ...
}

// Avoid
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    // ...
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

10. Use Fragment Shorthand

Use the <>...</> shorthand for fragments to avoid additional clutter in your JSX.

// Good
function Group({ children }) {
  return <>{children}</>;
}

// Avoid
function Group({ children }) {
  return <React.Fragment>{children}</React.Fragment>;
}
Enter fullscreen mode Exit fullscreen mode

By adhering to these guidelines,

Top comments (14)

Collapse
 
seandinan profile image
Sean Dinan

Adding to #7: Since we use Tailwind for CSS and the className string can get quite long, I started abstracting it out to a classes object to keep the JSX code more concise. For example:

// utils.js
export const classes = {
  wrapper: 'flex flex-col justify-between bg-white text-grey-8 border border-grey-5 rounded'
}

// component.jsx
import { classes } from './utils';

export default function Component(){
  return (
    <section className={classes.wrapper}>
      {/* ... */}
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
teamradhq profile image
teamradhq • Edited

This is an overly complex solution for providing semantic class names that only does half the job. If you want to abstract your classnames like this, you should just use Tailwind's CSS directives:

.wrapper {
  @apply flex flex-col justify-between bg-white text-grey-8 border border-grey-5 rounded;
}
Enter fullscreen mode Exit fullscreen mode

Now you don't have to pass around that utility object:

export default function Wrapper({children}: React.PropsWithChildren<{}>}) {
  return (
    <section className="wrapper">
      {children}
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode

You also have the added benefit of having semantic classes in your application markup.

Using a utility object:

<section class="flex flex-col justify-between bg-white text-grey-8 border border-grey-5 rounded">
    ...
</section>
Enter fullscreen mode Exit fullscreen mode

Using a tailwind directives:

<section class="wrapper">
    ...
</section>
Enter fullscreen mode Exit fullscreen mode

That will make it much easier for non-developers (for example, customer support team) to identify specific components in your markup, making issues much clearer.

Collapse
 
seandinan profile image
Sean Dinan

Thank you for the thorough reply! I wasn't previously familiar with the @apply directive.

Definitely a nice option & something I'd consider using in the future.

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him)

OMG. Why not just use CSS in the first place?

Collapse
 
seandinan profile image
Sean Dinan • Edited

Well it's still CSS, just based on utility classes.

It's certainly a matter of personal preference, but for me I think it's a logical approach to styling when using React or other component-based libraries.

What would've previously been a CSS class (e.g. <div class=".calendar-wrapper") would now be a component (e.g. <CalendarWrapper />., so the CSS classes are mostly for consistency of UI & branding rather than reusing component styles.

Also, passing down a className prop to components allows for inheritance-based style customization that would've previously been done with class chaining:

// Calendar.jsx
<CalendarWrapper>
  <Title className="text-xl">Calendar</Title>
</CalendarWrapper>
Enter fullscreen mode Exit fullscreen mode

as compared to something like:

<div class="calendar-wrapper">
  <h2 class="title">Calendar</h2>
</div>
Enter fullscreen mode Exit fullscreen mode
.calendar-wrapper .title {
  font-size: 1.25rem;
  line-height: 1.75rem;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
rowsanali profile image
Rowsan Ali

Because some people prefer tailwind over pure CSS

Thread Thread
 
syeo66 profile image
Red Ochsenbein (he/him) • Edited

Which never made sense to me. Why learn and use a new non-standard syntax to do the same thing in a less optimal way (especially if you have to extract it to a separate file again)? But well, I guess do not have to understand that.

Thread Thread
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

Because you can do it on demand.

With tailwind you have compactness. You can style components inside a single file, without the styles taking more visual space than the ts/html/jsx part.

Tailwinds only real alternative is inline css or css-in-jss.
All other non-tailwind solutions are much longer / more code (and mostly used in a separate file because it's so much) .

Collapse
 
edimeri profile image
Erkand Imeri • Edited

In terms of readability

// Good
function WelcomeBanner({ user }) {
  const isLoggedIn = user != null;
  return (
    <div>
      {isLoggedIn ? <LoggedInBanner user={user} /> : <LoggedOutBanner />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

i would prefer this one:

// Even better readability
function WelcomeBanner({ user }) {
  const isLoggedIn = user != null;

  if(!isLoggedIn) return <LoggedOutBanner />;

  return <LoggedInBanner user={user} />;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
teamradhq profile image
teamradhq

2. Use Descriptive Component Names

I would argue that both of these are descriptive names and I would expect that InputField is a generic input component that will be used to create specific inputs:

// Good
function EmailInputField(props: InputFieldProps & EmailProps) {
  return <InputField {...props} />;
}

// Avoid
function InputField(props: InputFieldProps)) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

3. Destructure Props for Clarity

I think this is definitely true for JavaScript users. But with TypeScript, I'd recommend the opposite a lot of the time. Also, you should just pass props if any of your input is stateful so you don't end up with a bunch of duplicated nomenclature:

// bad
function StatefulGreeting({ name, message }) {
  const [nameState, setNameState] = useState(name);
  const [messageState, setMessageState] = useState(message);

  return <SomeComponent name={nameState} message={messageState} />;
}
Enter fullscreen mode Exit fullscreen mode

This is just adding noise and making it complicated to reason about:

// good
function StatefulGreeting(props) {
  const [name, setName] = useState(props.name);
  const [message, setMessage] = useState(props.message);

  return <SomeComponent {...{name, message}} />
}
Enter fullscreen mode Exit fullscreen mode

5. Abstract Conditional Logic

This isn't is a very good example of complex conditional logic. In the current form, you are just doing a loose falsy comparison on user.

In this case, you should flip your examples:

// Bad
function WelcomeBanner({ user }) {
  const isLoggedIn = user != null;

  return (
    <div>
      {isLoggedIn ? <LoggedInBanner user={user} /> : <LoggedOutBanner />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This first version just adds unnecessary noise for such a simple comparison. Something so simple should be expressed inline:

// Better
function WelcomeBanner({ user }) {
  return (
    <div>
      {user != null 
        ? <LoggedInBanner user={user} /> 
        : <LoggedOutBanner />
      }
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This is better, but the loose comparison is unnecessary too. For simple conditions like this the most common pattern I see in the React community is like this:

// Best
function WelcomeBanner({ user }) {
  return (
    <div>
      {user && <LoggedInBanner user={user} /> || <LoggedOutBanner />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Short, simple and to the point.

Now say you have an actually complex condition, then you should extract it from your component template:

// bad
const isLoggedInLocalAndOverage = user && user.locale === APP_LOCALE && user.birthday < dayjs().subtract(18, 'year')

return <div>
    {isLoggedInLocalAndOverage && <LoggedInBanner user={user} /> || <LoggedOutBanner />}
</div>
Enter fullscreen mode Exit fullscreen mode

But that's still bad as you have to reason about it in your component, and this kind of logic may be needed elsewhere.

Better to abstract it out of the component completely:

return <div>
    {isLoggedInLocalAndOverage(user) && <LoggedInBanner user={user} /> || <LoggedOutBanner />}
</div>
Enter fullscreen mode Exit fullscreen mode

I think this is one piece of advice that's always missing from these kinds of articles: express component logic in your component, express your application logic elsewhere.

6. Use Comments Wisely

I feel like all of your examples here are bad comments. There's no difference between:

  • "Fetching data from the API and filtering for active users"
  • "This is a div"

They both literally describe the code. If your API call looks something like this, then it should be pretty clear what's happening. And if it doesn't look like this, you should modify it to make it more explicit.

api.get('/users', { active: true })
Enter fullscreen mode Exit fullscreen mode

And this is explicit from the wording. It would be very worrying if developers need a comment to understand what is happening here:

{activeUsers.length > 0 && (
  <UserList users={activeUsers} />
)}
Enter fullscreen mode Exit fullscreen mode

I think in your example, the only comment that's needed is to document the component's purpose as its name is very generic and doesn't describe itself:

/**  
 * Renders a list of active users.
 * @constructor  
 */
function ComplexComponent() {
  //...
}
Enter fullscreen mode Exit fullscreen mode

A better solution here might be renaming the component, so that the comment is redundant:

function ActiveUsersList() {
  //...
}
Enter fullscreen mode Exit fullscreen mode

And if you find yourself needing to pepper your component with comments, you should probably go back to step one and break it up into smaller components.

7. Avoid Inline Styles

This is a big opinion. Your example is very basic and not really inline with how a lot of teams would use inline styles. At the very least you should update it to use some variables:

function Button({ label }) {
  return (
    <button style={{ 
      backgroundColor: theme.colors.blue, 
      color: theme.colors.white,
    }}>      
      {label}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

But again, your advice here is personal opinion... On the one hand, extracting styles makes components cleaner. On the other hand, now you've increased the complexity of your application and maintenance burden.

Which of these is preferable is a matter for individual teams to determine.

8. Prop Types and Default Props

This is certainly true for JavaScript users. In this case the prop-types package is almost mandatory. But I've almost never used it on a TypeScript project. TypeScript is very good at catching a lot of these kinds of errors before runtime which just makes prop-types redundant.

If you need to check types and enforce defaults, just define a default props variable and merge them with passed props:

type UserProfileProps = {
  name: string;
  age?: number;
}

const DEFAULT_PROPS: Pick<UserProfileProps, 'age'> = { age: 30 };

function UserProfile(props: UserProfileProps): React.JSX.Element {
  const { name, age } = { ...DEFAULT_PROPS, ...props };
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

I would add point 1: use a proper state manager.
Without it, or relying on React build-in defaults like useState, useContext and useEffect to manage state - is pretty much a code smell. (With single exception in writing ui library code and using useContext where)

I recommend Jotai.

Resct number 1 biggest problem is unnecessary re-renders. Absolutely every react developer suffers because of it at least once.
This problem goes completely away using a proper state manager (and using it right).

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

Full Fragment is useful in for loops where you can group all entries with a react key prop without having to add it to every single component

Collapse
 
jonrandy profile image
Jon Randy 🎖️

"Clean and Maintainable JSX Code" feels very much like an oxymoron

Collapse
 
adaptive-shield-matrix profile image
Adaptive Shield Matrix

In comparison to that, jquery and direct dom manipulation?

I would say the title could be changed to "clean and maintainable react component code"
because half the suggestions do not apply to Svelte, Solidjs or other Solutions that use JSX as well.
The times where React was the only one using JSX are behind us.