Software DevelopmentJanuary 18, 20252 min readUpdated 10 months ago

A Deep Dive into JavaScript, JSX, and TypeScript

Share this article

Send it to someone who would find it useful.

Copied
Table of contents

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.

The Evolution of Modern Web Development

Remember when we used to write code like this?

Circa 2015
1var userList = document.getElementById('userList');
2var users = [];
3
4function addUser(name, email) {
5  users.push({ name: name, email: email });
6  renderUsers();
7}
8
9function renderUsers() {
10  userList.innerHTML = '';
11  for (var i = 0; i < users.length; i++) {
12    var li = document.createElement('li');
13    li.textContent = users[i].name + ' (' + users[i].email + ')';
14    userList.appendChild(li);
15  }
16}

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.

1. Modern JavaScript: The Foundation

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

Modern JavaScript
1const UserManager = {
2  users: [],
3  
4  addUser({ name, email }) {
5    this.users = [...this.users, { name, email, id: crypto.randomUUID() }];
6    this.renderUsers();
7  },
8
9  removeUser(id) {
10    this.users = this.users.filter(user => user.id !== id);
11    this.renderUsers();
12  },
13
14  async fetchUsers() {
15    try {
16      const response = await fetch('/api/users');
17      this.users = await response.json();
18      this.renderUsers();
19    } catch (error) {
20      console.error('Failed to fetch users:', error);
21    }
22  },
23
24  renderUsers() {
25    const userList = document.getElementById('userList');
26    userList.innerHTML = this.users
27      .map(user => `<li>${user.name} (${user.email})</li>`)
28      .join('');
29  }
30};

Key improvements:

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

2. JSX: Component-Based Architecture

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

UserList.jsx
1import React, { useState, useEffect } from 'react';
2
3const UserCard = ({ user, onDelete }) => (
4  <div className="user-card">
5    <h3>{user.name}</h3>
6    <p>{user.email}</p>
7    <button 
8      onClick={() => onDelete(user.id)}
9      className="delete-btn"
10    >
11      Delete
12    </button>
13  </div>
14);
15
16const UserList = () => {
17  const [users, setUsers] = useState([]);
18  const [isLoading, setIsLoading] = useState(false);
19  const [error, setError] = useState(null);
20
21  useEffect(() => {
22    fetchUsers();
23  }, []);
24
25  const fetchUsers = async () => {
26    setIsLoading(true);
27    try {
28      const response = await fetch('/api/users');
29      const data = await response.json();
30      setUsers(data);
31    } catch (err) {
32      setError('Failed to fetch users');
33    } finally {
34      setIsLoading(false);
35    }
36  };
37
38  const deleteUser = async (id) => {
39    try {
40      await fetch(`/api/users/${id}`, { method: 'DELETE' });
41      setUsers(users.filter(user => user.id !== id));
42    } catch (err) {
43      setError('Failed to delete user');
44    }
45  };
46
47  if (isLoading) return <div>Loading...</div>;
48  if (error) return <div>Error: {error}</div>;
49
50  return (
51    <div className="user-list">
52      {users.map(user => (
53        <UserCard 
54          key={user.id}
55          user={user}
56          onDelete={deleteUser}
57        />
58      ))}
59    </div>
60  );
61};
62
63export default UserList;

Benefits of JSX:

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

3. TypeScript: Adding Type Safety

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

types.ts
1interface User {
2  id: string;
3  name: string;
4  email: string;
5  role?: 'admin' | 'user';
6}
7
8interface UserCardProps {
9  user: User;
10  onDelete: (id: string) => Promise<void>;
11}
12
13// UserList.tsx
14import React, { useState, useEffect } from 'react';
15import { User, UserCardProps } from './types';
16
17const UserCard: React.FC<UserCardProps> = ({ user, onDelete }) => (
18  <div className="user-card">
19    <h3>{user.name}</h3>
20    <p>{user.email}</p>
21    {user.role && <span className="role-badge">{user.role}</span>}
22    <button 
23      onClick={() => onDelete(user.id)}
24      className="delete-btn"
25    >
26      Delete
27    </button>
28  </div>
29);
30
31const UserList: React.FC = () => {
32  const [users, setUsers] = useState<User[]>([]);
33  const [isLoading, setIsLoading] = useState<boolean>(false);
34  const [error, setError] = useState<string | null>(null);
35
36  useEffect(() => {
37    fetchUsers();
38  }, []);
39
40  const fetchUsers = async (): Promise<void> => {
41    setIsLoading(true);
42    try {
43      const response = await fetch('/api/users');
44      const data: User[] = await response.json();
45      setUsers(data);
46    } catch (err) {
47      setError(err instanceof Error ? err.message : 'Failed to fetch users');
48    } finally {
49      setIsLoading(false);
50    }
51  };
52
53  const deleteUser = async (id: string): Promise<void> => {
54    try {
55      await fetch(`/api/users/${id}`, { method: 'DELETE' });
56      setUsers(users.filter(user => user.id !== id));
57    } catch (err) {
58      setError(err instanceof Error ? err.message : 'Failed to delete user');
59    }
60  };
61
62  if (isLoading) return <div>Loading...</div>;
63  if (error) return <div>Error: {error}</div>;
64
65  return (
66    <div className="user-list">
67      {users.map(user => (
68        <UserCard 
69          key={user.id}
70          user={user}
71          onDelete={deleteUser}
72        />
73      ))}
74    </div>
75  );
76};
77
78export default UserList;

