Some tools just fit so well that they become a natural part of your workflow.
I’ve been writing code for a while now. I’ve seen frameworks rise and fall. I’ve written code I’m proud of and code that... well, let's just say it was a learning experience. Along the way, I’ve developed some preferences that have really stuck with me.
This isn't about chasing the latest trend. It's about finding tools that have genuinely improved my day-to-day development. These are the tools I consistently reach for.
Tailwind CSS: A Different Approach to Styling
I used to be a dedicated BEM (.block__element--modifier
) user. It’s a solid methodology for keeping CSS organized, but it necessitates a constant context switch. You write your markup in one file and then jump to another .css
file to define the styles.
Let's say we want to build a simple card component. With BEM, the process looks something like this:
First, you write the HTML structure:
<!-- card.html -->
<div class="card">
<h2 class="card__title">BEM Card</h2>
<p class="card__description">
This card is styled using the BEM methodology.
</p>
<button class="card__button card__button--primary">Learn More</button>
</div>
Then, you open a separate CSS file to write the styles:
/* card.css */
.card {
background-color: white;
border-radius: 0.5rem;
padding: 1.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.card__title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.75rem;
}
.card__description {
color: #4a5568; /* A shade of gray */
margin-bottom: 1rem;
}
.card__button {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
.card__button--primary {
background-color: #3b82f6; /* A shade of blue */
color: white;
}
This works, but I always found the separation to be a drag on my workflow. I had to invent class names, and making a small visual tweak required finding and editing the correct rule in a different file.
Then I tried Tailwind. Here is the exact same component:
<!-- card.html -->
<div class="rounded-lg bg-white p-6 shadow-md">
<h2 class="mb-3 text-xl font-semibold">Tailwind Card</h2>
<p class="mb-4 text-gray-600">This card is styled using Tailwind CSS.</p>
<button class="rounded bg-blue-500 px-4 py-2 text-white">Learn More</button>
</div>
That's it. There is no separate .css
file to maintain. All the styling information is right there in the markup. At first, I thought it looked cluttered, but I quickly realized it wasn't clutter; it was honesty. I could see exactly how a component was styled without leaving my HTML. Refactoring or deleting a component meant I could do so with full confidence, knowing I wasn't leaving orphaned styles in a forgotten stylesheet.
TypeScript: Bringing Confidence to My Code
I've become a huge advocate for TypeScript. For any new project I start, it's my first choice. The safety and predictability it adds, especially on a growing team, are invaluable to me.
I've spent many hours debugging issues that a type system would have caught instantly. You know the one. The classic, the legend:
Uncaught TypeError: undefined is not a function
Why did the JavaScript developer leave the party early? Because he couldn't find his prototype
.
I have console.log(typeof myVar)
-ed my way through entire applications, trying to figure out what a function is actually receiving.
For me, using plain JavaScript can sometimes feel like navigating without a map. TypeScript provides that map. It tells me before I even save the file that I've passed the wrong prop to a component. It autocompletes the properties on an object. It turns VS Code from a simple text editor into an active partner in writing correct code.
The confidence and clarity I get from TypeScript have made it a core part of my development process. It's a tool I'd advocate for on any team.
Zustand: Simple and Effective State Management
Redux is a powerful tool that brought a lot of structure to front-end state management. But the amount of setup, especially in the days of class components, could feel quite heavy.
Let's imagine a simple counter. With the classic Redux connect
pattern, you'd have several pieces of boilerplate.
First, the actions and reducer:
// actions.js
export const increment = () => ({ type: 'INCREMENT' });
export const decrement = () => ({ type: 'DECREMENT' });
// reducer.js
const initialState = { count: 0 };
export const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
Then, in your component file, you'd have the component itself, plus the connection logic at the bottom:
// Counter.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from './actions';
class Counter extends React.Component {
render() {
return (
<div>
<span>{this.props.count}</span>
<button onClick={this.props.increment}>+</button>
<button onClick={this.props.decrement}>-</button>
</div>
);
}
}
const mapStateToProps = state => ({
count: state.counter.count,
});
const mapDispatchToProps = {
increment,
decrement,
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
All of that was necessary just to get and update a single number. The logic is spread across multiple files and functions, and the connection to the component feels indirect.
I vividly remember this pain from my time at Zahir Internasional. We were building a large application, and almost every component was wrapped in this connect
HOC. Opening a file meant scrolling past a huge block of mapStateToProps
and mapDispatchToProps
just to get to the component's render
method. It created a real sense of friction; even a small change required navigating this web of boilerplate, which was a constant drag on productivity and morale.
Discovering Zustand was a breath of fresh air. Here’s how you’d build the exact same counter.
First, create the store:
// store.js
import { create } from 'zustand';
export const useCounterStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
}));
Now, let's use it in the component.
// Counter.js
import React from 'react';
import { useCounterStore } from './store';
export function Counter() {
const { count, increment, decrement } = useCounterStore();
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
That's it. The store definition is concise, and the component consumes the state and actions directly via a single hook. The logic is co-located and the amount of boilerplate is drastically reduced. It just feels more intuitive and lets me move faster.
Conclusion
For me, it's not about following trends. It's about finding tools that lead to a more efficient and enjoyable development process. Tailwind, TypeScript, and Zustand are my go-to stack because they help me sidestep common issues, reduce cognitive load, and let me focus on what I enjoy most: building things.
If you haven't tried them, I'd recommend giving them a look. You might find they fit well into your workflow, too.