DEV Community

Cover image for Moving away from ReactJs and VueJs on front-end using Clean Architecture
Jorge Sánchez Fernández
Jorge Sánchez Fernández

Posted on • Updated on • Originally published at xurxodev.Medium

Moving away from ReactJs and VueJs on front-end using Clean Architecture

This article is an English translation of the original in my blog: Alejándonos de ReactJs y VueJs en el front end usando Clean Architecture.

One of the advantages of using Clean Architecture, among others, is the ability to uncouple our application of the delivery mechanism to the user, that is, from the UI framework or library.

This advantage in long-term applications allows us to adapt in the future to the changes that will surely take place in libraries and frameworks.

In this article, we will take Clean Architecture to the extreme in the front-end by applying two delivery mechanisms: ReactJS and VueJs.

We will have as much code as possible reused between the two implementations.

This will be possible by creating the domain, data, and remote display logic of ReactJs and VueJs.

Why move away from the framework?

I have developed different technologies applying Clean Architecture like .Net, Android, iOS, and Flutter. For a long time, I am also programming in the front-end and writing about it.

One of the biggest problems when it comes to evolving an application is the coupling to the UI framework.

On the front-end little by little due to the responsibilities that applications of this type have been gained over time, it makes more and more sense to develop in a more structured way and the problems to be solved are very similar to those that exist on other fronts such as backend or mobile development.

There are frameworks like ReactJs and VueJs that make life easier for us to take on these challenges on the front-end.

A front-end application today is an independent application of the backend in many cases and therefore needs to have its own architecture.

In addition, this architecture must help us in the next points:

  • Independent of UI, frameworks, API rest and persistence, databases o third-party services.
  • Escalability.
  • Testability.

This means that if we change the vision of having a ReactJs or VueJs application, to have a front-end application that uses ReactJs or VueJs to render, this will make our lives much easier in the future.

So, for example, evolving your ReactJS application from using classes as before, to using functions and hooks as is done now, is much more trivial. The same happens if you switch in VueJS from using the options API to the composition API.

It’s more trivial because you only use the framework for what is strictly necessary, such as rendering and thus you do not overuse it, keeping it away from any type of logic, be its domain, data, or presentation logic.

Frameworks evolve and you cannot control that, but what you can control is the coupling you have with them and how their changes affect you.

But in this case, we are going to go beyond how to adapt to changes that can happen in a framework and we are going to see the amount of code that could not change when we modify ReactJS by VueJS if we use Clean Architecture and separate responsibilities.

Clean Architecture

This is the picture you keep in mind if you develop using Clean Architecture.

If you don’t have clear the concepts of Clean Architecture, I recommend that you read this article.

The most important part is the dependency rule, so if you don’t know what I’m talking about, I recommend that you read this article.

The example that we are going to see is based on the one we saw in this article.

Our scenario

It’s a shopping cart with enough functionality to look like a real example. We are going to have a global state, non-global state, and we will simulate invoke to a remote service.

Shopping Cart

Architecture

At the project structure level, we will use a monorepo using yarn workspaces, in this way we can split the project into modules or packages sharing code between them.

We have several packages:

  • Core: in this package, we will have all the shared code between the app rendered by ReactJS and the app rendered by VueJs.
  • React: in this package is found the react app version.
  • Vue: in this package is found the Vue app version.

¿What code is reused?

We are going to reuse all the code that we must have uncoupled from the UI Framework, since being different versions of the same app it makes sense that this code is shared and not write twice.

This is a demonstration exercise of the potential that Clean Architecture has but this uncoupling of the UI framework is necessary even when we develop a real app.

Using the UI framework for what is strictly necessary allows us to better adapt to changes in future versions of the framework.

This is because the code that contains the application logic, which is the most important part, that changes less over time, and is the code potentially to be shared between two versions of the same app as in this example, it’s uncoupled without depending on the UI framework.

In Clean Architecture the domain layer is where the enterprise and application business logic is located.

The data layer is where we communicate with the persistence.

The presentation logic is the one that decides what data is shown if something should be visible or not if it should be shown to the user that we are loading data or if an error should be displayed. It is where the state of the components is managed.

Each of these 3 parts contains logic that we must uncouple and is found in the core package.

Front-end Clean architecture packages

Domain Layer

The domain layer is where the enterprise and application business logic is located.

Use Cases

Use cases are intents, contains the business logic of the application, they are actions and in this example, we have the next:

  • GetProductsUseCase
  • GetCartUseCase
  • AddProductToCartUseCase
  • EditQuantityOfCartItemUseCase
  • RemoveItemFromCartUseCase

Let’s see the example of GetProductsUseCase:

export class GetProductsUseCase {
    private productRepository: ProductRepository;
constructor(productRepository: ProductRepository) {
        this.productRepository = productRepository;
    }
execute(filter: string): Promise<Either<DataError, Product[]>> {
        return this.productRepository.get(filter);
    }
}
Enter fullscreen mode Exit fullscreen mode

This use case is simple because it consists of a simple call to the data layer, in other contexts where, for example, when creating a product, we have to validate that there is no longer one with the same SKU, there would be more logic.

The use cases returns Either type, if you are not sure what it is then I recommend that you read this article and this article.

In this way, the error handling is not done using the catch of the promises, but the result object of the promise itself tells you if the result is successful or not.

The use of Either versus the classic try-catch has several advantages:

  • The flow of execution is simpler to follow without jumps between callers when an error occurs.
  • That something can go wrong, is explicitly indicated. Errors that may occur are explicitly indicated.
  • Doing the use of the exhaustive switch, if you add more errors in the future, TypeScript will warn you where you have not taken this new error into account.

The type for the errors is as follows:

export interface UnexpectedError {
    kind: "UnexpectedError";
    message: Error;
}
export type DataError = UnexpectedError;
Enter fullscreen mode Exit fullscreen mode

Potentially in the future, it could evolve to something like this:

export interface ApiError {
    kind: "ApiError";
    error: string;
    statusCode: number;
    message: string;
}
export interface UnexpectedError {
    kind: "UnexpectedError";
    message: Error;
}
export interface Unauthorized {
    kind: "Unauthorized";
}
export interface NotFound {
    kind: "NotFound";
}
export type DataError = ApiError | UnexpectedError | Unauthorized;
Enter fullscreen mode Exit fullscreen mode

And in the presentation layer, if I’m using an exhaustive switch, Typescript would warn me, I should add more cases for each new error.

Entities

The entities contain the enterprise business logic.

Let’s see the example of Cart:

