O CZYM PISZEMY?

How NOT to Write Code in React JS

Wysłano dnia: 26.11.2019 | Komentarzy: 0
How NOT to Write Code in React JS

 

React, Vue, Angular, Ember, Backbone, Polymer, Aurelia, Meteor, Mithril.js, Preact… There are many super fancy Javascript frameworks nowadays. We can write anything we want, it’s comfortable, easy to understand, although difficult to master. After few lines of code, even after writing few small applications you may think that no matter what you write, these frameworks will do the job.

 

 

Yeah, what you see above is the iconic series Star Wars and Chewbacca being very skeptical about Han’s idea. In the programming universe, you should have this Chewbacca in your mind and this Chewbacca should always be skeptical about your code. It’s very important to write your code carefully and thoughtfully no matter what framework you are using. I know it looks that these frameworks do everything for you and you don’t have to worry about anything but it’s not entirely true. Buckle up, in this article we are going to go through the most common mistakes done in React (and probably other similar frameworks/libraries). I am probably one of the most reliable people to talk about it because I used to make some of these mistakes for a loooong time. And probably I’m still doing some of them.

 

 

New feature? Yeah.. One component is enough.

Nope. Nine times out of ten it won’t be enough. Imagine that, in your application, you have a list of board games with some simple filtering controls over the table. You know, choosing a price, an age or a type. At the beginning it looks like it’s enough to create a BoardGamesList component and put all of the logic inside. You may think that it doesn’t have sense to create a separate BoardGameRow and BoardGamesFilters components. Or even PriceFilter, AgeFilter and BoardGameTypeFilter.

 

„It’s just a few lines! I don’t have time to create all these components with so few lines of code.”

 

It’s just a few lines for now. But it’s very likely that during next year your client will be requiring few more filter options, some fancy icons, 5 ways of ordering the game and 10 ways to display the game row depending on something. Trust me, in my programming life I have experienced too many components which were small at the beginning and after a year it was a massive, uncontrollable piece of sh.. component. Seriously, if you take a few moments and divide it functionally at the beginning, it’ll be much easier to work with this component in future. For you. For your colleagues. And even if it stays so small, it’ll be easier to find what you need if you rationally divided it into separate React components. Then your work will look like this:

 

– Hey, we have some weird bug while filtering board games by age. Can you do something about it?

– Yeah, I know exactly where to find it. It’s in PriceFilter.js and it’s so small that I will need half an hour to fix it!

– Great, I think you should get a pay rise!

 

 

 

It won’t necessarily end this way. But there is a better chance if you properly divide your code into components. And look –  I don’t even say a word about reusability which is the main reason we use components. But this is a topic for a separate article.

 

 

setState? Pff.. I don’t have to read the documentation.

If you decided to use React state in your components, you should know that its main function, setState, is asynchronous. What means you can’t just put some important code depending on your state just after setState execution. Let’s look at this case:

 

this.setState({isItWorking: true});
console.log(this.state.isItWorking); // returns false, WHY?!

 

Method setState is asynchronous what means it needs few moments to properly set the data you passed. How to handle this problem correctly? The most common way is to pass a callback function as a second parameter which will be executed when the data is passed to the state.

 

this.setState({isItWorking: true}, () => {
  console.log(this.state.isItWorking); // and now it returns true
});

 

Sometimes you have to do some consecutive operations on your state. Because of setState asynchrony, you may receive unexpected results.

 

// this.state.name = 'STAR WARS: '
this.setState({name: this.state.name + 'THE RISE OF '});
this.setState({url: this.state.name + 'SKYWALKER'});

 

Unfortunately, you won’t finish with the real name of episode IX – STAR WARS: THE RISE OF SKYWALKER. Probably you will get partially filled title like STAR WARS: SKYWALKER. It would be a nice title but it’s not what we wanted because the second setState has been overwritten by the last one. To fix it you can use one more time the callback technique but there is another way to handle this case. Instead of passing a new object you can pass a function which returns an object. What’s the difference? This function’s first parameter is the current „version” of state so you will always work on the updated state.

 

// this.state.name = 'STAR WARS: '
this.setState(state => ({name: state.name + 'THE RISE OF '}));
this.setState(state => ({name: state.name + 'SKYWALKER'}));

 

If it’s not enough for you and you want to know how setState works internally it’ll be a smart choice to read an article from Redux co-author, Dan Abramov: How Does setState Know What to Do?

 

 

Hey! Why this.props is undefined?

This mistake is still very common, no matter that arrow functions are one of the main features of ES6 specification.

 

handleFieldChange() {
  console.log(this.props); // return undefined
}

 

Why? I am inside the component, I should have access to my props! Unfortunately not. This function has its own this (different than component’s this) and if you want to use the standard function you should consider binding this with .bind(this) or not so beautiful const self = this before the function. Much easier and a simply better option is to use ES6 arrow functions.

 

handleFieldChange = () => {
  console.log(this.props); // YEAH! It returns my props!
}

 

Arrow function uses something what is called lexical scoping. In a simple way – it uses this from the code containing arrow function – in our case, the component. That’s it. No more bindings, no more awful selves, no more unexpectedly missing variables. Arrow functions are also very useful if you need to propagate a few functions. For example, you need a setup function which takes some important parameters and then returns the generic handler function.

 

handleFieldChange = fieldName => value => {
  this.setState({[fieldName]: value});
  // [fieldName] - for your knowledge, it's dynamic key name, it'll take the name you pass in fieldName variable
}

 

This is a very generic way to create a function that receives field name and then returns the generic handler function for, let’s say, an input element. And if you execute it like that..

 

<Input onChange={this.handleFieldChange('description')} />;

 

..your input will have this classic function assigned to onChange event:

 

handleFieldChange = value => {
  this.setState({description: value});
}

 

You should also know that you can fully omit curly braces if you have something very short to return.

 

getParsedValue = value => parseInt(value, 10); 

 

In my opinion, in most cases you should avoid it because it can be difficult to read it. On the other hand, in simple cases like above, it’ll save you a few lines. But you should be careful doing it. Let’s say, I have single object to return. I decide to return it in one line because it’s a really short line.

 

getSomeObject = value => {id: value};

 

Oh yeah, you may think that based on the previous code example it should definitely work. But it’s not and it’s quite easy to explain. In this case, the system thinks that you use standard arrow function and these curly braces are just the beginning and the end of the function. If you really want to return an object in one line you should use this syntax:

 

getSomeObject = value => ({id: value});

 

In this case, the returned object is contained in the brackets and it works like intended. Personally, I don’t like using one line functions but it’s a very nice way to pass a short code to functions like map or filter. Clean, easy to read and it’s included in one line.

 

someCrazyArray.map(element => element.value).filter(value => value.active);

 

Okaaaaaaay, quite a lot of info about simple arrow functions but I think it’s valuable if you didn’t know about it. Let’s go to the next one of the ReactJS mistakes!

 

 

The browser has crashed.. Why?

I can’t even count how many times I or my buddies were struggling with some weird browser crash or inability to do any action in the application. In many cases, the reason is an infinite loop. I suppose there are billions of ways to get the infinite loop but in React the most common is componentWillReceiveProps or any other React lifecycle method. ComponentWillReceiveProps is currently deprecated but I will focus on this one because there are plenty of React applications still using it and for me most of these bugs happened in this very important lifecycle method. I have multiple examples in my mind which can help visualize the problem but for the purposes of this article I will present this use-case based on the board games example:

 

Every time a user changes the age, the application should load board games for the chosen age.

 

componentWillReceiveProps(nextProps) {
  if (nextProps.age) {
    this.loadBoardGames(nextProps.age);
  }
}

 

„Right, if there is the age passed, I load board games.” If you don’t know how this lifecycle works you may end up with the solution similar to above. But this lifecycle method doesn’t work exactly like that. First, every time there is some change in component’s props, componentWillReceiveProps is executed. It’s quite easy to understand. But you may still think: „Okay, so every time the age is changed, it’ll load board games. Isn’t it okay?”. Partially yes, but in most cases there are other props in your component. Props which will also trigger this lifecycle function. Imagine that we have also boardGames prop (where we store currently displayed board games). Let’s examine such a situation:

 

  1. Age prop is changed
  2. componentWillReceiveProps is executed (what causes board games’ load)
  3. Board games prop is changed
  4. componentWillReceiveProps is executed (what causes board games’ load)
  5. Board games prop is changed
  6. componentWillReceiveProps is executed (what causes board games’ load)
  7. INFINITE LOOP!!!

 

Podobny obraz

 

As you can see, you change age prop only once and board games will be loading forever. Let’s change this function a little bit.

 

componentWillReceiveProps(nextProps) {
  if (this.props.age !== nextProps.age) {
    this.loadBoardGames(nextProps.age);
  }
}

 

Now we are not simply checking if there is the age but we are checking if there is some change in the age. We check if next „version” of props contains a DIFFERENT age. „Different” is the keyword here. And we are safe, every time boardGames prop (or any other prop) is changed, it won’t trigger board games to load because the age prop will still be the same.

 

Wait.. Are we really safe?

Not necessarily. Most Javascript developers prefer strict comparison (=== or !==) because it’s more certain than abstract comparison (==). But it can highlight other minor bugs in your application (what is actually good). Sometimes, some prop can theoretically be the same but of a different format. Let’s say our age is represented by years. But there is the possibility that this somehow it will be passed in months. For example, the initial age is fetched from the database and initially we forgot to convert it into the year format. In this case, board games will be loaded two times instead of one. Theoretically, there isn’t any serious change in the age but there is a change in the age’s format and it’s enough to trigger loadBoardGames again. The problem appears more often if the given prop is an object. The object can have 100% the same content but it can still be a different object. Generally, I want to indicate that lifecycle methods are powerful but they should be used very, very carefully. If you need to use it, think what you want to achieve and what is the flow of the prop.

 

 

Something else?

No, that’s it. There are many different ways to crash or mess your React application but I chose these which I experienced myself. I hope it was a valuable reading for you. Now.. let’s code!

Dodaj komentarz