DEV Community

Sam Magura
Sam Magura

Posted on • Updated on

Bad Habits of Mid-Level React Developers

If you're a mid-level React developer looking to become an advanced React developer, this post is for you!

I've been reviewing React code written by junior and mid-level developers on a daily basis for a couple of years now, and this post covers the most common mistakes I see. I'll be assuming you already know the basics of React and therefore won't be covering pitfalls like "don't mutate props or state".

Bad Habits

Each heading in this section is a bad habit that you should avoid!

I'll be using the classical example of a to-do list application to illustrate some of my points.

Duplicating state

There should be a single source of truth for each piece of state. If the same piece of information is stored in state twice, the two pieces of state can get out of sync. You can try writing code that synchronizes the two pieces of state, but this is an error prone band-aid rather than a solution.

Here's an example of duplicate state in the context of our to-do list app. We need to track the items on the to-do list as well as which ones have been checked off. You could store two arrays in state, with one array containing all of the to-dos and the other containing only the completed ones:

const [todos, setTodos] = useState<Todo[]>([])
const [completedTodos, setCompletedTodos] = useState<Todo[]>([])
Enter fullscreen mode Exit fullscreen mode

But this code is buggy at worst and smelly at best! Completed to-dos are stored in the state twice, so if the user edits the text content of a to-do and you only call setTodos, completedTodos now contains the old text which is incorrect!

There are a few ways to deduplicate your state. In this contrived example, you can simply add a completed boolean to the Todo type so that the completedTodos array is no longer necessary.

Underutilizing reducers

React has two built-in ways to store state: useState and useReducer. There are also countless libraries for managing global state, with Redux being the most popular. Since Redux handles all state updates through reducers, I'll be using the term "reducer" to refer to both useReducer reducers and Redux reducers.

useState is perfectly fine when state updates are simple. For example, you can useState to track whether a checkbox is checked, or to track the value of a text input.

That being said, when state updates become even slightly complex, you should be using a reducer. In particular, you should be using a reducer any time you are storing an array in state and the user can edit each item in the array. In the context of our to-do list app, you should definitely manage the array of to-dos using a reducer, whether that's via useReducer or Redux.

Reducers are beneficial because:

  • They provide a centralized place to define state transition logic.
  • They are extremely easy to unit test.
  • They move complex logic out of your components, resulting in simpler components.
  • They prevent state updates from being overwritten if two changes occur simultaneously. Passing a function to setState is another way to prevent this.
  • They enable performance optimizations since dispatch has a stable identity.
  • They let you write mutation-style code with Immer. You can use Immer with useState, but I don't think many people actually do this.

Not writing unit tests for the low-hanging fruit

Developers are busy people and writing automated tests can be time consuming. When deciding if you should write a test, ask yourself, "Will this test be impactful enough to justify the time I spent writing it?" When the answer is yes, write the test!

I find that mid-level React developers typically do not write tests, even when the test would take 5 minutes to write and have a medium or high impact! These situations are what I call the "low-hanging fruit" of testing. Test the low-hanging fruit!!!

In practice, this means writing unit tests for all "standalone" functions which contain non-trivial logic. By standalone, I mean pure functions which are defined outside of a React component.

Reducers are the perfect example of this! Any complex reducers in your codebase should have nearly 100% test coverage. I highly recommend developing complex reducers with Test-Driven Development. This means you'll write at least one test for each action handled by the reducer, and alternate between writing a test and writing the reducer logic that makes the test pass.

Underutilizing React.memo, useMemo, and useCallback

