Do Function Components Mean Functional Programming?
Daniel Hayes
Full-Stack Engineer · Leapcell
Long-term React users are likely familiar with the fact that React has two types of components:
- Class Components
- Function Components
Given the mention of "class" and "function," it’s natural to wonder:
- Are class components related to OOP (Object-Oriented Programming)?
- Are function components related to FP (Functional Programming)?
After all, if class components are associated with OOP, then OOP principles (inheritance, encapsulation, polymorphism, etc.) could guide the development of class-based components. Similarly, FP principles could influence function components. In other words, we might directly apply the best practices of these programming paradigms to React projects.
So, what is the relationship between function components and functional programming? This article explores this topic in depth.
Programming Paradigms and DSL
First, we should clarify that framework syntax is essentially a type of DSL (Domain-Specific Language), tailored for development in a specific domain.
For example, React is a DSL for building views. Although different platforms use different frameworks for views, such as:
- For web: ReactDOM
- For mini-programs: Taro
- For native development: ByteDance’s internal framework React Lynx
These frameworks generally follow the same DSL (React syntax). This DSL is not tied to any specific programming paradigm but should instead be seen as a collection of language features well-suited for view development.
Therefore, as part of the React DSL:
- Function components can embody OOP principles.
- Class components can reflect FP principles.
As long as these principles benefit view development, they can be incorporated into the DSL.
For example, consider the following function component Header
, which is composed of WelcomeMessage
and LogoutButton
. This demonstrates OOP's principle of composition over inheritance:
function Header(props) { return ( <div> <WelcomeMessage name={props.name} /> <LogoutButton onClick={props.onLogout} /> </div> ); }
Similarly, consider the class component Cpn
, where state count
is updated not through mutation (this.state.count++
) but by calling this.setState
with immutable data:
class Cpn extends React.Component { // ... onClick() { const count = this.state.count; this.setState({ count: count + 1 }); } render() { // ... } }
Using immutable data reflects principles from FP.
Thus, when exploring any React feature, we should think through these three steps:
- What is React’s core development philosophy?
- What ideas from various programming paradigms are used to implement this philosophy?
- How are these ideas applied in React?
By applying this thought process to the relationship between function components and functional programming, we find that:
- Function components are the result of implementation (step 3).
- Functional programming is a programming paradigm (step 2).
This defines their relationship: Function components are the product of implementing multiple programming paradigms (primarily OOP and FP) in React, borrowing some ideas from FP along the way.
Function components should not be seen solely as the embodiment of functional programming in React.
The Evolution of Function Components
Let’s explore the evolution of function components using the three-step thought process mentioned earlier. React’s development philosophy is best expressed through this formula:
UI = fn(snapshot);
To implement this philosophy, two key elements are needed:
- Data snapshots
- Function mapping
Here, immutable data from FP is more suitable as the carrier for data snapshots. This is why state in React is immutable—the essence of state is a snapshot.
The carrier for function mapping doesn’t have specific requirements. In React, every update triggers a re-render, and the render process itself is the function mapping process. The input is props
and state
, and the output is JSX.
In contrast, Vue components align more with OOP principles. Consider this Vue App
component:
const App = { setup(initialProps) { const count = reactive({count: 0}) const add = () => { count.value++ } return {count, add} } template: "...omitted" }
The setup
method of the component is executed only once during initialization. Subsequent updates manipulate the same data within a closure, which corresponds to the concept of an instance in OOP.
Since React doesn’t impose special requirements on the carrier for function mapping, both class components and function components are viable options.
Why Did Function Components Replace Class Components?
Many believe that the improved reusability of logic via hooks is the main reason function components outshine class components. However, the decorator-based class development model, especially when paired with TypeScript, has proven to be an effective approach to logic reuse.
The real reason lies in the ability of function components to better implement the UI = fn(snapshot) philosophy.
As mentioned earlier, the snapshot
in the formula represents a snapshot of the state, which in React includes:
state
props
context
For a given component, the formula UI = fn(snapshot)
ensures that identical snapshots yield identical outputs (JSX). However, state updates may also trigger side effects, such as data fetching or DOM manipulation.
In class components, these side effect logics are scattered across various lifecycle methods, making them difficult for React to control. In function components, however:
- Side effects are confined to
useEffect
. React ensures that effects from the previous render are cleaned up (via the return value ofuseEffect
) before applying new ones. - The propagation of
ref
is restricted through mechanisms likeforwardRef
, limiting its potential impact. - Data fetching side effects are managed by
Suspense
, as shown below:
function UserList({ id }) { const data = use(fetchUser(id)); // ... }
Usage:
<Suspense fallback={<div>Loading...</div>}> <UserList id={1} /> </Suspense>
In short, function components ensure that side effects remain manageable, enabling consistent outputs for the same snapshot inputs. This aligns with FP’s concept of pure functions, which is why function components have become the mainstream choice in React.
Conclusion
Function components are not a direct implementation of functional programming in React but rather the most suitable carrier for realizing React’s core philosophy: UI = fn(snapshot). React integrates excellent ideas from various programming paradigms, with FP being the most influential. Ultimately, every design choice serves the overarching philosophy.
We are Leapcell, your top choice for hosting Node.js projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