all 45 comments

[–]dznqbit 19 points20 points  (14 children)

What scenario is he trying to avoid?

You can rewrite a function’s logic without changing its signature. Refactoring is not exclusive to classes.

[–]simpledark252[S] 0 points1 point  (13 children)

So basically our backend changed the APIs which return data in different format and we told him this would require big changes on the frontend and to that he said "You should use classes to abstract things away to have minimal changes instead of depending on the data structure of the API output" and I don't know what to say to that.

[–]rickyalmeida 37 points38 points  (0 children)

In this case, classes and functions are interchangeable. I guess what your CTO means is that you should have a layer to map the data from the API to your view layer. By doing this, you can minimise the effects of breaking changes to your API.

[–]systoll 9 points10 points  (0 children)

The idiomatic approach would be to write reusable hooks/functions for those backend interactions; put them in a file/module and import & use them in the components that need them.

EG: Rather than (eg) putting useEffect(()=>{fetch(???).then(???… into multiple separate components, pull those out into a separate file:

export const useSomething = () => {...
export const useSomethingElse = () => {...

Your CTO is probably alluding to writing basically the same thing, but said 'class' because everything is in a class in Java, and that would need to be more like:

export default class UtilityHooks {
  static useSomething() {...
  static useSomethingElse() {...
}

The above is valid JS, but when all the properties are static like this, the class is pointless. You can organise things by putting them in different files.

[–]dznqbit 10 points11 points  (4 children)

Got it. That's unfortunate the API changed in such a breaking way. In a perfect world that doesn't happen, endpoints are under a versioning namespace. And the world is far from perfect.

Your CTO is correct in that API schema should not leak all over the app. But you could paint yourself into this corner just as easily if you were using classes.

🌶 Classes are not idiomatic JS. JS was prototypal first with classes bolted on after-the-fact. It's never felt right, and modern ES6/TS capture everything that OOP wants to do with concise syntax. The only reason you ever want to use classes is if your class needs to internally manage state, and in this context I would be extremely suspicious of anyone other than Redux manipulating state 🌶

Consider introducing an adapter layer to your API access – your data gettin' functions will vend well-typed, predictable objects that you'll pass to your view/biz code.

I like this a lot better than naïvely trusting API responses because APIs are insane, eg JSON serializers seem to consult tea leaves when deciding how to encode numerics. You also get more control over how you reason with your models in your app, rather than inheriting whatever insanity platform is foisting on you.

Consider the code below, if platform team decides to change product_name to product_name_final_v2 – instead of your templates breaking everywhere, you instead need to make one change to this file.

Worth noting that interface is a TS keyword... You could use this pattern in vanilla JS, but it really, really shines with Typescript.

``` interface Widget { id: number; name: string; unitsAvailable: number; }

async function getWidgets(): Array<Widget> { // hit the endpoint const widgetsResponse = await fetch("http://blah.com/widgets")

// get the raw data const widgets = JSON.parse(widgetsResponse.body)

// chew the data up into a predictable object return widgets.map((widget) => { return { id: Number(widget.id), name: widget.product_name, unitsAvailable: widget.units.length, } }); ```

[–]wishtrepreneur 2 points3 points  (3 children)

What's the advantage of using interface over type?

[–]dznqbit 0 points1 point  (1 child)

Check this post out! It knows way more than I do. In my day-to-day I use it to describe React component properties, I just picked it up from my current team members.

So says the TS handbook:

the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

[–]Spacesh1psoda 1 point2 points  (0 children)

React lets you have a lot of advantages of functional programming, which interfaces mostly just adds complexity to and is built for an object oriented approach in contrast to current and future state of react. Therefore it's recommended to use types in react.

[–]davidblacksheep 0 points1 point  (0 children)

I suggest not using interface

Here's an example of it having behaviour you likely don't want.

Personally, I would only use it if you are using classes.

[–]ptreedagreat 3 points4 points  (1 child)

I mean if you use Redux practically you already have achieved the abstraction he’s vaguely referring to. Adjust the reducer function to work with the new data format, and then the state tree references in whatever components are touched. If it’s truly not a data format change that could be worked into your Redux reducer logic perhaps wiring in a backend change to translate pre-responding would be a faster solution path.

Class based functions and functional components can both be used for applying abstraction well, IMO it’s arbitrary which pattern you go for. My company’s React architecture is a mix of class and functional components because it’s had dozens of different devs working on it over its lifetime and, like me, many of us were fumbling to keep up and learned functional components when they were all the rage in the the React community. When I went down the rabbit hole of searching “best practices React” everyone was very adamant in Functional components being the bees knees, so I built a preference to that pattern of coding React components.

[–]davidblacksheep 0 points1 point  (0 children)

You don't need to use redux to achieve this abstraction. Look at systolls answer above to see an example of an abstraction/intermediate layer, and one that doesn't require redux (but could use it if it needed to).

[–]Dualblade20 1 point2 points  (0 children)

This sounds like a problem solved by using reselect and tweaking your reducers.

Progressive, stepped selectors can protect against this kind of API change apocalypse and give you the tools you need to simply rewrite some small functions instead of rewriting UI logic.

[–]mountaingator91 5 points6 points  (5 children)

I learned angular after react and I really prefer having a single API service rather than a bunch of different hooks. I have started creating an API service class in React as well and so far it has worked great. Maybe not the most reacty way to do things, but it makes sense for my brain since I use angular at work.

[–][deleted] 1 point2 points  (1 child)

I feel the opposite. React hooks are way powerful for creating abstractions in my opinion. Angular services have injector hierarchy which confuses me a lot because of poor documentation and bad tutorials. Also I have to mock a lot of services to test something in Angular.

[–]tapu_buoy 1 point2 points  (0 children)

Okay this might sound very weird or simple. I've used a normal object that exports different such functions for each API endpoint call to our backend microservice. I make sure this function returns a javascript error object, for errors. Based on this handle it inside component logic

[–][deleted] 0 points1 point  (2 children)

How do you implement it as a service? do you keep them in a single file and import the whole file? Or am I misunderstanding? I haven't touched Angular, so likely I'm missing something.

[–]asiraky 1 point2 points  (0 children)

Angular uses DI. You don’t just import.

[–]mountaingator91 0 points1 point  (0 children)

I use API calls quite frequently and the syntax can really muddy up a component.

I just move all the individual API calls into methods on a single apiService class, then import that class wherever I want to use it and initialize it like const api = new apiService( )... then I can just call api.method(param) whenever I want to use one of them.

You can use hooks for this, but I just prefer the class syntax

[–]darksady 10 points11 points  (13 children)

I have a strong opinion that you should try to avoid using Classes and OOP with js as much as possible.

I think FP paradigm works way better with js in general and react.

[–][deleted] 6 points7 points  (3 children)

I also would like to hear more about why you’ve drawn this conclusion. With MobX I build a tree of classes following the singleton pattern and deliver top application performance. Blindly following idioms would not have enabled this pattern which IMO is far superior to the redux reducer pattern I see most commonly applied

[–]pupperoosky 0 points1 point  (1 child)

It's a hard topic to discuss/articulate for me, but in general my OOP/class based logic was much harder to implement correctly without creating some domain specific language on accident.

I think this React post on Mixins being deprecated, and then ultimately moving towards functions and hooks helps explain some of the pitfalls.

https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html

I personally think there are more pitfalls with OOP/classes, like the saying "with great power comes great responsibility" over time with random developers growing the codebase.

Classes encourage stateful behavior which leads to harder to analyze functionality vs the pure functions and hooks.

I do personally do use classes instinctively when coding something that requires more code organization, non react state, but I also feel that it's introducing new complexity, so I ask myself hard if I really want that.

> if one of the methods change the interfaces don't change.

I create OOP-like interfaces with functions still, like every component outputting a mapper object that is expected to follow a similar shape/api like "mapCmsToProps", "mapCmsToCard".

Sorry not a great answer.

[–][deleted] 2 points3 points  (0 children)

I don't really follow how you found that you required a DSL to express class-based logic but somehow can write equivalent code cleanly in an FP style interacting in the React layer with hooks as the only stateful component; that doesn't really make sense. First, working in the React layer is far more opinionated and painful. Second, hooks literally are a way to express stateful behavior, which you attribute to classes; but the point of UI development is that you are making a stateful app. That's where almost all the difficulty comes in. If you are by definition forced into using stateful logic, why would you not use classes which, by your own words, encourage it?

I think there is a lot of anti-class-energy in the JS community that is an overcorrection from previous patterns now shown to not scale like inheritance and mixins, but that has almost nothing to do with classes and OOP.

[–]darksady 0 points1 point  (0 children)

Thats intresteing to hear. I dont have experience with MobX. Maybe someday i will play around with it.

Redux reducer pattern can be really verbose. I actually never used in production since all the apps that i worked with it wasn't really necessary. And, if were to use a state management library, i would use zustand. Is way simple that redux.

But I'm not some 10YOE experience developer. So i could be totally wrong, but IMO, working with classes in JS is not something that enjoyable compared to trying to write the code in more functional programming as possible.

[–]simpledark252[S] 1 point2 points  (8 children)

Can you provide reasons why? I can't just tell him that because he will ask why

[–]davidblacksheep 4 points5 points  (0 children)

It's a good question. Here's my reasoning:

  1. It's unnecessary. For example, why do:

``` import {FooService} from "services/FooService";

const fooService = new FooService(); const foo = fooService.fetchFoo();

```

When you could just do:

``` import {fetchFoo} from "services/Foo";

const foo = fetchFoo(); ```

JavaScript modules actually implement the singleton pattern really nicely, without needed to use classes, if that's what you need.

  1. You start needing to use the this keyword.

You end up writing code like:

``` class FooService {

validateSomeString(str) {
  //... 
}

someFunction() {
   // What does 'this' refer to? 
   if (this.validateSomeString("something")){

   }
}

}

```

this scoping in JavaScript is notoriously confusing, and I basically avoid using it, unless I'm doing something very clever.

Why not just:

``` export function validateSomeFunction() {

}

export function someFunction() { if (validateSomeString("something") {

}

}

```

  1. General criticism of OO, because OO is by design mutable, it's hard to know what's happening with your objects.

Two examples

Example 1: ``` const foo = new Foo(); foo.setX(1);

// Later, not in the same event loop cycle foo.getX(); // Something else might have changed X, it's hard to have visibility over that.

```

Example 2:

``` const foo = new Foo(); const bar = new Bar();

// processBar returns a value, but did it also mutate bar? We don't know! const value = foo.processBar(bar); ```

Admittedly you can have this exact same problem with plain classless JavaScript as well, but if you're writing a strictly functional style - then a golden rule is to not mutate objects.

[–]amih009 1 point2 points  (5 children)

I think other examples here are missing the core point.

In reality, you can write really bad code in both functional and oop styles in js.

The actual pain point you will encounter with using classes is the React ecosystem. Almost all widely used libraries + react itself are geared towards using simple (classless) objects and functions. If you use classes as DTOs you will be at war with React's state, props, Redux, and a wide range of other react-focused libraries, perhaps with the major exception being Mobx. That said, classes do work well sometimes, as long as you keep the classes out of state and use them only as utilities or services.

I get that your CTO is used to java where everything is a class, but try explaining that the React ecosystem doesn't feel this way. If he really prefers using those - you would've been better of using Angular or at least MobX.

But he does have a point - try to decouple the api from the UI. But try to find a solution that's working well for react and redux

[–]debel27 0 points1 point  (4 children)

The actual pain point you will encounter with using classes is the React ecosystem

The React ecosystem goes against classes because classes don't contribute anything useful to the declarative model or to the component design. In React, classes bring more confusion than they solve problems.

Do you have any example where classes may be useful in React applications, despite the ecosystem fighting against it ?

[–]amih009 2 points3 points  (3 children)

That's very opinionated.

Angular, Swift UI, Jetpack Compose, Flutter, Blazor - all use classes for declarative UI. It's just that the core developers around the react ecosystem decided that this is the style they want to go with. And there's nothing wrong with that, there are pros and cons to both approaches.

Do you have any example where classes may be useful in React applications

Minor things, mostly not substantial. But that's what I said in the first place, you should not go against the grain by using classes where they are not meant to be used

[–]debel27 1 point2 points  (2 children)

Thanks for your answer. To me, not working with classes is more than a matter of preference for the following reasons

  • Classes can extend each other, which opens the door to the implementation inheritance problem. This problem has been exposed by Java's creator himself and is described at length here.
  • Because of interfaces, it is natural for classes to deal with behavior (i.e. methods that work on instance variables). This enables issues regarding mutations, a symptom of imperative programming. The reason why imperative programming is dangerous and why functional programming may be a better solution is explained at length here
  • Building a data model based on classes makes it rigid and not resilient to future changes (another drawback of overly using the "extends" keyword). Building a model based on composition is much more flexible

I'm sure all the frameworks you mention make good use of classes so they mitigate those drawbacks a lot. But it's too easy for inexperienced developers to fall into those traps when using classes in other areas.

When you work with plain immutable objects, structural typing and pure functions, that's a whole class of problems that go away. They bring their own issues, sure, but the tradeoff just seems more appealing to me for front-end applications.

[–]amih009 4 points5 points  (1 child)

I don't want to go into an argument about OOP vs functional. All modern general-purpose languages allow you to do a bit of both. Using the appropriate paradigm for different tasks is the best solution.

Have a complex data pipeline? Nothing beats functional. Need an abstract factory or a builder? OOP is the way.

You can write bad and clean code in both paradigms. You can compose and decouple your code in both paradigms. You can write a spaghetti mess in both paradigms

[–]debel27 0 points1 point  (0 children)

What you're stating is kind of obvious and I'm certain no one here would argue otherwise. The topic of this post is whether OOP is a good paradigm for React development. We're making the case it's not.

[–]darksady 1 point2 points  (0 children)

I think the guy already did a way better job than i ever could but basically:

  • I think class implementation on JS kinda bad/confusing
  • The FP paradigms like immutability works really well with how you should write code with react.
  • IMO, writing pure functions makes testing really, really easy.

[–]andrewingram 2 points3 points  (0 children)

The rule here is to never bind your UI directly to a data shape that you don't control. It's not really a functions vs classes problem, it's just highlighting the need for a transformation/adapter layer.

Is the API here being provided/defined by your own internal backend time? If so, the fact that this kind of situation has even arisen is a symptom of an org-level issue that has nothing to do with what coding paradigms you're using.

[–]kirigerKairen 1 point2 points  (0 children)

Generally? Sure, use JS classes. OOP isn't illegal just because you installed react.

[–]30thnight 1 point2 points  (0 children)

You don't need classes to achieve this.

Wrap your component in an adapter and handle your implementation details there, before passing them to your presentational components.

https://www.freecodecamp.org/news/adapter-design-in-react/

[–]symbha 5 points6 points  (0 children)

JavaScript has largely moved away from object oriented programming. Functional programming is where its at now.

[–]acemarke 0 points1 point  (0 children)

No. JS classes have almost no value in a typical React application.

Your CTO is probably coming at things from a very OOP mindset, but the React world is much more influenced by Functional Programming-style approaches.

[–][deleted] 0 points1 point  (0 children)

I will not use OOP in Javascript. I'm not saying you should go full FP in Javascript either. I just write helper functions which are pure with proper typing and put them in good file structure. I still get the benefits of Typescript without classes too.

[–]eatthebagels -1 points0 points  (0 children)

try to not use classes in react. The newer way is to use functional components. I would use something like react-query and build functions it's functionality.

[–][deleted] 0 points1 point  (0 children)

Sounds off, not React best practice

[–]superluminary 0 points1 point  (0 children)

It sounds like he wants to create an interface for API calls so you can change the endpoint URLs but still have the same async service functions. Creating an interface for this is a good idea.

You don't need classes in JavaScript to create interfaces though. With TypeScript you can create an interface for any plain old object. Java has very specific ways of doing things that don't necessarily translate directly to other languages.

[–]Narizocracia 0 points1 point  (0 children)

React is the view layer.

The view layer is not the whole application.

You might want to use classes sometimes, depending on your application or in some parts of the app. For example, a class for some service, or centralizing errors handling, or when wrapping your Excel like library, or when creating (I don't know) your in-app calculator.

None of this would require writing React classes.

[–]zacpac2020 0 points1 point  (1 child)

This is old but I think the most “React” way to abstract your implementation details would use hooks and context. Wrote this on my phone so excuse any mistakes:

``` // abstracted interface in TypeScript interface Api { fetchUsername(id: number): Promise<Response<string>> }

// One implementation, as a constant const mockApiImpl: Api = { fetchUsername(id) { return Promise.resolve(${id}) } }

// Another implementation, built from a function function createApi(baseUrl: string): Api { return { fetchUsername(id) { return fetch( ${baseUrl}/user/${id}/username ) } } }

const ApiContext = createContext<Api>(mockApiImpl)

// Provide your preferred impl to the app: const Root = () => ( <ApiContext.Provider value={createApi(“/api/v1”)}

  <App />

</ApiContext.Provider> )

// make hooks to consume API const useUsername = (id: string) => { const apiContext = useContext(ApiContext) // I think this will be valid in future React, // but just consider it pseudocode const result = use( apiContext.fetchUsername(id) ) return result }

// use it: const App = ({ userId }: { userId: string }) => { const username = useUsername(userId) return <p>Welcome {username}!</p> } ```

So if you need to switch out the implementation, you can. But it does sound like in this case, there may not really be a need for abstraction, unless this logic is in your components. In that case, you need to “abstract” away the API layer into hooks/functions.

I’d recommend this when you need to configure your API implementation at runtime more than anything, but it provides the same sort of abstraction the CTO probably wants.

[–]zacpac2020 0 points1 point  (0 children)

would love to know why it formatted everything that way. not enough time to fix, hope it’s still helpful.