User interfaces powered by React can become laggy in many cases, especially when you pair frequent state updates with components that are expensive to render (React Select and FontAwesome, I'm looking at you.) The React DevTools are great for identifying render performance problems, either with the "Highlight updates when components render" checkbox or the profiler tab.

Your most powerful weapon in the fight against poor render performance is React.memo, which only rerenders the component if its props changed. The challenge here is ensuring that the props don't change on every render, in which case React.memo will do nothing. You will need to employ the useMemo and useCallback hooks to prevent this.

I like to proactively use React.memo, useMemo, and useCallback to prevent performance problems before they occur, but a reactive approach — i.e. waiting to make optimizations until a performance issue is identified — can work too.

Writing useEffects that run too often or not often enough

My only complaint with React Hooks is that useEffect is easy to misuse. To become an advanced React developer, you need to fully understand the behavior of useEffect and dependency arrays.

If you aren't using the React Hooks ESLint plugin, you can easily miss a dependency of your effect, resulting in an effect that does not run as often as it should. This one is easy to fix — just use the ESLint plugin and fix the warnings.

Once you do have every dependency listed in the dependency array, you may find that your effect runs too often. For example, the effect may run on every render and cause an infinite update loop. There's no "one size fits all" solution to this problem, so you'll need to analyze your specific situation to figure out what's wrong. I will say that, if your effect depends on a function, storing that function in a ref is a useful pattern. Like this:

const funcRef = useRef(func)

useEffect(() => {
    funcRef.current = func
})

useEffect(() => {
    // do some stuff and then call
    funcRef.current()
}, [/* ... */])
Enter fullscreen mode Exit fullscreen mode

Not considering usability

As a frontend developer, you should strive to be more than just a programmer. The best frontend developers are also experts on usability and web design, even if this isn't reflected in their job titles.

Usability simply refers to how easy it is to use an application. For example, how easy is it to add a new to-do to the list?

If you have the opportunity to perform usability testing with real users, that is awesome. Most of us don't have that luxury, so we have to design interfaces based on our intuition about what is user-friendly. A lot of this comes down to common sense and observing what works or doesn't work in the applications you use everyday.

Here's a few simple usability best practices that you can implement today:

  • Make sure clickable elements appear clickable. Moving your cursor over a clickable element should change the element's color slightly and cause the cursor to become a "pointing hand" i.e. cursor: pointer in CSS. Hover over a Bootstrap button to see these best practices in action.
  • Don't hide important UI elements. Imagine a to-do list app when there "X" button that deletes a to-do is invisible until you hover over that specific to-do. Some designers like how "clean" this is, but it requires the user to hunt around to figure out how to perform a basic action.
  • Use color to convey meaning. When displaying a form, use a bold color to draw attention to the submit button! If there's a button that permanently deletes something, it better be red! Check out Bootstrap's buttons and alerts to get a sense of this.

Not working towards mastery of CSS & web design

If you want to create beautiful UIs efficiently, you must master CSS and web design. I don't expect mid-level developers to immediately be able to create clean and user-friendly interfaces while still keeping their efficiency high. It takes time to learn the intricacies of CSS and build an intuition for what looks good. But you need to be working towards this and getting better over time!

It's hard to give specific tips on improving your styling skills, but here's one: master flexbox. While flexbox can be intimidating at first, it is a versatile and powerful tool that you can use to create virtually all of the layouts you'll need in everyday development.

That covers the bad habits! See if you are guilty of any of these and work on improving. Now I'll zoom out and discuss some big picture best practices that can improve your React codebases.

General Best Practices

Use TypeScript exclusively

Normal JavaScript is an okay language, but the lack type checking makes it a poor choice for anything but small hobby projects. Writing all of your code in TypeScript will massively increase the stability and maintainability of your application.

If TypeScript feels too complex to you, keep working at. Once you gain fluency, you'll be able to write TypeScript just as fast as you can write JavaScript now.

Use a data-fetching library

As I said in the "Bad Habits" section of this post, writing useEffects correctly is hard. This is especially true when you are using useEffect directly to load data from your backend's API. You will save yourself countless headaches by using a library which abstracts away the details of data fetching. My personal preference is React Query, though RTK Query, SWR, and Apollo are also great options.

Only use server rendering if you really need it

Server-side rendering (SSR) is one of the coolest features of React. It also adds a massive amount of complexity to your application. While frameworks like Next.js make SSR much easier, there is still unavoidable complexity that must be dealt with. If you need SSR for SEO or fast load times on mobile devices, by all means use it. But if you're writing a business application that does not have these requirements, please just use client-side rendering. You'll thank me later.

Colocate styles with components

An application's CSS can quickly become a sprawling mess that no one understands. Sass and other CSS preprocessors add a few nice-to-haves but still largely suffer from the same problems as vanilla CSS.

I believe styles should be scoped to individual React components, with the CSS colocated with the React code. I highly recommend reading Kent C. Dodds' excellent blog post on the benefits of colocation. Scoping CSS to individual components leads to component reuse as the primary method of sharing styles and prevents issues where styles are accidentally applied to the wrong elements.

You can implement component-scoped, colocated styles with the help of Emotion, styled-components, or CSS Modules, among other similar libraries. My personal preference is Emotion with the css prop.

Update 2022-04-15: Clarified my statement that you should "always" use a reducer when the state is an array.

Discussion (66)

Collapse
tbm206 profile image
Taha Ben Masaud

While I agree with most of the advice given, some are subjective and opinionated and do not necessary reflect universally accepted best practices.

For example, I prefer SASS/LESS over css-in-js. But again, this is my own subjective preference. Similarly, I prefer to use Elm, ReScript or PureScript if I'm overly concerned with the correctness of the app. Otherwise, (JS + tests) is better than TS for fast development and less overhead of maintaining a mountain of TS types that often leak bugs anyways.

Also, it would be better to zoom out and think why React.memo helps. Perhaps restructuring the react tree would make several instances of React.memo redundant.

I know I'm in the minority but I hate hooks because of all the pitfalls one needs to remember when using them, especially when there are nested 2 or more levels deep. Check out RefractJs; no need for hooks. HoCs are better because they're a universal pattern; not a library's API.

Collapse
natescode profile image
Nate • Edited on

Typescript isn't perfect but it gives you many tests for free. Why waste time writing tests you get for free? Tests are to test logic not usage.

Maybe JS + Tests is faster for junior devs that don't understand type systems. My interns are doing fine with it..

Plus the static typing helps document how to use components. My interns love it as it tells them when they miss a required attribute or what type it expects as a parameter.

Agree on Purescript / Elm etc.

Collapse
joelbonetr profile image
JoelBonetR

Self documenting code is a lie that I honestly thank the community was aware of since long time ago.

Thread Thread
natescode profile image
Nate • Edited on

Hardly. So you don't use Swagger UI?

Tools like Swagger and static typing don't replace all documentation of course. automatic documentation is extremely valuable.

Too many JavaScript only developers that haven't worked on large scale enterprise projects.

Collapse
kanishka profile image
Kanishka • Edited on

Otherwise, (JS + tests) is better than TS for fast development and less overhead of maintaining a mountain of TS types that often leak bugs anyways.

I have been thinking this as well. I would only want to use rescript or elm, or just js. Of those three options, I am slightly leaning towards js with tests because I don't have to deal with any special tooling. If elm gains more momentum, or rescript launches a huge framework developed in rescript, I would make those my default. I am disappointed that Elm hasn't won (yet?). (I very rarely build small front ends, so my opinions here don't have a lot to back them up).

I have also been thinking about leaving business object data as json (e.g. package.elm-lang.org/packages/1602...) instead of converting to records, when using elm or rescript, because I think most business objects will eventually want some user defined fields.

Collapse
tbm206 profile image
Taha Ben Masaud

I recently started a JS project and had trouble with conflicting versions of npm dependencies. It took me a day to balance the correct versions and still couldn't use the latest version if one of the core dependencies. At some point, I was very tempted to switch to Elm because dependency management is easier. I just didn't want to handle encoding/decoding of JSON; lazy 😂

Thread Thread
kanishka profile image
Kanishka

I would have just used an escape hatch like package.elm-lang.org/packages/1602...

Thread Thread
Sloan, the sloth mascot
Comment deleted
tbm206 profile image
Taha Ben Masaud

💯😂

Collapse
absynce profile image
Jared M. Smith

Elm is my go-to for a nice developer experience. For me it has "won".

I let my IDE (Atom/elmjutsu) generate decoders/encoders for me. You can use korban.net/elm/json2elm/ if your preferred editor doesn't support generating them. I don't recommend circumventing decoders.

I only decode/encode the data I need. Sometimes (usually) I want my model to look different that the data in the API, so I make tweaks for that. Decoding even helps me find flaws in my assumptions on data coming from the API too!

There's elm-graphql if that's an option for you.

Collapse
brense profile image
Rense Bakker

Css in js and typescript are best practices and every day more companies are joining the bandwagon asking for these skills. While I do personally also like to take a backseat in the bandwagon, css in js and typescript are quickly becoming industry standards (typescript already is) and you're going to hurt your career if you refuse to become proficient in these skills.

Collapse
tbm206 profile image
Taha Ben Masaud

Claiming css-in-js and TS are best practices is incorrect. However, claiming there's momentum behind these technologies is correct.

TS, in my opinion, is the new Java. It'll build a massive pile of horrible codebases that future developers will have to maintain. It's an opportunity as well as a curse 😅. Pure FP languages with clear FFI are better for guaranteeing the correctness of apps.

Thread Thread
brense profile image
Rense Bakker

I've worked extensively with existing codebases in both Java, Typescript and Javascript. Your claim that Typescript is the new Java is just a little bit ridiculous tbh... If you want to make such a crooked comparison, Typescript is more comparable to C# than Java. The pile of horrible codebases you talk about, I've seen them but they were all in Javascript.

Collapse
spock123 profile image
Lars Rye Jeppesen

css-in-js is horrible ,stop your self please.

Collapse
spock123 profile image
Lars Rye Jeppesen • Edited on

TailwindCSS is the shit. css-in-js is the worst and performs really badly.

Collapse
reactifystudio profile image
Reactify • Edited on

I agree with everything. However I still love hooks💯

Collapse
joelbonetr profile image
JoelBonetR • Edited on

God damn! using TS as "good practice"... The post was good since I read such a stupid thing.

I encourage you to learn JS properly instead.
We've tools in JS to use as we need them, it's not the most used programming language around the world just by chance.

facts

Do you want type checking? Simply add

// @ts-check

at the top of your file, VSCode will handle it for you in dev time[*1], all you need to do is to add JSDoc to your functions/methods and variables, which is one of the most important best practices: Document your code.

Do you want types and function/method inference? Again, JSDoc.

Better intellisense? Guess what... JSDoc.

Do you want to produce a maintainable code? Apply SOLID and KISS patterns.

Since we're talking about react, you should know about prop-types as well, more useful than type-checking if you take in mind the context of React[*2].

About data fetching libraries, there's fetch API by default in JS, that can handle ALL existent features that HTTP requests provide (moreover fetch API is coming to Node as well). Why adding an additional one?

One important (and real) good practice is to add the minimum amount of third party libraries into your project. This makes your project more robust, faster to build/deploy and usually more secure.[*3]

personal opinion

I find styled-components useful and probably the best option for several reasons (everything is a component, no unused CSS, much cleaner than tailwind, sass/scss capabilities by default, JSX inside styles definition, it can handle JS props (usefull for interactions that CSS alone can't handle and you can place it on a different file).

I like to have 3 files in each component folder, those are, as example:

myComponent.js
myComponent.styles.js
myComponent.utils.js

Which I guess are self-explanatory (ask me if you want detail).


[*1] Meaning that you don't need to add TS to your project and waiting the extra build time, it will work, let's say, in runtime, while you are developing instead. 0 time wasted and same type-checking than adding the huge TS lib to your project, isn't it amazing? 🤗

[*2]Most part of the logic should be in the backend. You should only handle interaction logic in the frontend, which makes easier to scope everything where it belongs so you can swap your backend or frontend without the hell of a code with heavy coupling.
It may seem that React will exist forever but people that is migrating from JQuery-UI maybe thank the same back those days 😂

[*3] By robustness meaning that you don't need to update your project each time a lib changes it's way-to-work, relying on language API (if it suits the project needs) is so, usually better.
By secure I mean that the probability that a third party deals to a security hole is higher than using the language API itself as a general rule of thumb.
I find faster to build/deploy as self-explanatory.

Collapse
mehyam profile image
MehYam

Major error in your post: you centered your "personal opinion" header instead of aligning it to the top.

Collapse
srmagura profile image
Sam Magura Author

@mehyam 😂😂😂

Collapse
joelbonetr profile image
JoelBonetR

It's an introduction 😆

Collapse
haaxor1689 profile image
Maroš Beťko

Learning JS properly as you said and using Ts are by no means exclusive. You write to not use Ts but then immediately suggest to use ts-check pragma. While JSDoc is powerful tool, it shouldn't be used for typechecking your code, this again is thanks to TS extracting some info from your js documentation and by no means is a solid solution for larger projects.

Also about the data fetching libraries, you clearly didn't understood what they are for. React Query isn't there to replace fetch, it uses fetch to smartly and effectively work with async data.

Collapse
joelbonetr profile image
JoelBonetR • Edited on
Learning JS properly as you said and using Ts are by no means exclusive.

Sure it's not, I totally agree with this. I'm not editing the previous comment to keep things as they are, I should have been avoided the word "instead" at the end or communicate it on a different manner.

You write to not use Ts but then immediately suggest to use ts-check pragma.

I said that that if you want type-checking for any reason you can simply use this instead loading the entire TS as dependency in your project.

While JSDoc is powerful tool, it shouldn't be used for typechecking your code

¿Why not? JSDoc annotations live in comments, rather than directly in syntax, which I prefer but it's totally opinionated, you can choose one or another or none of both and it will be Ok if it suits your project needs.

this again is thanks to TS extracting some info from your js documentation and by no means is a solid solution for larger projects. Also about the data fetching libraries, you clearly didn't understood what they are for. React Query isn't there to replace fetch, it uses fetch to smartly and effectively work with async data.

This post is about good practices, so my comment is targeted to avoid opinionated stuff into good practices rather than discussing the usefulness of a given library.

We need to take apart what we "like" or what we found "useful" or what it worked "well for us in a specific use-case" so we can differentiate what are actual good practices than what are our preferences.

Do you prefer TS for some reasons? It's OK, go for it. Do you prefer to avoid using TS? It's equally good, go ahead. The same applies to fetching libraries.

Is strong typing things good? Sure it can avoid runtime errors, but it's not the only way to reach the same.

Saying that TS or fetching libraries are "good practices" is just so out of the reality that it simply can't fit. By all means, best practices are not language specific, just for that reason adding TS or a given lib is just an opinion (to which you can agree or not, but that's not the point of discussion).

Thread Thread
brense profile image
Rense Bakker

The reason we can tell what is a best practice is because we can see Typescript winning massively in popularity. We can see code written in Typescript failing much less frequently in production. We can see junior developers learning much faster how to write proper testable code.

Javascript spawned all these cowboy devs who started their own religion of writing proper code in Javascript (with 1 follower per religion), because the language itself has no opinion about what proper code is. There's a reason why all those "tools" you mentioned earlier exist now. Typescript is the natural evolution of some of those tools.

And yes, best practices can definitely be language specific. For example its best practice to use proper indentation in Typescript, in Python your code simply doesnt work and in other languages like cobal appearantly its best practice to not use indentation at all :P

Thread Thread
joelbonetr profile image
JoelBonetR • Edited on

That script is looking good but then you realize that JSDoc exists sice the 90s and that Doc capabilities are in almost every language...
I'm always at the JS side here in Dev community for a good reason. You defend TS with personal preferences rather than analysing properly the project needs.

I would prefer, to use TS in a Node backend which provides business logic but I find a non-sense adding it to the auth service (which will simply validate tokens from Google IDP for example).
I would probably avoid adding TS to a microservice that aggregates 2 third parties (you'll not get strong typing in runtime anyway so all you need to do is a prop map, and the result will be the same with vanilla JS than with TS, probably you'll add some validation library like Joi).

So no, we can't tell that using TS is a good practice, as every tech, it's good where it has sense. I'm tired to tell that there's no single tech to rule them all, and that we are working in a Science field. Adding personal preferences to your projects harm them on a way or another. I.e.
Not using TS when you should makes the product less robust.
Adding TS where you shouldn't expands your dev times for any reason.

C'mon, you can find proper non-opinionated definitions on best practices around the Internet.

Thread Thread
brense profile image
Rense Bakker • Edited on

We were talking about React projects here. The Wikipedia page you link holds the definition of what best practices are. It does not keep an extensive list of all best practices used in the field, nor does it claim that anything not mentioned there is not a best practice.

"Coding best practices are a set of informal rules that the software development community employs to help improve software quality"

Improve software quality is what Typescript does, especially for React projects.

Thread Thread
joelbonetr profile image
JoelBonetR • Edited on

This is an opinion, not a definition. Assuming that TS improve the software quality we can extrapolate that and say that Java (which includes the "strong sides" of TS by default) improve software quality and by extension all Java code has quality.

I worked 4 years using Java and I'm confident to say that this is not true.
Strong typing and so gives your code robustness which is absolutelly not the same than quality.

SOLID principles, design patterns (when they fit), documentation, testing, good naming conventions etc are things that improve software quality and some of them also add robustness.

Moreover TS shines when dealing with complex logic (as it adds robustness, either be with ts-check pragma or adding TS into your project) and you are not supposed to add complex logic in the frontent (it adds coupling and makes hard to migrate to a new frontend technology when react dies in a future) so I would rather say "specially in complex Node services" instead "specially in React", where you will get interaction logic only.

Thread Thread
brense profile image
Rense Bakker

I'm just going to ignore the absurd comparison with Java (The way in which Typescript deals with type safety is completely different from Java) and only reply to the more sensible part of your reply. SOLID is great, especially the parts that carry over to functional programming, yes design patterns, documentation, testing, good naming conventions, completely agree and there is zero reason why you cant do this in Typescript. The second part of your reply... Thats not the point of Typescript, the point of Typescript is that I can trust that when a team member writes some code or some intern writes some code and after a while I have to make changes to that code, I'm not confronted with a big mess of different styles of coding and variable declarations that are changed all over the place, holding values that I cannot predict. Typescript has made my life a lot easier and less filled with fixing nonsense bugs and typos. If you want to dismiss that as a personal opinion, go right ahead lol

Thread Thread
joelbonetr profile image
JoelBonetR • Edited on

I know that difference between Java and TS, but making the assumption that TS always deals to a quality code is absurd in the same way.

I agree with you in most parts to be honest, it's just that, to add objectivity into the equation, you can get the same with JSDoc definitions and using ts-check pragma instead loading the entire TS as project dependency.

Again, I don't mean to say that using TS is bad or any other thing, it's just that fanboying a tech makes you think that it's good for everyone in every situation and for each use-case that you have to face and that's simply not true.

To get the correct tech stack for a project you need to check different metrics; the specific use-case/s to cover, the future planning, the human resources available (quantity, seniority, previous experience), the overall experience of the team on each tech stack and so on.

Then, in a perfect world, all projects would have unit tests, e2e tests, good documentation, an up-to-date confluence with the acceptance criteria to review before refactoring something and a huge etc of things.

The real life for most projects is that business needs go first and it usually means "We need this done by [DEADLINE]" which leads inevitably to cut time in some of those things, so you need to choose.
Do you prefer extended dev time by using TS? Do you prefer to have unit tests? Do you enforce documentation?

There are different approaches that lead you to -almost- the same result, for example, if you take the approach of "everything is a component" in React, using propTypes is more flexible than strong typing your code, plus you add explicit definition of what is optional and what is required and so on, just to add an incomplete example.

It's not all black or white, it's not a "good practice", it's not a use TS or not use TS, it's a tech stack definition and it will be the correct one or not depending on those different metrics I add before and it may be a question on "how much TS do you want to imply in your project" and for which reason.

I'm adding 2 use cases from some months ago just to illustrate.

Real-time chat app with Node + React using Websockets

and persisting data in a MySQL database.

It was meant to be integrated inside a different webapp as stand-alone webapp.

React code is just an entry point to authenticate the user in context + the chat component (divided in two, the place where you type and the place where you read). Around 200 lines of code as much, you just deal with strings.

Node backend it's just the event emmiter, the persister (to store) and the reader (to get previous chat history), around 150 lines of code as much if you put the files together, again you just deal with strings.

There is no logical benefit of adding TS. You can fantasize with things like "if the project grows then...", "if bla bla... then" but the reallity is that this project will remain as is for it's entire life.


Service to handle bussiness logic

Between a React frontend and a headless CMS.

  • Forecast of project minimum support time? 5 Years.
  • Forecast for project growth? Big.
  • Does this use-case fit for TS in the Node service? Sure it does! I wouldn't recommend doing it without TS.
  • Does this use-case needs to have TS in the React part (y/n)? No, it does not.
  • Are we about to using TS here? Let's see the rest of the metrics, oh, the team got experience with JS but few in TS, we are about to add only interaction logic to the frontend so we can cover the robustness with JSDoc, TS-pragma and prop-types.

That's the objective truth about this regarding the metrics, but you seem like the one dev that cries at the back saying "but we HAVE TO use TS" (usually just because he's used to it and as a personal preference) and can't give any good reason for it to Architecture and Management people.

Thread Thread
longebane profile image
Long Dao • Edited on

I don't know man, jsdocs, proptypes.. I see that too often as wishful thinking, because only one dogmatic guy of the entire team is keeping those up to date and consistent. Strict ts is more friendly for a mixed-level team because it puts less burden on Sr engineers during code review to always double check if "best practices" are adhered to (and reduces friction due to being code-blocked during the code feedback stage)

A codebase with outdated jsdoc/proptypes/etc brings a tear to my eye.

Thread Thread
joelbonetr profile image
JoelBonetR

You can use a linter to ensure those are present and validate ts-pragma as well, so you can't have an invalid definition

Thread Thread
brense profile image
Rense Bakker

Or you can not use all those things and just use one thing: Typescript :P

Thread Thread
joelbonetr profile image
JoelBonetR

Sure it's an option that I follow depending on the use-case :)

Collapse
nicoladj77 profile image
Nicola Peluchetti

I really don't agree with you on this one, writing jsDocs or PHPDocs is one of the biggest waste of time ever, in my opinion: when was the last time you read a useful comment in a jsDoc?
I think that all jsDocs do is either:

  • make your files bigger for no parctical reason
  • make you think twice before you write a function, because you are forced to put in the boilerplate comment To give some context, I am talking about agency level codebases, so for important clients but that will be used by not more than 40 developers in 5 years: I understand that WordPress needs proper documentation, it's a library, but code you write for clients needs to be clear, not to have boilerplate comments, so basically it just needs commenting when you are doing something bad. For me the golden standard should be to type hint everything, in PHP you get it for free, in JS you need typescript.
Collapse
joelbonetr profile image
JoelBonetR • Edited on

It's the first time in my life that I read something that "documenting your code is bad". You guys surprise me more every day 😆

Of course you need to think before writing a function, and as best practice you'll probably re-write this function splitting it in two or three because you forgot to apply the S on SOLID (single responsibility).

Remember that the first half of a developer job is to make the code work, the other half is to make it maintainable, readable, documented and tested.

Thread Thread
brense profile image
Rense Bakker

Yes, good code doesnt need documentation. Read Clean Code by Martin Robert. If your code needs documentation, its not easy to read/understand and is therefor bad code.

Thread Thread
joelbonetr profile image
JoelBonetR • Edited on

That describes a concept not a reality. The maximum perfection in a team would be to understand each other's code to the perfection, all with the same level/amount of knowledge and working together for years.

In a real life project working in a team, usually you'll get different expertise levels, there's people rotation and you'll have to maintain the code for long time.

I can code a function composition inside my code and the junior will come and say "bruh, WTF". If we can take the time and resources to raise the seniority to a given "minimum" level for the project it would be fine (I guess, never saw it IRL)..

But even that, there's no single pattern or best practice other than documenting your code that can release you from the burden of remembering everything in a future stage.

Let's say you work 5 years in a project and someone new comes to it. Understanding what it does in the big picture is a matter of reading function names (if the codebase is good).

i.e. getUserId(user) will obvious get the user ID, probably from a user object. If you use TS you can see that user ID is a number and that user param is a user object. That's all the knowledge you can take from the declarative part of the code.
Usually you'll see something like:

const getNewNickname = (user) => generateNickname(randomize(capitalize(concatNameAndSurname(user))));
Enter fullscreen mode Exit fullscreen mode

Adding doc would be something like:

/**
   * Generates a new random nickname based on the current user name and surname
   * @param {Object<User>} user 
   * @returns {string} Nickname
   */
  const getNewNickname = (user) => capitalize(generateNickname(randomize(concatNameAndSurname(user))));
Enter fullscreen mode Exit fullscreen mode

Isn't that beautiful?
Let's get a step further adding function composition:

const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
const getNewNickname = compose(capitalize, generateNickname, randomize, concatNameAndSurname, user);
Enter fullscreen mode Exit fullscreen mode

Can everyone predict what will that do exactly without having to think for some seconds? I can honestly say that a quarter of my current team wouldn't.

Let's add Doc to it:

 /**
 * Executes a sort of functions from right to left, passing the output to the next function
 * @param  {...any} fns functions
 * @returns {any} execution result
 */
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);

/**
 * Generates a new random nickname based on the current user name and surname
 * @type {string} nickname
 */
const getNewNickname = compose(capitalize, generateNickname, randomize, concatNameAndSurname, user);
Enter fullscreen mode Exit fullscreen mode

Isn't it better, faster and quickier?

So i've lost less than a minute to avoid others in the team not loosing a couple of them in a future.

And we just saw the declarative part, on the imperative part scoped in helpers and functions could be harder to catch up things quickly as they'll have procedimental code with conditions, loops and so on, and here is where doc shines most.

Thread Thread
brense profile image
Rense Bakker

Clean code is a concept that was born from a shit ton of practical experience... Uncle Bob has what? 40 years experience in the field? And it's not like he's alone, or that the concept of clean code wasnt adopted and put in to practice for the past 14 years...

You can claim all you want... No junior developer is going to understand what your JSDoc comments really mean, or know how to use it properly themselves. With typescript they have no choice... But more importantly, they don't have to care at all because Typescript does type inference automatically and it will just tell them what they're doing wrong if they try to assign the wrong type to a variable or function :B no understanding JSDoc required and a bunch of other tools and VS code plugins that you need... I have the complete opposite experience than what you claim... The junior devs I have encountered found Typescript very easy to learn and use.

JSDoc, like PHPDoc is archaic, from a century before we had modern type systems and stems from Javadoc which is even more archaic...

I agree, the example you give is a horrible obfuscation of logic that you should not do at all. If someone changes what is returned by one of the functions in your composition and doesnt update the JSDoc for it, Javascript is going to crash in runtime and I would love for you to let a junior dev try to find and fix that runtime error with your function composition. If you work with 10 devs, I guarantee atleast 5 of them are not going to care much about updating code comments when they're writing code. I much prefer devs who try to write clean code, instead of trying to explain their mess with JSDoc.

Thread Thread
joelbonetr profile image
JoelBonetR

So, to recap, you'll use TS always instead JS, isn't it?

Thread Thread
brense profile image
Rense Bakker

I am using JS since TS is just superset of JS, but yes, for typesafety and to avoid a million runtime errors everytime you forget something, I will use TS until something better comes along.

Thread Thread
joelbonetr profile image
JoelBonetR

So you took a decision based on other decisions you made before (learning TS and ignoring vanilla JS as much as possible).

“If the only tool you have is a hammer, you tend to see everything as a nail.”

Taking in mind that TS does not check types at runtime and also knowing the context where you are working on (in this case React), where runtime errors are mostly due to some API error (the backend guy forgot to add some property, the swagger says that in this contract you should get a string but instead you got an integer and so on) you'll face exactly the same amount of runtime errors either using one thing or another but hey, those are your projects, not mine.

Thread Thread
brense profile image
Rense Bakker

The amount of assumptions you make about my work experience with other languages shows how narrow minded you really are.

There are ways to avoid runtime errors from API changes, but i'm sure you have a very strong opinion about those tools as well and frankly I have come to the conclusion that its utterly pointless to discuss these things with you.

Collapse
kanishka profile image
Kanishka

In terms of compile time, ReScript is lightning fast, if you ever want to dabble in it. I am not suggesting switching to it, it's just an interesting thing to try if compile speed is something that bothers you about typescript.

Collapse
joelbonetr profile image
JoelBonetR

Just pointing out some things there 😆 Thanks for your comment, I'll check it out :)

Collapse
ohscee profile image
Vincent S.C.

I’m definitely no js purest (in fact I simply adore types and interfaces and abstract classes) but I think you’re absolutely right with your statements, especially concerning React.

The framework itself isn’t really ideal for especially complicated projects to begin with, and TS is just another build layer on top that isn’t really necessary if you’re comfortable with JSDoc.

I’m ALWAYS astonished that React documents don’t seem to bring up styled-components at all. I loathe how inflexible css-in-js is. It’s absolutely nonsensical if you’re building anything larger than a personal website or a series of detached components. Having a SASS or LESS is such a time saver, and really optimizes how you can use css

Collapse
brense profile image
Rense Bakker

Or instead of learning all your "tools in js", you could just learn Typescript properly because it has all those things built in :)