TypeScript advantages:

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

Putting It All Together: A Real-World Example

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

UserManagement.tsx
1import React, { useState, useEffect, useCallback } from 'react';
2import debounce from 'lodash/debounce';
3
4interface User {
5  id: string;
6  name: string;
7  email: string;
8  role: 'admin' | 'user';
9  lastActive: Date;
10}
11
12interface UserFilters {
13  role?: 'admin' | 'user';
14  searchTerm: string;
15  isActive: boolean;
16}
17
18const UserManagement: React.FC = () => {
19  const [users, setUsers] = useState<User[]>([]);
20  const [filters, setFilters] = useState<UserFilters>({
21    searchTerm: '',
22    isActive: true
23  });
24  const [isLoading, setIsLoading] = useState(false);
25
26  // Debounced search function
27  const debouncedSearch = useCallback(
28    debounce((term: string) => {
29      setFilters(prev => ({ ...prev, searchTerm: term }));
30    }, 300),
31    []
32  );
33
34  const fetchFilteredUsers = async () => {
35    setIsLoading(true);
36    try {
37      const queryParams = new URLSearchParams({
38        role: filters.role || '',
39        search: filters.searchTerm,
40        active: filters.isActive.toString()
41      });
42
43      const response = await fetch(`/api/users?${queryParams}`);
44      const data: User[] = await response.json();
45      setUsers(data);
46    } catch (error) {
47      console.error('Failed to fetch users:', error);
48    } finally {
49      setIsLoading(false);
50    }
51  };
52
53  useEffect(() => {
54    fetchFilteredUsers();
55  }, [filters]);
56
57  return (
58    <div className="user-management">
59      <div className="filters">
60        <input
61          type="text"
62          placeholder="Search users..."
63          onChange={e => debouncedSearch(e.target.value)}
64          className="search-input"
65        />
66        
67        <select
68          onChange={e => setFilters(prev => ({ 
69            ...prev, 
70            role: e.target.value as 'admin' | 'user' | undefined 
71          }))}
72        >
73          <option value="">All Roles</option>
74          <option value="admin">Admin</option>
75          <option value="user">User</option>
76        </select>
77
78        <label>
79          <input
80            type="checkbox"
81            checked={filters.isActive}
82            onChange={e => setFilters(prev => ({
83              ...prev,
84              isActive: e.target.checked
85            }))}
86          />
87          Active Users Only
88        </label>
89      </div>
90
91      {isLoading ? (
92        <div className="loading">Loading users...</div>
93      ) : (
94        <div className="user-grid">
95          {users.map(user => (
96            <UserCard
97              key={user.id}
98              user={user}
99              onUpdate={fetchFilteredUsers}
100            />
101          ))}
102        </div>
103      )}
104    </div>
105  );
106};
107
108export 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

Best Practices and Tips

1. State Management:

1// Bad
2const [userState, setUserState] = useState<any>({});
3
4// Good
5interface UserState {
6  data: User[];
7  isLoading: boolean;
8  error: Error | null;
9}
10
11const [userState, setUserState] = useState<UserState>({
12  data: [],
13  isLoading: false,
14  error: null
15});

2. Error Boundaries:

1class ErrorBoundary extends React.Component<
2  { children: React.ReactNode },
3  { hasError: boolean }
4> {
5  state = { hasError: false };
6
7  static getDerivedStateFromError() {
8    return { hasError: true };
9  }
10
11  render() {
12    if (this.state.hasError) {
13      return <h1>Something went wrong.</h1>;
14    }
15
16    return this.props.children;
17  }
18}

3. Custom Hooks:

1const useDebounce = <T>(value: T, delay: number): T => {
2  const [debouncedValue, setDebouncedValue] = useState<T>(value);
3
4  useEffect(() => {
5    const handler = setTimeout(() => {
6      setDebouncedValue(value);
7    }, delay);
8
9    return () => {
10      clearTimeout(handler);
11    };
12  }, [value, delay]);
13
14  return debouncedValue;
15};

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 this article

Send it to someone who would find it useful.

Copied