Hacker News new | past | comments | ask | show | jobs | submit login
JavaScript fundamentals before learning React (robinwieruch.de)
307 points by rwieruch 8 months ago | hide | past | web | favorite | 70 comments



This article has a lot to offer a JS + React beginner so it feels lame to reply to some tiny part of it, but this bit is a constant error propogated in the ecosystem:

> Even though it is possible to mutate the inner properties of objects and arrays when using const, the variable declaration shows the intent of keeping the variable immutable though.

let and const only control mutability of the reference. They say nothing of the value mutability. const then can only be a signal that you are not reassigning to the identifier.

const is still useful as a default of course since reassignment is generally the exception.

Maybe this is what they meant but the language used made it seem otherwise.


'const prevents reassignment' is a much simpler way of saying that.

Mutability doesn't need to come into it.


While it is simpler, I think it's better that people actually understand the difference between a reference to a value and the value itself. "Pointers are hard" is a statement that many beginner programmers make, but if you don't understand the concept, you will always be limited as a programmer.

And whether you say "can be reassigned" or "mutable" (which means exactly he same thing, BTW) it doesn't really matter. JS has some surprising mutability rules. Variable references can either be mutable or not (depending on const, let or var), however function parameter references are always mutable (which can cause much hilarity in some circumstances).

Even values are a bit strange at times unless you understand what's going on under the hood. It's obvious that boolean or number values are immutable, but it's less obvious that string values are immutable; even more so since it doesn't throw an error (at least in V8) when you try to mutate them. You might assume that function values are immutable (how could you mutate it?), but because of closures, they are completely mutable (as long as the values being closed over are mutable). And, well, functions are also so-called "object" values in JS (which I still think is not a good idea, but I understand why they did it).

While, it is more complex to discuss that separation, it's valuable when you get into more difficult discussions -- especially if you are trying to write mostly pure functional code, with a few non-pure bits for performance. You need to be able segregate the pure from the non-pure and if you are passing closures, for instance, it's super important to understand how mutating a value in one place can essentially infect something that you thought was pure.


> "can be reassigned" or "mutable" (which means exactly he same thing, BTW)

I have never used 'mutable' to describe a variable, but rather only to what it references. Perhaps it's more lax in js-land. Why would you call something that's atomic ally changed anything other than assignment?


> And whether you say "can be reassigned" or "mutable" (which means exactly he same thing, BTW) it doesn't really matter.

They don't mean the same thing, so it matters.

None of your comment about booleans etc is relevant. `const` prevents reassignment and that is all, it literally has nothing to do with mutability.

If you bring mutability into the conversation you've confused something simple.


I agree with you, but I’ve cleared up this confusion with a number of colleagues. People coming from languages that have a truly immutable “const” find the keyword confusing in js.


Yea, I think const in JS alludes to the way const works in C++.

If someone knows java though, it can always be explained that const in JS is identical to marking a local variable as final in Java.

More so than const, I was perplexed by the JS choice of the `let` keyword. Are there any other languages that use let for declarations that can be reassigned? Did BASIC allow lets to be reassigned? Can’t remember...


`let` in common lisp can be reassigned.


PostScript actually stores read, write and execute bits in the references themselves! (Unlike the way Unix files store the access mode attributes in the inode itself, so all hard links to the same inode have the same rwx attributes.)

So you can have a reference to a mutable PostScript dictionary representing a font or something, then go "dup readonly" to hand out a read-only reference to the same dictionary, so other code can't modify it, but you can still modify the writable font if you hang onto the original writable reference.

You're allowed to change the execute bit of a reference with cvx/cvlit, but you can only downgrade the readability and writability of a reference with readonly, executeonly or noaccess. Since PostScript code is data (homoiconic), protected code in proprietary fonts can be read-only executable arrays.

http://www.math.ubc.ca/~cass/courses/ps.html


> let and const only control mutability of the reference. They say nothing of the value mutability. const then can only be a signal that you are not reassigning to the identifier.

Wouldn't this definition suggest that the value CAN be reassigned? The reference could be immutable but allow value reassignment, but is not the implementation of const.


> The reference could be immutable but allow value reassignment, but is not the implementation of const.

You can reassign values through a const reference in javascript. For example:

    const x = [1]
    x[0] = 2 // ok!
This just doesn't work with primitive values, because primitive values are immutable and copy-by-value. So this works with lists but not strings (because strings are a primitive and thus immutable).

Or, put in C++ friendly terms, javascript doesn't distinguish between pointer reassignment and value reassignment. It only has reassignment, which is illegal for const variables. Variables which hold non-primitives (lists, objects, etc) are actually pointers to those values. const does not affect the mutability of the value stored at the pointer.


I think the author was going for something like "When you see a const, think twice before changing anything inside that const."