Collapse
joelbonetr profile image
JoelBonetR

Following the same logic you could just learn Kotlin and then transpile it into JS, as it has even more capabilities than TypeScript and you can also code native Android Applications, web services, gateways and so on with it running over the JVM which is better for complex computational tasks than JS is over Node.

Maybe you could just code in Rust and compile it to WebAssembly .

There's plenty of options if you really want to avoid digging deep in a programming language!
If you prefer to just scratch the surface, then you get tired for any reason and need a different challenge, go for it.

I'm pretty sure that more than half the people reading this didn't even know about JSDoc, I even got comments about how bad documenting your code is 😆

Thread Thread
brense profile image
Rense Bakker

If your code needs documentation to be understandable, its bad. Highly recommend reading Clean Code by Robert Martin. Sure you could invent your own language and be a solo dev cowboy and nobody will understand what the hell you're doing, or you could just use whats already there and appearantly works for a large chunck of the dev population. JSDoc doesnt work, thats why nobody uses it except a very small group of preachers. And yes, I highly recommend that you dig a little deeper into Typescript until you understand that its not comparable to Java.

Collapse
pelv profile image
Alex Benfaremo • Edited on

Talking about "// @ts-check" i don't know if you use the comment for some means but actually there is an options in the settings that enabled it by default for all javascript file.

