Toolbox
Shelf

Pages

  • Home
  • Shelf
  • Toolbox

Extras

  • Resume

Crafted with and few cups of coffee.

Designed in Figma • Built with Next.js & Tailwind • Hosted on Vercel

© 2026 Gentle Joseph | All rights reserved.

May 29th, 2022•6 min read

Functional Programming in React

Functional programming is a paradigm that emphasizes immutability, pure functions, and declarative code. React naturally aligns with many functional programming principles, making it an excellent framework for applying these concepts.

1. Pure Functions and Components

Pure functions are the foundation of functional programming. They always return the same output for the same input and have no side effects.

// Pure component - always renders the same output for the same props
const UserCard = ({ name, email }) => (
  <div className="user-card">
    <h3>{name}</h3>
    <p>{email}</p>
  </div>
);

// Impure component - depends on external state
const ImpureUserCard = ({ name }) => (
  <div className="user-card">
    <h3>{name}</h3>
    <p>{Math.random()}</p> {/* Side effect - not predictable */}
  </div>
);

2. Controlled vs Uncontrolled Components

React forms can be managed in two main ways: controlled and uncontrolled components. Understanding both approaches helps you choose the right one for your use case.

  • Controlled Components

Controlled components are fully managed by React state. You pass the value and change handler as props, making the component predictable and testable.

// Controlled input component
const ControlledInput = ({ value, onChange, placeholder }) => (
  <input
    type="text"
    value={value}
    onChange={onChange}
    placeholder={placeholder}
  />
);

// Usage
const App = () => {
  const [inputValue, setInputValue] = useState('');
  return (
    <ControlledInput
      value={inputValue}
      onChange={(e) => setInputValue(e.target.value)}
      placeholder="Enter text..."
    />
  );
};
  • Uncontrolled Components

Uncontrolled components manage their own state internally, using refs to access values. They are less predictable but sometimes useful for simple use cases or integrating with non-React code.

// Uncontrolled input component
const UncontrolledInput = ({ placeholder }) => {
  const inputRef = useRef();

  const handleButtonClick = () => {
    alert(inputRef.current.value);
  };

  return (
    <>
      <input type="text" ref={inputRef} placeholder={placeholder} />
      <button onClick={handleButtonClick}>Show Value</button>
    </>
  );
};

// Usage
const App = () => <UncontrolledInput placeholder="Enter text..." />;

3. Higher-Order Components (HOCs)

A higher-order component is an example of functional programming because it's a function that return other functions, which is an application of first-class functions.

// HOC that adds loading functionality
const withLoading = (WrappedComponent) => {
  return ({ isLoading, ...props }) => {
    if (isLoading) {
      return <div>Loading...</div>;
    }
    return <WrappedComponent {...props} />;
  };
};

// Usage
const UserList = ({ users }) => (
  <ul>
    {users.map((user) => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);

const UserListWithLoading = withLoading(UserList);

4. Function Composition with Hooks

React Hooks enable function composition, allowing us to build complex logic from simple, reusable functions.

// Custom hooks as composable functions
const useCounter = (initialValue = 0) => {
  const [count, setCount] = useState(initialValue);

  const increment = useCallback(() => setCount((c) => c + 1), []);
  const decrement = useCallback(() => setCount((c) => c - 1), []);
  const reset = useCallback(() => setCount(initialValue), [initialValue]);

  return { count, increment, decrement, reset };
};

const useLocalStorage = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : initialValue;
  });

  const setStoredValue = useCallback(
    (newValue) => {
      setValue(newValue);
      localStorage.setItem(key, JSON.stringify(newValue));
    },
    [key]
  );

  return [value, setStoredValue];
};

// Composing hooks
const Counter = () => {
  const { count, increment, decrement, reset } = useCounter(0);
  const [persistedCount, setPersistedCount] = useLocalStorage('count', 0);

  useEffect(() => {
    setPersistedCount(count);
  }, [count, setPersistedCount]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

5. Immutability and State Updates

Functional programming emphasizes immutability. In React, we should never mutate state directly but create new objects/arrays.

// ❌ Mutating state directly
const TodoList = () => {
  const [todos, setTodos] = useState([]);

  const addTodo = (text) => {
    todos.push({ id: Date.now(), text, completed: false }); // Mutation!
    setTodos(todos);
  };

  // This won't trigger re-render because the array reference hasn't changed
};

// ✅ Immutable state updates
const TodoList = () => {
  const [todos, setTodos] = useState([]);

  const addTodo = (text) => {
    setTodos((prevTodos) => [
      ...prevTodos,
      { id: Date.now(), text, completed: false },
    ]);
  };

  const toggleTodo = (id) => {
    setTodos((prevTodos) =>
      prevTodos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const removeTodo = (id) => {
    setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
  };

  return (
    <div>
      {todos.map((todo) => (
        <div key={todo.id}>
          <span
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </span>
          <button onClick={() => toggleTodo(todo.id)}>Toggle</button>
          <button onClick={() => removeTodo(todo.id)}>Remove</button>
        </div>
      ))}
    </div>
  );
};

6. Functional State Management with useReducer

useReducer follows functional programming principles by using pure reducer functions to manage complex state.

// Pure reducer function
const todoReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.payload,
            completed: false,
          },
        ],
      };

    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        ),
      };

    case 'SET_FILTER':
      return {
        ...state,
        filter: action.payload,
      };

    default:
      return state;
  }
};

const TodoApp = () => {
  const [state, dispatch] = useReducer(todoReducer, {
    todos: [],
    filter: 'all',
  });

  const addTodo = (text) => {
    dispatch({ type: 'ADD_TODO', payload: text });
  };

  const toggleTodo = (id) => {
    dispatch({ type: 'TOGGLE_TODO', payload: id });
  };

  // Component JSX...
};

7. Declarative vs Imperative Programming

React encourages declarative programming - describing what the UI should look like rather than how to achieve it.

// Imperative approach (vanilla JavaScript)
function createUserList(users) {
  const ul = document.createElement('ul');

  for (let i = 0; i < users.length; i++) {
    const li = document.createElement('li');
    li.textContent = users[i].name;
    ul.appendChild(li);
  }

  document.body.appendChild(ul);
}

// Declarative approach (React)
const UserList = ({ users }) => (
  <ul>
    {users.map((user) => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);

Key Benefits

  1. Predictability: Pure functions and immutable data make code behavior predictable
  2. Testability: Pure components are easy to test with different inputs
  3. Reusability: Functional components and hooks can be easily composed and reused
  4. Debugging: Immutable state makes it easier to track changes and debug issues
  5. Performance: React can optimize pure components with React.memo and other techniques

Conclusion

Functional programming in React isn't just about using functions - it's about embracing immutability, composition, and declarative patterns. By following these principles, you'll write more maintainable, testable, and predictable React applications.

The combination of React's component model with functional programming concepts creates a powerful paradigm for building modern user interfaces that are both performant and easy to reason about.

Back to Category