Beginners don't know about references. What you say is exactly what the author meant.


Author here {: Yes, what you are saying here is what I meant. I didn't want to mix in the whole topic around "passing by value or reference" in this short teaser.

Hi Swizec :wave: :)


Hi, Robin.

Then you may want to say that `const` guarantees that the name will remain bound to that object.

The great-GP is correct in that `const` has nothing to do with immutability besides that. It doesn't necessarily conveys an intent of inner immutability. It just states that whatever is in the variable will stay there until it goes out of scope.

Using `const` whenever possible is good advice, off course.


It's been astonishing to me how people switched from var to let, but seem confused as to when to use const. I use const wherever I can since it's more appropriate for non-changing variables, and it helps me more easily visualize how a variable is being used in a block. But in other people's code I see a lot of letting all over the place. Maybe it's because of those articles saying to use let instead of var, but glossing over const.


I tend to go by the following rules:

Use const by default. If I need let, see if I can refactor to using const.

I've found that actually I can go far without using let, improving my code along the way.

I see a pattern from colleagues. They initialise a variable using let and then branch to figure out what should go inside it. That can be replaced by a pure function.

For loops tend to be the next thing. In that case, they can be replaced by higher order functions/methods without detriment to clarity (usually an improvement).

Perhaps the move towards iterators, with async support, will make it more common and legitimate. I haven't yet used those in a codebase but perhaps let in that instance leads to clearer code.


> They initialise a variable using let and then branch to figure out > what should go inside it. That can be replaced by a pure function.

I don't want to detail the discussion, but I would love to see some of your code. I contend with this antipattern often but solve it in different ways often depending on language features. If you're doing anything other than simply calling an auxiliary method with a ton of parameters, I would appreciate seeing your solution in Javascript, a language that I'm learning but not proficient in.


It's trivial to learn as well. You use const for everything until you get an error for trying to reassign it.


the only way to go


maybe it’s because const is 2 more letters to type.

if “let” was “letitbe” I bet const would be more popular

seriously though, I wish they could have chosen a 3-letter word for const in keeping with var and let. I know const exists in other languages, but it doesn’t even mean the exact same thing as some other languages anyway.


> I wish they could have chosen a 3-letter word for const in keeping with var and let.

"set" or "fix" would be quite nice, given what const actually does.


I'm having one of those days when variables won't and constants aren't.


It would have been nice to have `let` work like `const` does, and something else for what is currently `let` (`mut`?).

The TC39 was constrained by the list of reserved words though. `let` and `const` were reserved in ES5, and there was nothing that could have conveyed `mut`.

https://mathiasbynens.be/notes/reserved-keywords#ecmascript-...


Unrelated but didn't know how to contact you: https://www.youtube.com/watch?v=TdzIKD0oMzo


How about balancing out every "let" with a "forbid" at the end of the block to mark the end of the scope?


Good point, thank you for the clarification! Somehow I have always thought about showing the intent of keeping the data structure immutable when using const. I have changed it in the article. One more learning for me today :)


I've found this is often a problem in languages that pass simple values by value but compound values like objects and arrays by reference. The meaning of a qualifier like const and the inference a reader can draw when they see it can fundamentally change depending on what type of value it is applied to.

My experience has been that both the change in value/reference behaviour and the lack of total immutability guarantees when using a const qualifier on a reference type can be a source of bugs.

Languages like C and C++ distinguish more explicitly between value and reference semantics, and likewise between constant pointers and [mutable] pointers to constant data. To some extent that removes those sources of bugs, at the expense of having to write more explicit but verbose types like `const SomeType &` instead of `SomeType` all over the place.


If you don't know about references, I feel like you shouldn't be touching react. React is quite easy to pickup if you have an understanding of JavaScript and the Dom. The tooling surrounding react is an entirely different story.


To be fair, beginners should stay far away from most of the tooling around React. In-fact a lot of veterans should too. Most people only use stuff like Redux or Immutable out of habit rather than necessity, or as a crutch to constrain themselves away from bad habits that they shouldn't have picked up in the first place.


I think this idea is propagated by Swift developers because that’s the only place I’ve ever seen const used this way at all, ever.

That said I started writing articles on JavaScript and React and TypeScript in medium and please subscribe and check out my post history of you’re interested in this topic! I only did two so far but I plan to do one every day starting tonight, with this very topic!


Another basic ES6 trick that beginners usually don't know:

  handleChange = event => {
    this.setState({ [event.currentTarget.name]: event.currentTarget.value })
  }
So you can handle 10 inputs with the same handler.


This is great, and a strategy I used a lot in my last code base. If you do this for statically typed JS though, you'll get either error messages or bad typing with lots of `any`s and weak type checks.