Collapse
joelbonetr profile image
JoelBonetR

Adding the TS pragma makes it work on any team member VSCode without having to check that they have it enabled in their IDE, so it's a convenience while working in a team.

Thread Thread
pelv profile image
Alex Benfaremo

make sense :)

Collapse
spock123 profile image
Lars Rye Jeppesen

Wow I cannot believe you wrote this.

Collapse
kiranmarshall profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
kiranmarshall

God tier troll or ramblings of a madperson?

Collapse
ekeijl profile image
Edwin

Great article, I agree with most of these points. Not too sure about using reducers for just arrays, since they also add a bit of complexity and boilerplate themself. I would say it helps if you can identify unique actions in your component tree that affect multiple parts of state.

One thing I noticed is that people try to come up with their own solutions while React has a somewhat opinionated solution for that problem. This mostly boils down to them trying to optimize prematurely or storing calculated values in state (leading to the problems you describe) - they think a rerender would be too expensive based on their gut feeling instead of actually measuring it.

I'm not a great fan of unit testing React related code (the internal state of your app), but for specific business logic it could make sense. I think Kent C. Dodds is on to something with his testing trophy - mostly write integration tests so you can verify the most important use cases of your app and try to avoid testing the internals.

Also, using testing library (with Cypress) was an eye-opener for me. Multiple times I tried writing a test using querying methods that test the interface based on what the user sees (aria role, form label, aria label, text, etc), but noticed I couldn't because there were no proper labels on my elements. By writing the test, the accessibility of my app improved as well! Win win!

Collapse
srmagura profile image
Sam Magura Author • Edited on

Thanks Edwin. When I say "using reducers for arrays", I primarily am referring to the "I have an array of objects and the user can edit each object" use case. For a simple array of IDs, reducer is not really necessary.

I also love the testing trophy so I agree with you there!

@testing-library/cypress is an interesting topic... I have used React Testing Library and Cypress, just not together 😉

Collapse
vital_tech_results profile image
Vital Tech Results • Edited on

This is a very professionally written article that benefits the dev.to community.

What are your thoughts on TailwindCSS? I've grown to prefer Tailwind over styled components.

I agree on the importance of Typescript. I recently worked on a large site with over a million dollars in monthly sales and it uses .js only (not a single .tsx file). In this regard, what differentiates an opinion from best practice. I think using TypeScript is best practice. Or is that an opinion?

I'm definitely sharing this on Linkedin.

Collapse
brense profile image
Rense Bakker

I agree with everything, except the reducers. Although its good to stay up to date with a library like redux because of its widespread use, and using built in react hooks is definitely good advice. Personally I prefer more lightweight state management solutions like jotai and I hope to see a move away from redux in the industry.

Collapse
radulle profile image
Nikola Radulaški
  1. CSS modules yes, CSS-in-JS no. Using SCSS modules gives you same encapsulation you get with CSS-in-JS while offloading main thread. Sure, there's Lerna and similar libs that compile to CSS but they just add unnecessary complexity to the build system.
  2. If you use memoization too much something's wrong.
  3. Don't use state libs unless your app really needs them.
Collapse
decryptus007 profile image
Dominic

I've been using useState to handle most of my state in my React related project but I need to know more about how to handle state globally like using redux and the likes, can anyone recommend a good course for me to study?

Collapse
srmagura profile image
Sam Magura Author

The Redux Toolkit documentation has some good tutorials and guides for using Redux!

Collapse
trickydisco profile image
Gavin

The other thing worth mentioning about reducers is to switch on state first than action (event). Most developers write them action first which means the event can be fired unconditionally. It even mentions this in the docs
redux.js.org/style-guide/style-gui...

Collapse
intermundos profile image
intermundos

And the last advice - you don't switch to Vue.

Collapse
jorgemadson profile image
Jorge Madson

I loved this post, I am trying to improve my level as a react developer and I really liked the tips, I will read it every week until I internalized them.

Collapse
getsetgopi profile image
Gopinath Prasanna

A new JavaScript expert is born :)

Collapse
booboboston profile image
Bobo Brussels

Very helpful 🥳

Collapse
paratron profile image
Christian Engel

Found the post mostly edgy but went along until the " Only use server rendering if you really need it ".

That has to be the worst tip ever :(