type TotalPrice = number;
type TotalItems = number;
export class Cart {
    items: readonly CartItem[];
    readonly totalPrice: TotalPrice;
    readonly totalItems: TotalItems;
constructor(items: CartItem[]) {
        this.items = items;
        this.totalPrice = this.calculateTotalPrice(items);
        this.totalItems = this.calculateTotalItems(items);
    }
static createEmpty(): Cart {
        return new Cart([]);
    }
addItem(item: CartItem): Cart {
        const existedItem = this.items.find(i => i.id === item.id);
if (existedItem) {
            const newItems = this.items.map(oldItem => {
                if (oldItem.id === item.id) {
                    return { ...oldItem, quantity: oldItem.quantity + item.quantity };
                } else {
                    return oldItem;
                }
            });
return new Cart(newItems);
        } else {
            const newItems = [...this.items, item];
return new Cart(newItems);
        }
    }
removeItem(itemId: string): Cart {
        const newItems = this.items.filter(i => i.id !== itemId);
return new Cart(newItems);
    }
editItem(itemId: string, quantity: number): Cart {
        const newItems = this.items.map(oldItem => {
            if (oldItem.id === itemId) {
                return { ...oldItem, quantity: quantity };
            } else {
                return oldItem;
            }
        });
return new Cart(newItems);
    }
private calculateTotalPrice(items: CartItem[]): TotalPrice {
        return +items
            .reduce((accumulator, item) => accumulator + item.quantity * item.price, 0)
            .toFixed(2);
    }
private calculateTotalItems(items: CartItem[]): TotalItems {
        return +items.reduce((accumulator, item) => accumulator + item.quantity, 0);
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the entities are simple, with properties of primitive types, but a real example where there were validations we could have Entities and Value Objects defined as classes and with factory methods where the validation is performed. We use Either to return the errors or the result.

Boundaries

The boundaries are the abstractions of the adapters, for example, in Hexagonal Architecture they are called ports. They are defined in the layer of the use cases in the domain and indicate how we are going to communicate with the adapters.

For example, to communicate with the data layer we use the repository pattern.

export interface ProductRepository {
    get(filter: string): Promise<Either<DataError, Product[]>>;
}
Enter fullscreen mode Exit fullscreen mode

Data Layer

The data layer is where the adapters are found and an adapter is responsible to transform the information between the domain and external systems.

External systems may be a web service, a database, etc…

In this simple example, I’m using the same entities that represent the product, shopping cart, and cart items between the presentation, domain, and data layers.

In real applications, is common to have a different data structure for each layer or even to have Data Transfer Objects (DTOs) to pass data between layers.

In this example, we have repositories that return data stored in memory.

const products = [
  ...
];
export class ProductInMemoryRepository implements ProductRepository {
    get(filter: string): Promise<Either<DataError, Product[]>> {
        return new Promise((resolve, _reject) => {
            setTimeout(() => {
                try {
                    if (filter) {
                        const filteredProducts = products.filter((p: Product) => {
                            return p.title.toLowerCase().includes(filter.toLowerCase());
                        });
resolve(Either.right(filteredProducts));
                    } else {
                        resolve(Either.right(products));
                    }
                } catch (error) {
                    resolve(Either.left(error));
                }
            }, 100);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

The important thing is to understand that the repository is an adapter and that its abstraction or port is defined in the domain, so the traditional direction of the dependency is inverted.

Dependency inversion

This is the most important part of Clean Architecture, the domain should not have any dependency on external layers, in this way it is uncoupled and it will be easier to replace an adapter with another in the future or even for testing purposes.

In this way, if we replace the adapter implementation with one that invokes a web service, the domain is not affected and therefore we are hiding implementation details.

Presentation Layer — Adapters

The adapters of the presentation layer are the last reuse part of our core package and It’s where we hook the UI React or Vue layers.

These adapters are also reusable between the two versions of the app, they are intermediaries between the UI components and the domain layer.

They contain the presentation logic, deciding what information is shown, what should be visible, etc…

The state management is performed by this layer and does not depend on React or Vue.

There are different presentation patterns that we can use. In this case, I am using the BLoC Pattern because it fits very well with declarative frameworks such as React and Vue.

BLoC Pattern

If you want to delve into the BLoC pattern, I recommend that you read this article.

As I discussed in that article, when you use BLoC with Clean Architecture, it makes more sense to call them PLoC, Presentation Logic Component. So in this example, they are named this way.

Let’s see the shopping cart example:

export class CartPloc extends Ploc<CartState> {
    constructor(
        private getCartUseCase: GetCartUseCase,
        private addProductToCartUseCase: AddProductToCartUseCase,
        private removeItemFromCartUseCase: RemoveItemFromCartUseCase,
        private editQuantityOfCartItemUseCase: EditQuantityOfCartItemUseCase
    ) {
        super(cartInitialState);
        this.loadCart();
    }
closeCart() {
        this.changeState({ ...this.state, open: false });
    }
openCart() {
        this.changeState({ ...this.state, open: true });
    }
removeCartItem(item: CartItemState) {
        this.removeItemFromCartUseCase
            .execute(item.id)
            .then(cart => this.changeState(this.mapToUpdatedState(cart)));
    }
editQuantityCartItem(item: CartItemState, quantity: number) {
        this.editQuantityOfCartItemUseCase
            .execute(item.id, quantity)
            .then(cart => this.changeState(this.mapToUpdatedState(cart)));
    }
addProductToCart(product: Product) {
        this.addProductToCartUseCase
            .execute(product)
            .then(cart => this.changeState(this.mapToUpdatedState(cart)));
    }
private loadCart() {
        this.getCartUseCase
            .execute()
            .then(cart => this.changeState(this.mapToUpdatedState(cart)))
            .catch(() =>
                this.changeState({
                    kind: "ErrorCartState",
                    error: "An error has ocurred loading products",
                    open: this.state.open,
                })
            );
    }
mapToUpdatedState(cart: Cart): CartState {
        const formatOptions = { style: "currency", currency: "EUR" };
return {
            kind: "UpdatedCartState",
            open: this.state.open,
            totalItems: cart.totalItems,
            totalPrice: cart.totalPrice.toLocaleString("es-ES", formatOptions),
            items: cart.items.map(cartItem => {
                return {
                    id: cartItem.id,
                    image: cartItem.image,
                    title: cartItem.title,
                    price: cartItem.price.toLocaleString("es-ES", formatOptions),
                    quantity: cartItem.quantity,
                };
            }),
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

The base class of all PLoCs is responsible for storing the state and notifying when it changes.

type Subscription<S> = (state: S) => void;
export abstract class Ploc<S> {
    private internalState: S;
    private listeners: Subscription<S>[] = [];
constructor(initalState: S) {
        this.internalState = initalState;
    }
public get state(): S {
        return this.internalState;
    }
changeState(state: S) {
        this.internalState = state;
if (this.listeners.length > 0) {
            this.listeners.forEach(listener => listener(this.state));
        }
    }
subscribe(listener: Subscription<S>) {
        this.listeners.push(listener);
    }
unsubscribe(listener: Subscription<S>) {
        const index = this.listeners.indexOf(listener);
        if (index > -1) {
            this.listeners.splice(index, 1);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

All the information that the UI component needs must be interpreted from the state, elements to render in a table or list, but also if something should be visible or not, such as the shopping cart, the loading, or an error to show.

export interface CommonCartState {
    open: boolean;
}
export interface LoadingCartState {
    kind: "LoadingCartState";
}
export interface UpdatedCartState {
    kind: "UpdatedCartState";
    items: Array<CartItemState>;
    totalPrice: string;
    totalItems: number;
}
export interface ErrorCartState {
    kind: "ErrorCartState";
    error: string;
}
export type CartState = (LoadingCartState | UpdatedCartState | ErrorCartState) & CommonCartState;
export interface CartItemState {
    id: string;
    image: string;
    title: string;
    price: string;
    quantity: number;
}
export const cartInitialState: CartState = {
    kind: "LoadingCartState",
    open: false,
};
Enter fullscreen mode Exit fullscreen mode

In this case through union types of typescript, we can more securely and functionally model our state using sum algebraic data types.

This way of modeling is less prone to errors because you indicate that a very clear form that the state has 3 main possibilities:

  • Loading information
  • An error has occurred
  • Updated data

Presentation Layer — UI

In this layer is where are the components and everything related to React or Vue such as components, hooks, applications, etc.

The components are very simple and light because they are free to manage any type of logic or state management, this is the responsibility of each of the layers in the core package.

React App

In react we will have the components that render our list of products, the app bar with the number of products in the cart, and the product cart rendered as a Sidebar.

Let’s see the example of the component that renders the content of the cart.

import React from "react";
import { makeStyles, Theme } from "@material-ui/core/styles";
import { List, Divider, Box, Typography, CircularProgress } from "@material-ui/core";
import CartContentItem from "./CartContentItem";
import { CartItemState } from "@frontend-clean-architecture/core";
import { useCartPloc } from "../app/App";
import { usePlocState } from "../common/usePlocState";
const useStyles = makeStyles((theme: Theme) => ({
    totalPriceContainer: {
        display: "flex",
        alignItems: "center",
        padding: theme.spacing(1, 0),
        justifyContent: "space-around",
    },
    itemsContainer: {
        display: "flex",
        alignItems: "center",
        padding: theme.spacing(1, 0),
        justifyContent: "space-around",
        minHeight: 150,
    },
    itemsList: {
        overflow: "scroll",
    },
    infoContainer: {
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        height: "100vh",
    },
}));
const CartContent: React.FC = () => {
    const classes = useStyles();
    const ploc = useCartPloc();
    const state = usePlocState(ploc);
const cartItems = (items: CartItemState[]) => (
        <List className={classes.itemsList}>
            {items.map((item, index) => (
                <CartContentItem key={index} cartItem={item} />
            ))}
        </List>
    );
const emptyCartItems = () => (
        <React.Fragment>
            <Typography variant="h6" component="h2">
                Empty Cart :(
            </Typography>
        </React.Fragment>
    );
switch (state.kind) {
        case "LoadingCartState": {
            return (
                <div className={classes.infoContainer}>
                    <CircularProgress />
                </div>
            );
        }
        case "ErrorCartState": {
            return (
                <div className={classes.infoContainer}>
                    <Typography display="inline" variant="h5" component="h2">
                        {state.error}
                    </Typography>
                </div>
            );
        }
        case "UpdatedCartState": {
            return (
                <React.Fragment>
                    <Box flexDirection="column" className={classes.itemsContainer}>
                        {state.items.length > 0 ? cartItems(state.items) : emptyCartItems()}
                    </Box>
                    <Divider />
                    <Box flexDirection="row" className={classes.totalPriceContainer}>
                        <Typography variant="h6" component="h2">
                            Total Price
                        </Typography>
                        <Typography variant="h6" component="h2">
                            {state.totalPrice}
                        </Typography>
                    </Box>
                </React.Fragment>
            );
        }
    }
};
export default CartContent;
Enter fullscreen mode Exit fullscreen mode

Hooks

Using Clean Architecture, hooks are not used? Yes, they are used, but for what is strictly necessary.

The state will not be managed with hooks, the side effects are not triggered from hooks, this is the responsibility of the PloCs in the core package.

But we will use them to store the final state of the component that its PloC returns to us and we will use them to share context between components or react to the change of state that the PloC returns to us.

Let’s see how the usePLocState hook that we used in the component is defined:

export function usePlocState<S>(ploc: Ploc<S>) {
    const [state, setState] = useState(ploc.state);
useEffect(() => {
        const stateSubscription = (state: S) => {
            setState(state);
        };
ploc.subscribe(stateSubscription);
return () => ploc.unsubscribe(stateSubscription);
    }, [ploc]);
return state;
}
Enter fullscreen mode Exit fullscreen mode

This custom hook is in charge of subscribing to the PloC state changes and storing the final state.

Vue App

In Vue, we will also have the same components as in the React version.

Now let’s see the component that renders the content of the shopping cart in the Vue version:

<template>
    <div id="info-container" v-if="state.kind === 'LoadingCartState'">
        <ProgressSpinner />
    </div>
    <div id="info-container" v-if="state.kind === 'ErrorCartState'">Error</div>
    <div id="items-container" v-if="state.kind === 'UpdatedCartState'">
        <div v-if="state.items.length > 0" style="overflow: scroll">
            <div v-for="item in state.items" v-bind:key="item.id">
                <CartContenttItem v-bind="item" />
            </div>
        </div>
        <h2 v-if="state.items.length === 0">Empty Cart :(</h2>
    </div>
    <Divider />
    <div id="total-price-container">
        <h3>Total Price</h3>
        <h3>{{ state.totalPrice }}</h3>
    </div>
</template>
<script lang="ts">
import { defineComponent, inject } from "vue";
import { CartPloc } from "@frontend-clean-architecture/core";
import { usePlocState } from "../common/usePlocState";
import CartContenttItem from "./CartContenttItem.vue";
export default defineComponent({
    components: {
        CartContenttItem,
    },
    setup() {
        const ploc = inject<CartPloc>("cartPloc") as CartPloc;
        const state = usePlocState(ploc);
return { state };
    },
});
</script>
<style scoped>
#info-container {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100vh;
}
#items-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: 150px;
    justify-content: space-around;
}
#total-price-container {
    display: flex;
    align-items: center;
    padding: 8px 0px;
    justify-content: space-around;
}
</style>
Enter fullscreen mode Exit fullscreen mode

As you can see, it looks a lot like the React version using composition API.

Composition API

In the Vue version we will also have hooks, such as the one that manages the subscription to changes in the PLoC state:

import { Ploc } from "@frontend-clean-architecture/core";
import { DeepReadonly, onMounted, onUnmounted, readonly, Ref, ref } from "vue";
export function usePlocState<S>(ploc: Ploc<S>): DeepReadonly<Ref<S>> {
    const state = ref(ploc.state) as Ref<S>;
const stateSubscription = (newState: S) => {
        state.value = newState;
    };
onMounted(() => {
        ploc.subscribe(stateSubscription);
    });
onUnmounted(() => {
        ploc.unsubscribe(stateSubscription);
    });
return readonly(state);
}
Enter fullscreen mode Exit fullscreen mode

Dependency Injection

From the React and Vue app, we have to create or reuse the PloC structure for each component: use cases and repositories.

If these concepts were defined in the core package, the part responsible for creating them may be in the core package as well.

This time I am using the Service Locator pattern statically:

function provideProductsPloc(): ProductsPloc {
    const productRepository = new ProductInMemoryRepository();
    const getProductsUseCase = new GetProductsUseCase(productRepository);
    const productsPloc = new ProductsPloc(getProductsUseCase);
return productsPloc;
}
function provideCartPloc(): CartPloc {
    const cartRepository = new CartInMemoryRepository();
    const getCartUseCase = new GetCartUseCase(cartRepository);
    const addProductToCartUseCase = new AddProductToCartUseCase(cartRepository);
    const removeItemFromCartUseCase = new RemoveItemFromCartUseCase(cartRepository);
    const editQuantityOfCartItemUseCase = new EditQuantityOfCartItemUseCase(cartRepository);
    const cartPloc = new CartPloc(
        getCartUseCase,
        addProductToCartUseCase,
        removeItemFromCartUseCase,
        editQuantityOfCartItemUseCase
    );
return cartPloc;
}
export const dependenciesLocator = {
    provideProductsPloc,
    provideCartPloc,
};
Enter fullscreen mode Exit fullscreen mode

We could also use a dynamic Service Locator together with Composition Root or a dependency injection library.

In the React app, there is a global state that must be shared, it is the shopping cart. Therefore CartPloc, which is the one who manages this state, must be shared and accessible by all components.

React

In React we solve this using createContext and a custom hook using useContext.

export function createContext<T>() {
    const context = React.createContext<T | undefined>(undefined);
function useContext() {
        const ctx = React.useContext(context);
        if (!ctx) throw new Error("context must be inside a Provider with a value");
        return ctx;
    }
    return [context, useContext] as const;
}
const [blocContext, usePloc] = createContext<CartPloc>();
export const useCartPloc = usePloc;
const App: React.FC = () => {
    return (
        <blocContext.Provider value={dependenciesLocator.provideCartPloc()}>
            <MyAppBar />
            <ProductList />
            <CartDrawer />
        </blocContext.Provider>
    );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

Using the custom useCartPloc we have access from any component to this PloC and its state.

Vue App

In Vue, we solve this by using the provide feature.

<template>
    <div id="app">
        <MyAppBar />
        <ProductList searchTerm="Element" />
        <CartSidebar />
    </div>
</template>
<script lang="ts">
import { dependenciesLocator } from "@frontend-clean-architecture/core";
import { defineComponent } from "vue";
import MyAppBar from "./appbar/MyAppBar.vue";
import ProductList from "./products/ProductList.vue";
import CartSidebar from "./cart/CartSidebar.vue";
export default defineComponent({
    name: "App",
    components: {
        ProductList,
        MyAppBar,
        CartSidebar,
    },
    provide: {
        cartPloc: dependenciesLocator.provideCartPloc(),
    },
});
</script>
Enter fullscreen mode Exit fullscreen mode

Later from any component, we have access to the PLoC and its state using:

const cartPloc = inject <CartPloc> (cartPloc) as CartPloc;
Enter fullscreen mode Exit fullscreen mode

Source code

The source code can be found here: frontend-clean-architecture.

Related Articles And Resources

Conclusions

In this article, we have seen a Clean Architecture implementation on the front-end.

We have a version of React and Vue app reusing as much code as possible between the two and placing it in a core package.

With this exercise of having a core package with all the logic uncoupled from the framework, we can appreciate the power that Clean Architecture can offer us on the front-end.

Organizing the project as a monorepo and having a core package has been necessary for this example, but it is not necessary when developing an app either React or Vue.

However, it is an interesting exercise to force you to uncouple from the UI framework as it can sometimes be difficult to see that you are coupling, especially at the beginning.

Top comments (81)

Collapse
 
bitclaw profile image
Daniel Chavez

The article title seems a little clickbaity for my taste. Even the Spanish one, same thing. From the title, you gather that the article is about NOT using VueJS/React on the front-end .

Collapse
 
gerostenko profile image
Galina Erostenko

That’s what tricked me into reading this article. 🤷🏻‍♀️

Collapse
 
chuddyjoachim profile image
Joachim Chikezie

Same ☝️.

Thread Thread
 
jwizerd profile image
Jeremiah

I'm so glad I was "click-baited" into reading this article. This article, is one of the best frontend architecture articles I've read in a long time.

Collapse
 
peerreynders profile image
peerreynders

On Using Clean Architecture to Decouple Client Side Architecture from React.js or Vue.js

Similarly: A different approach to frontend architecture

Collapse
 
pvfigueiredo profile image
Paulo Figueiredo

That's true. When I read the title, I thought it was about not using VueJS or ReactJS.

Collapse
 
ruyili profile image
Ruyi Li • Edited

same, also clicked because of the title. just seems like a translation error though, i don't think the author had any bad intentions.

Collapse
 
noamgu profile image
noamgu

+1

Collapse
 
andersonplima profile image
Anderson P. de Lima

I disagree since "moving away from" is different from "moving away from using".

Collapse
 
aabidsofi profile image
aabidsofi19

i searched for it .. love you for such a beautiful article . gonna skim through your whole blog today :)

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

I'm joining a meeting and could read few parts so I may come back later but now and just to clarify Angular is a Framework, also it is Vue, but not React or Preact which are Libs, there are so many differences (from the engineering point of view) that is not even ok to try to compare them 😅

A Framework makes you work on the way this framework was conceived while a lib let you choose which parts to use and how, how to structure your project and so on (that's also the reason why React is the TOP1 in loved, used and demanded)

Collapse
 
fabiorizzelloomnia profile image
fabio-rizzello-omnia

Well if you think about it React forces you to write applications in a determined way too.
It's just less opinionated on some topics but still gives you a way to do things, the React way. Just saying the thing you have to pass props, never change props, and component will re-render automatically on changed props(or state) without having to manually tell the component what changed, that's already enough to enforce you a way to develop.
Just because React is less opinionated on things like global storage, router, i18n, forms etc.. It doesn't mean it's not a framework

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

But it's not, and it's not my opinion. React and Preact are not framewors and it's a fact. It's like saying Node is a framework or a language when Node is only a javascript runtime environment so in order to speak consistently and concisely, we need to categorise and qualify things like they are.

In computer programming, a software framework is an abstraction in which software, providing generic functionality, can be selectively changed by additional user-written code, thus providing application-specific software. It provides a standard way to build and deploy applications and is a universal, reusable software environment that provides particular functionality as part of a larger software platform to facilitate the development of software applications, products and solutions. Software frameworks may include support programs, compilers, code libraries, toolsets, and application programming interfaces (APIs) that bring together all the different components to enable development of a project or system.

Frameworks have key distinguishing features that separate them from normal libraries:

  • inversion of control: In a framework, unlike in libraries or in standard user applications, the overall program's flow of control is not dictated by the caller, but by the framework. This is usually achieved with the Template Method Pattern.
  • default behaviour: This can be provided with the invariant methods of the Template Method Pattern in an abstract class which is provided by the framework.
  • extensibility: A user can extend the framework – usually by selective overriding – or programmers can add specialized user code to provide specific functionality. This is usually achieved by a hook method in a subclass that overrides a template method in the superclass.
  • non-modifiable framework code: The framework code, in general, is not supposed to be modified, while accepting user-implemented extensions. In other words, users can extend the framework, but cannot modify its code.

By the other hand, a software library is a suite of data and programming code that is used to develop software programs and applications. It is designed to assist both the programmer and the programming language compiler in building and executing software. It generally consists of pre-written code, classes, procedures, scripts, configuration data and more. Typically, a developer might manually add a software library to a program to achieve more functionality or to automate a process without writing code for it.

That being said, from the different options we have for the front-end, i'll pick 5 and they are categorised like this:

  • Angular and Vue are frameworks
  • React and Preact are libs
  • Svelte is a compiler.

Hope it helps you understand the differences :)

Thread Thread
 
rockson_30 profile image
Rockson • Edited

Answering from non-work account
That's a really good answer. Yes even tho React has some sort of DI you can't really say it's extensible

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

When you import a lib just to achieve a goal you usually fit into this approach, otherwise you can either choose another one (and fit into it) or write down your approach and publish it as the new lib to do the same others do but on a different manner, then your lib will need to pass 2 filters:

  1. Community (if it solves a real issue, it does it on a simple (no suggar needed) way, it's buggless, covers all possibilities that any project could need, a medium-big project is built with that as example of capabilities and it's well documented, you can get some fans and stars on github).
  2. Us current engineers, tech leads and so will eventually take a look at it if the community growth enough to find devs who knows that lib or to share with the team to solve a current situation for a new or existing project. If you simply changed the way of doing things by stubbornness instead trying to understand how the things work and why they work this way, it'll simply be ditched off and it'll die along the other hundreds of js libs released every month that does not fit for real solutions.

I can understand your reasons to avoid using a lib or framework "as is" and in the end you can agree with me that is a try to avoid tech debt in a project, not less not more.

Then there reasons that counters this reinvented wheel. If you use react millions of devs out there will be available to work within your project in few hours, if you need higher performance and less included libs, you can simply pick preact and every react dev will be skilled to work with that as well.

If you create your own approach and build a project or even a company with that, the end result is that the world keeps rolling and new tech will appear anyway to solve other problems you don't even know about -yet- or to automate things you didn't even mind -yet- so people will like to use this "latest tech" (We know "latest" mean that it appeared 4-5 or 7 years ago and now it's popular and everyone works with that) so even if you figure out how to trick some dev to work in your company or project, they will eventually fly away to another company at the first sight of working on a non standard dinosaur. This will put you in need to increase the salary for your devs and new hires which is also costly and at this point, the life-cycle of your App will be near the end and needs a tech refresh regardless of your initial effort.

The end questions would be then, Is it worth? what do you prefer?

Thread Thread
 
xurxodev profile image
Jorge Sánchez Fernández

Hi, thanks for your comment.

Everything has advantages and disadvantages but in long-term applications I think it makes more sense to uncouple and in the future it will be appreciated in my experience.

Regards

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇

Bet you don't pay the salaries so! 😁

 
jianwu profile image
jianwu

It's not worth to argue if it's a framework or library? There's no clear boundary. This is also not the main point of this article. This is article is about decoupling your core logic from third party code. No matter you call it a framework or library, so that in case you have to migrate to a different library or framework, you don't have to rewrite all your core logic. For the front end world, it's more important as it evolves so fast. Don't expect something you love today will survive for long time.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

Please, I know we live in an opinionated world where people believe what see in tik-tok and so but IT is a science, thus is well categorised. Before being ignorant in front of everyone and especially after the previous explanation make the simple search "react" in Google and you'll see:

React – A JavaScript library for building user interfaces.

Then search "Angular" and you will be able to read the following:

"Angular is a TypeScript-based free and open-source web application framework led by the Angular Team at Google..."

if your opinion is that React is a framework you must try to reach React devs and ask them to change the category of it's Library.
Maybe they can explain what I tried before on a way you can understand it.

So NO, you cannot use React as a framework because it's not a framework, the same way you cannot use Angular as library because it's not a library.
You cannot use a car to travel from europe to america because it's not a plane the same way you cannot use a plane for your commitment to work because it's not a car, even they are both vehicles.

Collapse
 
jbigorra profile image
Juan Bigorra

To the people getting triggered because of the "framework vs lib" discussion. Plus some considerations in regards of the article ;)

Both (React and Vuejs) are considered a "Delivery Mechanism" from the clean architecture point of view, so following that idea both fall into the same category, no matter if one is a framework and the other a library. Both are an abstraction on top of the DOM to facilitate building user interfaces. That's one of the important parts of the article together with trying to decouple/extracting important logic from it, so your code doesn't depend on any lib/framework that much. So, discussing if it's a framework or not is a bit pointless in my opinion.

I think one point to discuss is, if taking the time to code like this makes sense ? or at least to this high degree of decoupling. Because part of the idea is that you could easily interchange the delivery mechanism if you needed to. But in real life, how often does this happens? Which company/startup would be successful enough to last that many years where you would be in need of using a different lib/framework?

Don't get me wrong, this approach makes you write more testable code which is always a good thing and definitely is an approach that should/could be used when developing back-end systems, and personally I like it and try to stick to it as much as possible (it will always depend on the team though).

Collapse
 
shnydercom profile image
Jonathan Schneider

Which company/startup would be successful enough to last that many years where you would be in need of using a different lib/framework?

basically any company that's survived not fully digitizing until now - which is a large part of enterprise IT, manufacturing, banks and insurances. Startups are only a fraction of the market for User Interfaces. UIs can go into products facing customers or facing internal users. Internal promoters of a digitization project will easily get their budget turned down if the UI can't survive for a couple of years at least, and that means salvaging as much as possible from past projects

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

If you read all the comments about you'll probably find the point on the discussion avobe but I'll expand my boundaries to this one :D

I agree with you that being in need to change the lib/fw for the frontend would take years or decades. I.e. Spring that was released in 2002, Ruby on Rails in 2004, Symfony in 2005 and they are still being used widely. But let's think we've an Angular2 framework and that Google stop maintaining it like they did with tones of tech they released in the past ;)

  • or whatever you want)-

In the matter of discussion we can introduce a workaround used for migrating monoliths into services (front end side) that also fits well for migrating modern frontend stacks.
You simply pick a feature, isolate it, build a new JS app with that -stand alone- and embed it into any element of your current view where you want to show that and connect it to the global state (if any).
Now you have an entire Angular app but a feature is provided with a different new hypothetical lib.

This is usually a bit easier to handle if you've a lib than if you've a framework and by the other hand the major part of logic on the frontend is related to conditional rendering of elements and having a 1st layer of security in the shape of form validation, which is easy to understand, document and replicate elsewhere. On top of that there's the fact of having functionalities write down in JS and picking an hypothetical JS lib for the "new frontend", then you'll be able to simply copy-paste your preferred methods/functions and refactor them with the "current future ES version" if you want to (js is retro compatible so it may not be even a need).

This is also easy with a lib where you call methods and just the ones that this lib provide than migrating a ng-if statement because it will need to be rewritten according to the new framework or lib.

At the end you'll have a good modern "new" application with the current stack, methodologies etc.... part by part, feature by feature, view by view... whatever is better depending on your base architecture (usually with the current stack would probably be component by component).

So it's this kind of decoupling really needed? Isn't it a patch to make the job of the devs more difficult to get in return... almost nothing? Wouldn't be better to design a good architecture from the beginning to make this process described above as easier as possible and nothing more?

Collapse
 
itswillt profile image
Will T.

It's a really nice article. However, I'd like to have some complaints/suggestions:

  • I see that most of the code snippets in this article are not indented properly, which makes it really hard to read. They also seem to use 4-space indentation, so they are needlessly longer than necessary.
  • The dependency injection pattern is nice, but it will make it impossible for you to implement code splitting in the future because you've already imported all of the code into the dependency injector.
Collapse
 
tiptronic profile image
Andy Fuchs • Edited

I could not more disagree on spacing. Although it's always a personal preference - I prefer 4-chars spacing, as it makes the code much better readable

Collapse
 
joeschr profile image
JoeSchr

Love it. So rare to see somebody discuss this beyond beginner/intermediate level topics like architecture with frontend UI!

Where were you 3 years ago?! ;)

Collapse
 
xurxodev profile image
Jorge Sánchez Fernández

Thanks!

Collapse
 
vietphamh profile image
Viet Pham • Edited

This is an awesome article to me although the title made me confused a little bit at first. Honestly, I really want to learn more from your mentioned links in it, but they're all in Spanish. I think it would be a great help to the readers who want to learn more about your article if you could help to translate them to English. I'm willing to read them as soon as possible I know them translated.

Once again, thank you for the great job!

 
tiptronic profile image
Andy Fuchs

I solve this by using a larger display ;) and I doubt 2-space indentatioin lets you see 'more code on each line' in ~99% of all cases. While 4-space indentation let's you much better see where functions/declarations start/end and what belongs together (no matter how long lines are).

Collapse
 
efpage profile image
Eckehard

Maybe we should take into account that the world is constantly changing. If we look back 10 years, the browser ecosystem has significantly changed, so I think we will see some changes on the framework side too. There are constantly new ideas trending.

A good software architecture should make us some kind of independent to changes of the framework. Otherwise you are constantly rewriting all your code over and over. I´m not sure if this is possible if you use React or Vue? I assume, if some Post-Vue-Framework is trending some day, people will start totally from scratch again.

Collapse
 
ozzythegiant profile image
Oziel Perez

Correct me if I'm wrong but one particular downside I'm noticing from this is that if you write your components to use a state from a store or from some type of provider, now the component is coupled to that feature. Almost all your components should be written in such a way that they don't depend on anything. Only a top level component such as a page or template should handle fetching/changing state or retrieval of API data, but at the same time, ensure that you don't have too deep of a component tree that you need to pass state several levels down

Collapse
 
xurxodev profile image
Jorge Sánchez Fernández

Hi, thanks for your comment.

I think creating an agnostic of the app state component only has sense if this one is used in several contexts.

For example for a library of components as a table with advanced functionalities that will be used in several apps or a dropdown used in multiple places in your app.

But components (pages or not pages) used in a unique context I think that is not a problem.

Regards

 
joelbonetr profile image
JoelBonetR 🥇

The key difference between the Library and Framework related to this article is something known as inversion of control (as I wrote above).

Let's explain inversion of control in detail:

When you import a library such react, you have to call the specific methods or functions of your choice and it's up to you when and where to call the Library, so you are in charge of the flow.

On the other hand, a framework itself makes a call to your code and provide you with some space to write down details. In other words, when using a framework, your framework is in charge of flow.

Having a library and having a framework are completely different levels of coupling just for this reason. If you want to change React with Preact for example you simply change one for another and update your method calls (related to the lib) for the ones of the new lib, test, fix and you're done. You cannot do that with a FrameWork because your code depends on the specific framework flow and way-to-do.

Thread Thread
 
rockson_30 profile image
Rockson • Edited

Don't get me wrong I'm just interested on the subject.
Your argument about being able to replace the calls seems a bit off, I mean Preact emulates exactly the same calls of React, maybe does things differently, but if it there was an Angular "emulator" you would be able to do the same. In Angular you are importing @Component, @Injectable, @Module etc, if you override them you're able to "emulate" Angular behavior as with Preact.
Even in Angular you decide what to import, what to use.
Angular just have more tools, it's batteries included, so they made it possible to extend it and to achieve some common tasks through "configurations". But that's it, it just has more tools by default.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

Your notion of the role of IoC is correct. However not all frameworks are application frameworks.

React is not an application framework but React is a framework.

When you import a library such react, you have to call the specific methods or functions of your choice and it's up to you when and where to call the Library, so you are in charge of the flow.

For a React component to get a chance to call React, React has to call the component first. The only time that isn't the case is in this one line of code:

ReactDOM.render(<App />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

And even that isn't just a library call - it's the entry point so React can "play the role of the main program in coordinating and sequencing rendering activity" and behave as a framework.

React is a (view component) framework

Your React Components (user code) are always called by React - that is IoC in action - used exactly in the manner of a framework.

Looking into it further - Redux maintainer Mark Erikson astutely observed

… or the way I like to put it:

  • React is your application (component-centric)
  • React is not your application (app-centric)

In Kent C. Dodds's Application State Management with React - React is a state management library largely focuses on the React is your application (component-centric) style.

Given that React is used for state management, React is the backbone and the skeleton of the application and all functionality and capability tends to become deeply coupled with the React-ness of the overall client application. At this point React might as well be your application framework (and many developers use it in this fashion) - the missing bits and pieces are simply pulled in from the React ecosystem and these "pieces" tend to be specific to React (e.g. while other frameworks may offer hook-like services , hooks written for React will likely not work with other frameworks).

I argue that the React is your application (component-centric) style does not scale. It may work for smaller applications and has an attractive time-to-initial-success (TTIS) but due to the inherent internal coupling it may be more difficult to maintain over the long run. In a way the tools that are being used to implement the solution dominate the client architecture rather than the problem that is being solved.

Contrast that with Michel Weststrate's UI As An Afterthought which advocates a React is not your application (app-centric) style:

Initially, design your state, stores, processes as if you were building a CLI, not a web app

most components will be dumb

Here the application is built around the state management solution - Mobx in this particular case - and React's responsibilities are deliberately severely curtailed. Taken to the extreme the client application would eschew integration libraries like react-redux or mobx-react-lite and instead define an application specific adaptor for the React side to access "the application" which then turns around to use whatever state management solution was actually chosen. In terms of the Hexagonal Architecture the application logic lives in the core while both React and the state management tool is on the "outside" being connected to the application logic via adaptors whose interfaces are dictated by the application logic.

Note: the above article is advocating one particular flavour of React is not your application (app-centric).

But even in the case of React is not your application (app-centric) React is still calling the shots as it remains in firm control of the main/UI thread - i.e. the application logic only gets control when React calls a component via IoC. The primary benefit of this style is that it reduces the coupling between React and "the application" (and perhaps even the state management tool).

In effect React is not your application (app-centric) is the modern version of the Segregated DOM a concept which goes back to The Humble Dialog Box.

To wrestle control from React (while still using React) it is necessary to move "the application" into a web worker - example: React + Redux + Comlink = Off-main-thread.

The point is that it actually takes significant work and discipline to avoid React acting as your application's framework.


One issue that tends to rear it's head is that some application capabilities benefit from running on the main/UI thread. One solution is to "actorize" the application. That way it's simple to move the functionality back to the main thread should the need arise:

An actor is essentially a message processor (not an object or a component) which maintains it's own autonomous state. It doesn't share anything and only runs when it has a message to process; it processes any one message as quickly as possible while sending messages to other actors or even spawning new ones.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇

A lib deals with one or more tasks. React is a group of libraries, take a look at your package.json. In most project there are at least

  • react
  • react-dom
  • react-scripts

and there are more, of course.

If you get a library to control your application flow it will do simply this, and this not means it's a framework.
Have you tried to import only a single lib from react and build the rest by your own on the top of that?

The thing is that you simply can.

In order to get some kinda like an actual framework you need a group of libraries (some has nothing to do with react itself such those for state management).

Here it goes a metaphor:

You can go to a scrapper, put some car parts in the same place on a random position and you can say "I've a car", well, eventually, with some work and using certain patterns and order you'll have a car made of isolated pieces but at the starting point you don't have a car, and depending on what you want to do with those parts and the parts you really need to accomplish your goal, you'll never end up with a car.

Thread Thread
 
peerreynders profile image
peerreynders

If you get a library to control your application flow it will do simply this, and this not means it's a framework.

As I've already cited in my gist:

Martin Fowler: InversionOfControl (2005-Jun-26)

Inversion of Control is a key part of what makes a framework different to a library. A library is essentially a set of functions that you can call, these days usually organized into classes. Each call does some work and returns control to the client.

A framework embodies some abstract design, with more behavior built in. In order to use it you need to insert your behavior into various places in the framework either by subclassing or by plugging in your own classes. The framework's code then calls your code at these points.

The litmus test:

  • If your code calls it, it's a library.
  • If it calls your code, it's a framework.

Your code only starts the DOM renderer - at which point your code gives up control and your components are called by React to specialize the VDOM rendering behaviour - so React is a framework.

When the behaviour of the implementation contradicts the documentation then it's time to disregard the documentation.

Thread Thread
 
rockson_30 profile image
Rockson • Edited

I mean, you can consider react-dom the actual interpreter for react, but anyway we are talking about React as a whole. Of course if you start splitting its pieces it becomes less of a framework. So is angular, if you take a single element from it like the rxstore well its not a framework on his own

Just because the react team decided to separate react and react-dom in two different dependencies and allowed people to write their own "interpreter", it doesn't mean it's not a framework.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

While Angular is a complete solution (a framework), React only takes care of rendering the view and making sure that the view is in sync with the state. That's all react does, nothing less, nothing more.

If you want React to become a framework (it is not possible by itself as discussed above) you'll need other libs to compose an actual framework-like library compound that matches all the requirements of being considered as a framework, shipped together -and maintained- as a single agnostic set shipped as a base solution where to build new projects on.

@peerreynders the first call to the component is done by you inside a react render function, it's not handled by react out of the box nor even has a default required structure.
You can call componentDidMount() or not (same as other methods), you can call render() without having to call other specific method as requirement, you can define and manage your state at your own way...
You can even create your component inside the same app directory tree that does not fit into react and push it right in between the other components at the time you want (of course without using React), because react is a lib and you enforce the usage of a lib wherever you want and not vice-versa.

If you use react features (rendering the view and making sure that the view is in sync with the state) which are defined and controlled by yourself (with the React API) you simply added reactive functionality to your App using React lib.
If your App is completely based into react way to work it was your architectural decision same that happen when you use Redux -which enforces you to apply Reducer design pattern using functional programming paradigm-, then you can say Redux is a framework but again, it is not, it's intended for state management only and it's a lib.

YouTube:

Thread Thread
 
peerreynders profile image
peerreynders • Edited

That's all react does, nothing less, nothing more.

You are simply making a case that React isn't an application framework. Just because React isn't an application framework isn't evidence that it's just a library.

If you want React to become a framework

When I mean application framework I state application framework. Framework is not synonymous with application framework.

the first call to the component is done by you inside a react render function, it's not handled by react out of the box nor even has a default required structure.

ReactDOM.render(<App />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

is

ReactDOM.render( 
  React.createElement(App, null), 
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

You're handing the component tree over to the React renderer via the name/reference to the function or class that represents the root of your component tree:

React here is the component tree that represents my application, could you please run my application for me …

… exactly like one does for a framework. Given that Preact doesn't have a native renderer it's even clearer

  import { h, render } from 'preact';

  // Create your app
  const app = h('h1', null, 'Hello World!');

  render(app, document.body);
Enter fullscreen mode Exit fullscreen mode

With a library:

  • The application code is primarily run from the JavaScript event loop and the application code calls the library only temporarily relinquishing control to the library. When the library completes its task it returns full control control back to the application without leaving a trace of itself on the call stack, microtask queue or task queue (within reason).

With a framework:

  • The application code is run by the framework. Once the framework is started, it runs until the application finally exits while in the meantime orchestrating most of the application activity.

A React application is run by React - not the JS event loop - React (for the most part) sits between the application and the JS event loop.

By the nature in which it operates React is a framework and not a mere library.

Now some people claim that is doesn't matter whether React is a framework or a library - but it does. Lots of sources claim that React is "unopinionated".

It's less opinionated than Angular but that doesn't mean React is unopinionated. As it operates as a framework it creates considerable design pressure towards the "React is your Application" architectural style (which isn't in your long term interest; I argue that React's pit of success herds you towards this style) - and that is opinionated. Going with a "React is not your Application" style (which this article advocates) is considerably more work largely due to React being opinionated.


If you are using react-redux you are still practicing "React is your Application". To move to "React is not your Application" you would put Redux behind an application specific façade.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇

"You are simply making a case that React isn't an application framework. Just because React isn't an application framework isn't evidence that it's just a library."
I think I explained all key differences between a framework and a lib already, you can't simply stick to a given implementation or a single key feature and say "hey, this does not fit 100% with the given description/explanation".

The react-dom is a package that provides DOM-specific methods that can be used at the top level of your app and as an escape hatch to get outside of the React model if you need to. Most of your components should not need to use this module.

When you call

ReactDOM.render(, document.getElementById("root"));

Here you are saying, hey ReactDOM, please RENDER this component in this container.

YOU are calling the package method passing the required stuff because you added React-DOM for a specific feature, if you don't like nor want React-DOM approach you can write down your own code instead and use it along with React (which does not include React-DOM by default) or simply avoid using it.
Again is your decision whether to use it, where and how (as long as you use it according to the reference if you do, of course).

You can add more or less packages or libs into an application to extend functionality (that's what a lib does) and define -or not- your app structure depending on a lib or not.

Does this seem like inversion of control to you?

Do you really think it enforces you to do the things on a single way? Even JSX is optional and not required to use React!
You can also wrap an imperative UI into a declarative component, and vice versa.

The control over scheduling would be harder if the user directly compose views with a “push” based paradigm common in some variations of Functional Reactive Programming.

To bring reactive, some popular libraries implement the “push” approach functionality where computations are performed when the new data is available. React, however, sticks to the “pull” approach where computations can be delayed until necessary.

This means that your components (either user-written or platform specific) are declarative functions that returns a description about what needs to be done.
Then you pass this description to React and React will call this component whenever is necessary. It's just a matter of the architecture design behind it.
At the end, you are defining what needs to be done, how and where. React simply adds the when -generically speaking- while you keep the control over the process with some methods such componentDidMount

React is not a generic data processing library. It is a library for building user interfaces.

Whenever you like it or not there need to be a deep understanding of both programming paradigms and design patterns to fully understand that in deep.
Extrapolate a single thing in favour to this counter-culture is just populism that hurt -like any populism does- the community, specially those who can keep this in mind while learning and so having a hard time trying to understand the underlying tech -which is quite good and strict into it's decisions- specially design decisions.

Thread Thread
 
peerreynders profile image
peerreynders

I think I explained all key differences between a framework and a lib already.

You informed me of your opinion and you keep saying "framework" when you actually mean "application framework". While all "application frameworks" are "frameworks" not all "frameworks" are "application frameworks".

The discussion is "library" vs "framework" - not "library" vs "application framework".

you can't simply stick to a given implementation or a single key feature

Sure I can and the TLDNR is (I repeat):

  • If your code calls it, it's a library.
  • If it calls your code, it's a framework.

This is a distillation from Designing Reusable Classes 1988 - 25 years before React:

The framework often plays the role of the main program in coordinating and sequencing application activity.

React calls user components giving them a chance to update component instance state and initiate application activities - ✔.

This inversion of control gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application.

React: "The components supplied by the user tailor the generic rendering algorithm in the framework for a particular application" - ✔.

A framework's application specific behavior is usually defined by adding methods to subclasses of one or more of its classes.

React: "A framework's application specific behavior is usually defined by composing components" which themselves are created by:

  • Using createReactClass which "subclasses" an internal React type via a specification object.
  • Extending React.Component explicitly.
  • Or representing a component via a function complying to a type specification (and supplying services like hooks) that can be used to access and mutate an internal React component instance while rendering its state.

So - ✔.

In 2005 (8 years before React) Martin Fowler summarized that as:

  • A library is essentially a set of functions that you can call.
  • The framework's code then calls your code

React components "plug into" React when the component tree is passed to React and React uses that tree for the remainder of the application's runtime to drive all application activity. So - ✔.

Now by this point it's contrasting a library as just a bunch of helper functions (that don't take control of application activities for the remainder of the application session) vs a framework which drives the application's activities for the lifetime of the application session.

The reason that distinction is important is because frameworks (engines of application activity) put a much larger design pressure on solution architecture than libraries. That is the core point of Architecture the Lost Years. The same way all Rails applications "looked all the same" (despite application specialization) back then, many React-based front end solutions "look the same" burying the all important "application-logic" as bits-and-pieces inside them rather than emphasizing and delineating the application-logic through architecture.

Here you are saying, hey ReactDOM, please RENDER this component in this container.

… for the remainder of the application session while the container still exists - that is an important detail. For a library it would just render it once and return - a re-render would require a separate invocation.

Does this seem like inversion of control to you?

Your components are invoked by React - that is IoC in action - React is calling your code when it deems fit.

Do you really think it enforces you to do the things on a single way?

Because React is acting as the driver of application activity it imposes design pressures to write the application in a certain way. Going against those design pressures is going to require additional effort - i.e. it's easier to just give in even if the long term consequences are detrimental (maintainability mostly).

You can also wrap an imperative UI into a declarative component, and vice versa.

It's only declarative in relation to its position in the component tree which is fine if it's just a visual, dumb component - but more often than not React components also absorb application logic and the application-logic has entirely different communication pathways than what the component tree can represent - typically application communication is just kitchen-sinked via Context.

It is a library for building user interfaces.

React drives application activity via it's components - a UI library is driven by the running application, i.e. a UI library is called whenever the UI needs to change.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

wow wow wow, hold your tights. You are blending different concepts into the same speech. Let's break things down as Uncle bob said in the past.

In the "Architecture, the lost years" he's talking about MVC (which is an architectural design pattern) but let's stay apart from that for a moment would you?

You say I'm talking about application framework, that's correct so let's see which kind of frameworks we can find:

  • Technology Framework
  • Business/Enterprise Framework
  • Application Framework
  • Component Framework

Technology Framework

Provides a standard, generic software foundation.
Java is a technology framework, JavaScript is other technology framework etc


Business/Enterprise Framework

A complete environment for developing and implementing a comprehensive information system. Enterprise frameworks provide pre-built applications, development tools for customising and integrating those applications to existing ones as well as developing new applications.
They may also provide a workflow component.
Frameworks such as ERP and CRM are available for business functions such as order entry, inventory and payroll (Comparison of accounting software), while frameworks exist for specific industries such as health services and insurance.


Application Framework

Is a set of common software routines that provides a foundation structure for developing an application (Explained in detail some comments above with all key features).


Component Framework

In essence, component-frameworks break the system down into variant "components" that represent abstract processing that the system supports. These components are generally thought of as "pluggable" in the sense that the system can be configured with a set of components to accomplish a particular task or set of tasks. In many systems, the components can dynamically swapped in and out. The framework, on the other hand, is the invariant "superstructure" that manages the components, providing services to each component that, when combined with the services/behaviours supplied by that component, create the net behaviour defined by that component in that framework.

The framework has the effect of decoupling or isolating the components from each other since it the component interactions are handled by the framework in a manner that is opaque to the components.
Note that this does not preclude any ability of the components to communicate with each other outside of the confines of the framework, they are only decoupled with respect to their interactions through the framework.

One of the key and arguably, defining, characteristics of a component-framework system is its "inversion of control" or "Hollywood Principle": "Don't call us, we'll call you!" Since the framework portion of the system represents the invariant aspects of the system that mediates the dynamics of the variant components, control of the system must be delegated to the framework because the components are only providing services to the framework and are unaware of how those services interact with each other. In other words, it is the framework that is "running the show", directing the calls to the component services, not the other way around. Inversion of control virtually eliminates flow control in the components and thus reduces code complexity in the components.


Ok now let's talk about IOC (inversion of control).

Inversion of control is a design pattern principle in software design, specially (but not exclusive) Object Oriented software, that helps to keep it -among other things- loosely coupled.

One of the methods of achieving it is through Dependency Injection.

This small example of a pseudo code getting data from a database and displaying it:

// design implementation 1

class MyScreenRenderer {

   constructor() {
       this.dbQuery = new MyDBClass();
   }
   
   const showDataOnScreen = () => {
       Screen.show(this.dbQuery.getMyData());
   }
   
}
// design implementation 2
class MyScreenRendererWithDependencyInjection {

   constructor(dbHandlerInjected) {
       this.dbQuery = dbHandlerInjected
   }
   
   const showDataOnScreen = () => {
       Screen.show(this.dbQuery.getMyData());
   }
   
}

In simple terms, the later class uses Dependency Injection pattern to get a instance of some database handler it needs, but, instead of creating it, and therefore getting coupled with a specific implementation, the instance gets injected into it. This pattern makes possible to our system to change databases freely without having to change the client class.

You can learn more in the great Martin Fowler Article.

Now, in the React components, props, etc, you can apply these concepts in order to make components independent and exchangeable.

The Render Props technique for example, allows you to create a component that gets the data and render it using another component that it knows nothing about, the renderer gets injected into it.

You being able to apply this design pattern principle does not mean that React forces you to apply it. It's your decision in software design but you can simply don't apply those concepts.

Just found this post about Use the Inversion of Control pattern with React, take a look at it, it may help you understand the insights.

Thread Thread
 
fabiorizzelloomnia profile image
fabio-rizzello-omnia • Edited

Angular doesn't force you to use dependency injection too. You can declare an instance, import, and use it. That's with any framework.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇

yup but Angular is a complete solution that covers all keys for being a framework while react does not.
There's a big difference son something being X and using something as X.
Fresh cheese can be used as salad dressing (adding other things such at least salad) while itself is not a dressing, even further you can add some syrup as dressing for fresh cheese.

That's what I'm trying to say, you can add a bunch of libs together putting React as your top lib and say "hey I got a framework" but react itself does not cover the requirements for being one and even react can be a lib to extend functionality inside another framework.

Thread Thread
 
fabiorizzelloomnia profile image
fabio-rizzello-omnia • Edited

Yes but tbh that looks more like the definition of non opinionated rather than library.
You are kinda comparing React to Angular's View Engine. Yes, Angular's View Engine is not a framework, it's used inside a framework. But when we talk about React we are talking about the combination of react + react-dom. If you take react alone it might be just like Angular's View Engine

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

React brings (optionally) so little list of things that I can even type a short list here:

  • React
  • React.Component
  • ReactDOM
  • ReactDOMServer
  • DOM Elements
  • SyntheticEvent
  • Test Utilities
  • Test Renderer

*From React API Doc


Angular includes many more tools -of course it does, it's an entire framework, a complete solution- that you can check at Angular API Doc


You may notice that React needs third party libs even for routing while as I said many times, Angular is an Application framework which uses components as base paradigm , being a framework means it's a complete solution. They of course can break angular down and ship separate libs as optional, but they didn't, they choose to offer a complete framework while React team decided to offer a lib which solves specific needs standardising them for anyone who uses React, maintaining it on it's little scope, instead going further and adding all the things that React currently lacks for being a framework.

Thread Thread
 
fabiorizzelloomnia profile image
fabio-rizzello-omnia • Edited

Yes but still, that's not the definition of a framework
Having many tools != Being a Framework
Having only 1 way to do things != Being a framework

"Being a framework means it's a complete solution"
Well, we might need to define "complete solution" here. Who said that a global store, http routing, animations etc... are mandatory for A framework?

As said before by others, being an application framework is a different thing.
If you want an application framework there are many in React land like nextjs and gatsby.

Still, nextjs that is labeled as an application framework doesn't have tools that angular has like animations, http, localization etc.
Nobody ever specified which tools you need to be an application framework.

Tbh I feel like the word framework is being misused everywhere these days, as if being a framework means:

  1. being opinionated
  2. having batteries included

But that's not the case, not every framework is like that

The only valid point i've seen for being framework is:
it calls you, so it's a framework

And that's the case of react + reactdom

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇

whatever man, send an email to React team with your complains to see if they change its own description. I can explain things for the community but this became a fight with your stubbornness instead.

Thread Thread
 
fabiorizzelloomnia profile image
fabio-rizzello-omnia • Edited

Looks like community doesn't agree with you either.
"send an email to React team with your complains to see if they change its own description"
Just because others say something, it doesn't mean it's true :D Not even creators

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

You stick to a single key feature that is IOC, after demonstrating that you can get IOC or not with react -depending on the implementation- then you say something about angular.
Read this again and do a checklist point per point and you'll end up with the same conclusion.

React is great not being a framework because it is so minimal and deals with so little that a developer will have to learn JavaScript anyway to deal with bigger problems that React JS cannot solve.

If you want to know more, please, ask Google "Why React is not a framework" and you can start a war on any place that you feel like with the same vague arguments.

You can start here for example.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

In the "Architecture, the lost years" he's talking about MVC (which is an architectural design pattern)

If you had actually watched the video rather than just glanced at the thumbnail you would have realized that MVC was only a minor aside around 29:00 and that he considers MVC a design pattern

MVC is not an architecture

and that he really doesn't like Web MVC.

Aside: MVC: misunderstood for 37 years

He's at a Ruby conference and he's talking about how Rails applications in general don't have an architecture that clearly manifests the nature of the application being implemented other than that they're implemented with Rails.

Similarly many React applications have a "same-ish" structure without a clear demarcation where the UI ends and the application(-logic) begins.

In 2011 SPAs weren't commonplace so client-side architecture wasn't a discussion point (though Martin Fowler brought up Segregated DOM in 2014).

In Make the Magic go away Robert C. Martin even calls rxJava (ReactiveX) a framework which most people would classify as a library. However going "all in" with RX has a framework-like effect on solution architecture.

so let's see which kind of frameworks we can find

Wrong frame.
What are the characteristics that most if not all frameworks share? The key one in Johnson and Foote:

The framework often plays the role of the main program in coordinating and sequencing application activity.

React is invoking your components for the lifetime of the application session so as far as your components are concerned React is their "main program". And the way many if not most React components are authored - i.e. components include a fragment of the application logic - React becomes the "main program" for the entire application.

That being said, lets look at your "Component Framework" write up:

These components are generally thought of as "pluggable" in the sense that the system can be configured with a set of components to accomplish a particular task or set of tasks.

Your React components plug into React

  • to render markup in a way that is specific to your application
  • to trigger application activities, which in turn may modify component instance state causing React to schedule them for re-render.

The framework, on the other hand, is the invariant "superstructure" that manages the components, providing services to each component that, when combined with the services/behaviours supplied by that component, create the net behaviour defined by that component in that framework.

React definitely manages React components, delivers events to the component instances via its synthetic event system, delivers props from owner component instances to the nested component, renders the DOM on behalf of the component based the generated ReactNode (a framework specific type) and provides application infrastructure like Context - all of which can be considered services for the components and all for the purpose of rendering the UI and running the application activities.

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
Enter fullscreen mode Exit fullscreen mode

The framework has the effect of decoupling or isolating the components from each other since it the component interactions are handled by the framework in a manner that is opaque to the components.

React components don't interact directly with one another. All interactions go through React facilitated by the ReactNode objects generated by the components.

In other words, it is the framework that is "running the show", directing the calls to the component services, not the other way around.

That's React. Calls the component to get the rendered ReactNode and to schedule the application effects.

Also "Hollywood Principle":

"Don't call us, we'll call you"_

Relevant when you are writing a class/component that must fit into the constraints of an existing framework. You implement the interfaces, you get registered. You get called when the time is right.

This requires a distinctly different way of thinking from that which is taught in introductory programming where the student dictates the flow of control.

That pretty much describes React components.

So React is a component framework.

Hence the title of my gist : React is a (view component) framework.

Now, in the React components, props, etc, you can apply these concepts in order to make components independent and exchangeable.

The objective of dependency-injecting a database is to treat the database as an implementation detail. What persistence storage product is ultimately used should not affect your application's ability to function.

Your UI layout and behaviour on the other hand is specific to the needs your application - and ideally it's the UI technology (e.g. React) that you want to treat as an implementation detail, so it's React that you want to be exchangeable. Given that isn't possible, the standard tactic is to make the UI as dumb as possible (The Humble Dialog Box) and inject the whole UI as a single plugin into the application. That isn't what typically happens with React applications - typically the application is injected into React via the component tree.

The "component exchange-ability" argument is largely associated with the component reuse and replace-ability objective which is an entirely different goal. And you have to specifically design for components to adhere to a specific interface (what props, which context) to be independent and exchangeable so that isn't a by default feature, you have to put in some extra effort in for that to happen. And after all that it's still only reusable within React.

You can learn more in the great Martin Fowler Article.

Read it many times since its publication in 2004. The component tree is injected into React before anything else can happen - so React is in control.

The Render Props technique for example, allows you to create a component that gets the data and render it using another component that it knows nothing about, the renderer gets injected into it

And it has never occurred to you that it's kind of strange that a "UI library" needs support for non-visual components? You are injecting parts of the application into the UI. This is just further evidence that React is a framework.

it may help you understand the insights.

The first thing that I notice is that it's React-centric. Other than that nothing new.

And that may point to the underlying problem - in the mind of some people React is the center of the universe because React is the center of their applications.

To properly decouple the application from React it better look something like:

import { h, render } from 'preact';
import { UI } from './components/ui.jsx';
import { Shop } from './components/shop.js';
import { makeShop } from './app/shop.js';

const shop = (() => {
  const client = {
    getBooks: () => fetch('/books.json').then(response => response.json())
  };

  return makeShop(client);
})();

render(
  <App />,
  document.getElementById('root')
);

function App(_props) {
  return (
    <Shop.Provider value={ shop }>
      <UI />
    </Shop.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

that is, your components access nothing but React or the shop application object to get the job done.

"hey I got a framework" but react itself does not cover the requirements for being one and even react can be a lib to extend functionality inside another framework.

And again you're talking about an "application framework".

send an email to React team with your complains to see if they change its own description

After 8 years they are not going to change anything.

Best guess: "JavaScript library" is being treated as a marketing (propaganda) term:

  • So that people don't have false expectations of a "batteries included" solution like Angular.
  • So that people buy into the whole "so simple, React can be learned in a weekend" meme.

You can start here for example.

That article is irrelevant as it commits the same mistake of equating "framework" with "application framework" (perhaps even a case of false equivalence) as the author does not convey an understanding of the fundamental characteristics of a general framework (vs library) that were established long before React was authored.

The distinction is important because IoC used by frameworks to run user code impacts how you structure an application - often not in a good way. If you're comfortable re-writing your client-side apps every two years or so or start to look for another job then you won't have to bear the consequences of not countering the bias that React can introduce into an application (i.e. React is opinionated - in its own way).

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

Yeah ok, watever. TLDR.
All people is wrong but you.
This is my last answer to this thread, sincerely, you make all of us loosing time from a biased pov or misunderstanding of concepts.

I let you this link from a person I appreciate within the industry for his way of explaining things describing just that concepts.

Thread Thread
 
peerreynders profile image
peerreynders

Please listen to your own advice.

Thread Thread
 
fabiorizzelloomnia profile image
fabio-rizzello-omnia

Best guess: "JavaScript library" is being treated as a marketing (propaganda) term:
So that people don't have false expectations of a "batteries included" solution like Angular.
So that people buy into the whole "so simple, React can be learned in a weekend" meme.

Exactly

Collapse
 
dashcraft profile image
Daniel Ashcraft

Okay, i've tried to think of ways to say this without coming off as harsh. I'm going to give pointed criticism and i promise my intent is not to "bash" you. I'm just going to give my honest take.

The Code

The code is bad, doesn't take advantage of implicit returns, hastily abstracts dependencies, makes decent integration testing fairly cumbersome because of the amount of dependency mocking you'd need to do. All in all, it would literally not pass code review, switch statements in a render function vs a hash map? This would get you paired with a senior for a few hours to fix this mess, it's that bad. It's a huge red flag. Too many code smells.

The Concept

The concept is a reasonable one for the backend where the "state" is usually colocated and composability isn't favored over inheritence. But it's just not great for the frontend. It's not composable, it's not easily maintained, it's not extensible, it's tightly coupled to your dependency setup, it's difficult to read, it's difficult to reason about, it's hard to test and therefore it should literally be avoided like the plague. It might actually be one of the worst architectures i've seen for frontend frameworks, including Anguler 1 (insider joke).

If your component/class isn't readable and you cannot discern what it's attempting to do in a short period of time, or extend it with confidence, in the tests/implementation, that it wouldn't break, it's not the solution to the problem.

This is the hastiest of hasty abstractions. Please never write code like this for the frontend. It's fun to see, but it's definitely not realistic or production worthy.

If this comes off as mean spirited, i promise that's not my intent, i'm trying to approach this in good faith.

Collapse
 
olegkusov profile image
Oleg Kusov

The idea is good. Thank you! It’s really first time when I see so decoupled from frameworks and libs domain logic

Collapse
 
reikrom profile image
Rei Krom
Thread Thread
 
rockson_30 profile image
Rockson

That's pretty much what I was saying about React. Tbh I don't really care about the discussion but if you think about it, the while props passing and reactive re-rendering inside React is already a kind of Inversion of control if you think about it.
That's why I said it can be considered a framework at some extend, maybe without betteries included? Less opinionated? Still a framework. But yeah if you compare it to the alternatives it might look less of a framework

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

google.com/search?q=react&rlz=1C1C...

google.com/search?q=angular&rlz=1C...

I'm tired of discussing things that are not suitable for opinions. Things are what they are, not more, not less. I already defined the differences many times here. Both libs and frameworks came to solve the same root concern, it's just the engineering behind and the result, being something devs interact with on a given manner what categorise them to be part of a group or another.

Extrapolation:
Again you can consider that a car is a bike and you could argue that both have wheels, both have an engine, both... but they are in the market to solve different needs, and a bike is a bike and a car is a car even having more similarities than notable differences -leaving the visuals apart- unless you enter into the engineering world.
i.e. if you're driving at 80km/h and you want to turn right you need to turn your steering wheel to the right on a car, while on a bike you'll need to turn your handlebars to the left because some physic laws that are not matter of discussion here.

The differences are enough to avoid some crazy to build a bike with steering wheel and say "hey, it's a car". People would think he's silly or crazy, right? The same happens on any science when people try to give an opinion on things that they don't fully understand.

Thread Thread
 
reikrom profile image
Rei Krom

React could be considered a framework to an extent same way a horse could be considered as an organic car. Or a car could be a mechanical horse. Hotdog can be a sandwich. More about it here => youtube.com/watch?v=O9ak89FwYeI

There are differences between a library and a framework hence vue, angular and react are defined appropriately by the authors.
You could do similar things with both of them, but due to the differences associated by the accepted terminology, your experience will vary.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇

This is a causal argument that puts the things out of it's own boundaries to deal confusion and intends to take acceptance for the people who don't want to contrast the information. It needs people to blind trust them and the thrust on them are linear according to the similarities that can be abstracted between both groups.

Even the similarities that we can observe on them, let's define the items:

  • Horse: a large plant-eating domesticated mammal with solid hoofs and a flowing mane and tail, used for riding, racing, and to carry and pull loads.
  • Car: a four-wheeled road vehicle that is powered by an engine and is able to carry a small number of people.

A car does not eat plants, also is not a mammal, has no wheels, is not a vehicle (it's currently an animal), not domesticated, it's not powered by engine and so on...

Let's define the items from the video:
Soup: a liquid dish, typically savoury and made by boiling meat, fish, or vegetables etc. in stock or water.
Is cereal any of this? Nope, cereals are grain and are put into milk (not water) without boiling or any other process.

It's a speech that usually seems funny to me, I don't consider causal arguments on other context but comedy. It's something that is not usually used in devs world but take care about politicians doing this to confront people :)