Here's a great strategy to do the same thing with strong static types, like in flow and typescript:

  setTextField = (name: 'name' | 'email' | 'phone') => (event: InputEvent) => {
    this.setState({ user: { ...this.state.user, [name]: event.target.value } });
  }
  
  setBooleanField = (name: 'isCool') => (event: InputEvent) => {
    this.setState({ user: { ...this.state.user, [name]: event.target.checked } });
  }
  
  render() {
    <div>
    	<input type="text" onChange={ this.setTextField('email')} placeholder="Email" value={this.state.user.email} />
    	<input type="checkbox" onChange={ this.setBooleanField('isCool')} checked={this.state.user.isCool} />
    </div>
  }

More verbose than the non-typed version, but simpler than declaring a function for every field with all the goodness of strong static typing.


just blue my mind


Hey, author here :) I am curious about your experiences using/learning React. Are there any other JavaScript topics which are important when starting out with React? Would be great hearing your opinion!


This is great! Great write up and good idea on what to actually write about.

One suggestion I'd make is to use ES6 template literals over concatenation, since you're using other ES6 constructs anyway.

    getName() {
      return `${this.firstname} ${this.lastname}`;
    }
Cleaner and less error prone. On our team we had a convention to only use backticks when we intended to combine strings, so that if you ever saw a backtick, you knew a string was being built, likely for output.


Included it :)


It’s worth noting that using the fat arrow syntax for class functions make them exponentially harder to unit test because they do not exist on the Prototype of the component, but are instead only initialized when the class is initialized.

If you are trying to unit test a function that calls another function in the same class you cannot mock the second one it if it is a fat arrow.


I feel like testing things by replacing pieces of a prototype would be fairly fragile anyways. Is there a reason you couldn't use some form of composition to mock / abstract your dependencies instead?


Given this super contrived example: https://gist.github.com/sorahn/2b287c2a97a12e318585e84d9a9e8...

If 'baz' was a fat arrow function, you would never be able to test bar in isolation.

for example: Jest is set up so it's super easy to say `jest.spy(Foo.prototype, 'baz').mockReturnValue('whatever')`and test that bar is doing the right thing.


Mocking should only be used for extreme circumstances. This class is very easy to test without mocking. `bar` takes no arguments and has no external dependencies and therefore should always return the same result.


OK, you caught me, give bar an argument. (I'll edit the gist)

Why should mocking only be used in 'extreme circumstances'? I want to test what bar does, and I don't care what baz does, and if someone breaks baz, my unit tests for bar shouldn't fail, because it is doing its job.

I would mock it if it was calling some function in another module, so what's the difference if it's calling another function in the class?


Mocking a component means that you now have two places where that component's behavior is specified and they can diverge. To prevent that, you'll need an integration test where the components interact directly. Just not using a mock is enough to get such an integration test.

Then the value of the original, mocked unit test is questionable. It only provides additional information in the event that the mock differs from the actual component. If that's unintentional, then either the component is wrong (which should be caught by the tests for that component) or the mock is wrong. In either case, the mocked test provides little or negative value.

Then the remaining case, where mocking is actually useful, is when the mock intentionally shows different behavior. Mocking a slow computation to return the result instantly. Deliberately failing, to test error-handling code. Simulating unlikely events in general. Those are good uses of mocking.

TL;DR: Write more integration tests instead of unit tests with mocking.


> TL;DR: Write more integration tests instead of unit tests with mocking.

Interesting. I will investigate what that looks like at work tomorrow. Thanks!


I also think it's worth noting that fat arrow class methods is not a feature of JavaScript/ECMAScript, but rather a feature of Babel. It might make it into a future standard, but so might decorators (including autobinding decorators) or double-colon syntax for binding. None of these is currently legal vanilla JS.


Nice writeup!

I wrote a tutorial that would be the next step to yours, called React From Zero[0] I try to teach React here with the basic JavaScript knowledge most people already have.

[0] https://github.com/kay-is/react-from-zero


I can recommend this, having only read the first two, very enlightening. Thank you for your work.


I am learning React and ES6 currently. I dove into React before really understanding ES6, and your article is really helping to illuminate some of the dark corners of JS. Thank you.

One section I was hoping would get more treatment in your article (like the same treatment you gave classes, which was great) was imports/exports. Named vs. default exports are kind of baffling for a newcomer, and the usage of the named import with curly bracket syntax seems completely arbitrary.

I am sure there are many, many good explanations of named vs. default imports/exports out there on the Internet, but this is one that leaped out at me. I was a little disappointed that the section on imports/exports was so comparatively short. It mostly discussed the usage of imports/exports in CRA.

Still, awesome article. Thanks again.


Extended the section about imports and exports. Thanks for your feedback!


I think, the most important aspect I tell my developers is to not define functions inline during render. So, do not do this:

    <button onClick={ () => this.smth() }></button>
This creates a new function every time the rendering happens and mutates the prop onClick on every render. Once you have a component with a lot of elements (e.g. inputs) and child components that check for changes props to determine whether to render, this will get you in performance trouble.

We do not use class arrow functions but instead have helpers to bind specific functions to the component's context or generate setter functions.


I usually do this when I need to pass arguements in:

  <button onClick={ (arg) => this.smth(arg) }></button>
How can I do this without defining a function?


For simple use cases, we also use this pattern, however, usually moved to separate functions for readability, real example:

    createOpener(folder) {
        return () => this.props.dispatch(Actions.listFiles(folder, this.props.member));
    }
As said, for more complex layouts we need to reduce moving parts. For example, we have input masks that can easily consist of 200 input fields alone plus all kinds of other components. What we do in that case is usually pre-binding functions with arguments. Roughly like this:

    // Target function
    onItemClicked(item) {
        // ...
    }
    
    preBind() {
        const { data } = this.props;

        // Bind with primary key
        data.forEach(item => {
            this[`__boundFn_data_${item.get('primaryKey')}`] = this.onItemClicked.bind(this, item);
        })
    }

    render() {
        const { data } = this.props;

        return <Fragment>
            {
                data.map(item => {
                    const pk = item.get('primaryKey');

                    return <div key={pk} onClick={this[`__boundFn_data_${pk}`]}>{ item.get('label') }</div>
                })
            }
        </Fragment>
    }
This approach involves a lot more complexity concerning removing bound functions and caching. And things like function name generation is stored in separate functions etc. It's not trivial but you get some performance out of it.

By doing this, though, we can rely on props checks for components to determined the necessity of rendering which allows us to use React's PureComponent in 90% of our components.


> How can I do this without defining a function?

You do define a function, but only once. Constructor:

  this.smth = this.smth.bind(this);
JSX:

  <button onClick={this.smth}></button>
Kind of awkward, but the standard practice last I checked. Arrow methods (terminology?) are also something you can add to the language that does the equivalent of .bind() replacements.


isn't that functionally equivalent to `onClick={ this.smth }`?


No it is not equal because the former autobinds `this` into the body of the arrow function, however the latter is "just" a function pointer hence will not be called with the correct `this` value. One way to get around this is using `this.smth.bind(this)`, which binds the correct `this` for the later execution.


no, because you're not passing the arg in now right?


Of course you are. The `smth` method will receive whatever arguments the caller gives.

This totally works but only if `smth` has been declared as an arrow function (so that is captures the class `this` context).


Formatting strings (template literals [1]) comes up frequently for constructing ids/refs/various other props.

1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


Included :)


