Back to Articles

A Deep Dive into JavaScript, JSX, and TypeScript

Posted: 2 months ago·Last Updated: 2 months ago
Share on LinkedIn
Share on X
Share on Facebook
Share on WhatsApp
Share on Telegram
Share via Email
Copy Link

After spending countless hours reviewing code, mentoring teams, and building production applications, I've noticed a pattern: many developers aren't taking full advantage of modern web development tools. Let's change that! In this post, I'll show you exactly how JavaScript, JSX, and TypeScript work together to create robust, maintainable applications.

Remember when we used to write code like this?

Circa 2015
var userList = document.getElementById('userList');
var users = [];

function addUser(name, email) {
  users.push({ name: name, email: email });
  renderUsers();
}

function renderUsers() {
  userList.innerHTML = '';
  for (var i = 0; i < users.length; i++) {
    var li = document.createElement('li');
    li.textContent = users[i].name + ' (' + users[i].email + ')';
    userList.appendChild(li);
  }
}

Sure, it worked, but it was:

  • Hard to maintain
  • Prone to bugs
  • Difficult to scale

Let's see how we can transform this using our modern toolkit.

First, let's see how modern JavaScript features make our code cleaner and more reliable:

Modern JavaScript
const UserManager = {
  users: [],
  
  addUser({ name, email }) {
    this.users = [...this.users, { name, email, id: crypto.randomUUID() }];
    this.renderUsers();
  },

  removeUser(id) {
    this.users = this.users.filter(user => user.id !== id);
    this.renderUsers();
  },

  async fetchUsers() {
    try {
      const response = await fetch('/api/users');
      this.users = await response.json();
      this.renderUsers();
    } catch (error) {
      console.error('Failed to fetch users:', error);
    }
  },

  renderUsers() {
    const userList = document.getElementById('userList');
    userList.innerHTML = this.users
      .map(user => `<li>${user.name} (${user.email})</li>`)
      .join('');
  }
};

Key improvements:

  • Object-oriented organization
  • Modern array methods
  • Destructuring
  • Async/await for API calls
  • Template literals for HTML

Now, let's transform this into a React component using JSX:

UserList.jsx
import React, { useState, useEffect } from 'react';

const UserCard = ({ user, onDelete }) => (
  <div className="user-card">
    <h3>{user.name}</h3>
    <p>{user.email}</p>
    <button 
      onClick={() => onDelete(user.id)}
      className="delete-btn"
    >
      Delete
    </button>
  </div>
);

const UserList = () => {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchUsers();
  }, []);

  const fetchUsers = async () => {
    setIsLoading(true);
    try {
      const response = await fetch('/api/users');
      const data = await response.json();
      setUsers(data);
    } catch (err) {
      setError('Failed to fetch users');
    } finally {
      setIsLoading(false);
    }
  };

  const deleteUser = async (id) => {
    try {
      await fetch(`/api/users/${id}`, { method: 'DELETE' });
      setUsers(users.filter(user => user.id !== id));
    } catch (err) {
      setError('Failed to delete user');
    }
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div className="user-list">
      {users.map(user => (
        <UserCard 
          key={user.id}
          user={user}
          onDelete={deleteUser}
        />
      ))}
    </div>
  );
};

export default UserList;

Benefits of JSX:

  • Declarative UI components
  • Reusable components
  • Built-in state management
  • Clear data flow
  • Easy error handling

Finally, let's add TypeScript to make our code even more robust:

types.ts
interface User {
  id: string;
  name: string;
  email: string;
  role?: 'admin' | 'user';
}

interface UserCardProps {
  user: User;
  onDelete: (id: string) => Promise<void>;
}

// UserList.tsx
import React, { useState, useEffect } from 'react';
import { User, UserCardProps } from './types';

const UserCard: React.FC<UserCardProps> = ({ user, onDelete }) => (
  <div className="user-card">
    <h3>{user.name}</h3>
    <p>{user.email}</p>
    {user.role && <span className="role-badge">{user.role}</span>}
    <button 
      onClick={() => onDelete(user.id)}
      className="delete-btn"
    >
      Delete
    </button>
  </div>
);

const UserList: React.FC = () => {
  const [users, setUsers] = useState<User[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetchUsers();
  }, []);

  const fetchUsers = async (): Promise<void> => {
    setIsLoading(true);
    try {
      const response = await fetch('/api/users');
      const data: User[] = await response.json();
      setUsers(data);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to fetch users');
    } finally {
      setIsLoading(false);
    }
  };

  const deleteUser = async (id: string): Promise<void> => {
    try {
      await fetch(`/api/users/${id}`, { method: 'DELETE' });
      setUsers(users.filter(user => user.id !== id));
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to delete user');
    }
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div className="user-list">
      {users.map(user => (
        <UserCard 
          key={user.id}
          user={user}
          onDelete={deleteUser}
        />
      ))}
    </div>
  );
};

export default UserList;

TypeScript advantages:

  • Clear interface definitions
  • Compile-time error checking
  • Better IDE support
  • Self-documenting code
  • Easier refactoring

Let's create a complete feature using all three technologies. Here's a searchable, filterable user management system:

UserManagement.tsx
import React, { useState, useEffect, useCallback } from 'react';
import debounce from 'lodash/debounce';

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
  lastActive: Date;
}

interface UserFilters {
  role?: 'admin' | 'user';
  searchTerm: string;
  isActive: boolean;
}

const UserManagement: React.FC = () => {
  const [users, setUsers] = useState<User[]>([]);
  const [filters, setFilters] = useState<UserFilters>({
    searchTerm: '',
    isActive: true
  });
  const [isLoading, setIsLoading] = useState(false);

  // Debounced search function
  const debouncedSearch = useCallback(
    debounce((term: string) => {
      setFilters(prev => ({ ...prev, searchTerm: term }));
    }, 300),
    []
  );

  const fetchFilteredUsers = async () => {
    setIsLoading(true);
    try {
      const queryParams = new URLSearchParams({
        role: filters.role || '',
        search: filters.searchTerm,
        active: filters.isActive.toString()
      });

      const response = await fetch(`/api/users?${queryParams}`);
      const data: User[] = await response.json();
      setUsers(data);
    } catch (error) {
      console.error('Failed to fetch users:', error);
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    fetchFilteredUsers();
  }, [filters]);

  return (
    <div className="user-management">
      <div className="filters">
        <input
          type="text"
          placeholder="Search users..."
          onChange={e => debouncedSearch(e.target.value)}
          className="search-input"
        />
        
        <select
          onChange={e => setFilters(prev => ({ 
            ...prev, 
            role: e.target.value as 'admin' | 'user' | undefined 
          }))}
        >
          <option value="">All Roles</option>
          <option value="admin">Admin</option>
          <option value="user">User</option>
        </select>

        <label>
          <input
            type="checkbox"
            checked={filters.isActive}
            onChange={e => setFilters(prev => ({
              ...prev,
              isActive: e.target.checked
            }))}
          />
          Active Users Only
        </label>
      </div>

      {isLoading ? (
        <div className="loading">Loading users...</div>
      ) : (
        <div className="user-grid">
          {users.map(user => (
            <UserCard
              key={user.id}
              user={user}
              onUpdate={fetchFilteredUsers}
            />
          ))}
        </div>
      )}
    </div>
  );
};

export default UserManagement;

This example demonstrates:

  • Modern JavaScript features (async/await, destructuring)
  • JSX for component composition
  • TypeScript for type safety
  • Real-world patterns (debouncing, filtering)
  • Error handling
  • Loading states

1. State Management:

// Bad
const [userState, setUserState] = useState<any>({});

// Good
interface UserState {
  data: User[];
  isLoading: boolean;
  error: Error | null;
}

const [userState, setUserState] = useState<UserState>({
  data: [],
  isLoading: false,
  error: null
});

2. Error Boundaries:

class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

3. Custom Hooks:

const useDebounce = <T>(value: T, delay: number): T => {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
};

The combination of JavaScript, JSX, and TypeScript gives us a powerful toolkit for building modern web applications. Each technology serves a specific purpose:

  • JavaScript provides the core functionality
  • JSX makes UI development intuitive
  • TypeScript adds safety and maintainability

Remember: Start small, add complexity as needed, and always consider the maintenance implications of your code choices.

Share on LinkedIn
Share on X
Share on Facebook
Share on WhatsApp
Share on Telegram
Share via Email
Copy Link

Ready to take your business to the next level? Let’s make it happen.

Recommended For You