hey there, nice write-up

two minor things that you might include are

1: rest in the context of function args

2: short circuiting: e.g. `isLoading && <Loading />`


Included. Thanks :)


May want to point out you are the author of the article. :)


Done :)


I had the chance/experience to train a new team in Vue.js (and thus modern JS) which I imagine is similar to people learning React. I noticed the same thing as the author - the new JS frameworks are "all about JavaScript". Without a solid knowledge of how JS works, and ES6, learning any of them is difficult. When realizing this, the main things I focused on what getting people to understand JavaScript, _then_ start to make use of the framework's more powerful features.

I also noticed a lot of people interpreting `const` as "you can not change this, ever". It is short for constant, after all.


This was my personal experience learning Vue, I didn't know as much about JS as I thought I did.

I went back and started again filling in all the gaps I obviously had.

A year later and the frontend for all my stuff at work is TypeScript classes over Vue components and the code is actually simpler to understand.

I learnt something and the code got better, a win/win like that is rare ime.


The biggest fundamental is that you only rarely need new (when forced upon you by a bad API) and you don't need this (not ever).

That statement has worked well for me in this language. I know it causes many people to cry and get immediately angry. The bottom line is that they increase the verbosity (substantially) of code, they are completely optional, and they often compound code maintenance through a more convoluted flow control.


I can understand not liking `this` in JavaScript, but it seems optimistic to say you never need it. If nothing else, you are bound to encounter large amounts of code that uses it, so being familiar with its behaviour is necessary to understand that code.


"... but it seems optimistic to say you never need it. If nothing else, you are bound to encounter large amounts of code that uses it"

Wait -- JavaScript now supports "it"??! Can you iterate over "them"? At least it's not gendered, so you need to use "him" and "her" and "it" depending on the object's gender, or just keep everything in a collection so you can use "them".


this also has some really fun rules that are useful for mad science sometimes. For example, you can recover the JS context's global value via this:

    (function() { return this })()
In a browser this resolves to the window object. In nodejs it resolves to the global object. I've used this a few times to help write isomorphic javascript bundles, though its much less useful than it once was.


Shouldn't the doFilter function use query instead of this.state.query?

  const doFilter = query => user =>
    query === user.name;




Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: