When you start writing simple JavaScript programs, you don’t need to worry about the number of variables you are using, or how different functions and objects work together.
For example, most people start out by using a lot of global variables , or variables that are scoped at the top level of the file. They are not part of any individual class, object or function.
For example, this is a global variable called state:
let state = "global";
But once your program starts to involve many different functions and/or objects, you will need to create a more rigorous set of rules for your code.
This is where the concept of state comes into play. State describes the status of the entire program or an individual object. It could be text, a number, a boolean, or another data type.
It’s a common tool for coordinating code. For example, once you update state, a bunch of different functions can instantly react to that change.
This article describes state in the context of React, a popular JavaScript library.
But guess what? Even state can give you headaches once your code gets complicated! Changing state can cause unintended consequences.
Let’s stop right there. State is a popular tool in object-oriented programming , or OOP. But, many programmers prefer functional programming , which discourages state changes. JavaScript supports both paradigms.
Okay, that’s a lot of terminology at once. I wanted to find a way to show how OOP and functional programming can accomplish the same goals, even if functional programming does not use state.
This tutorial will show how you might cook a meal of spaghetti and sauce from an OOP and functional perspective.
Here’s a quick preview of the two different approaches:
Let’s jump into it. In order to understand this tutorial, you just need to understand functions and objects in JavaScript.
Object-Oriented Method (Using State)
In the graphic above, we showed two different approaches to making this pasta dinner:
- A method that is focused on the state of the different tools , like the stove, the pot and the pasta.
- A method that is focused on the progression of the food itself , with no mention of state of the individual tools (pots, stoves etc.)
The object-oriented approach focuses on updating state , so our code will have state at two different levels:
- Global, or the state of this entire meal.
- Local for each object.
We are going to use ES6 syntax in this tutorial to create objects. Here’s an example of global state and the “Pot” prototype.
let stoveTemp = 500;
function Pot(){
this.boilStatus = '';
this.startBoiling = function(){
if( stoveTemp > 400)
this.boilStatus = "boiling";
}
}
let pastaPot = new Pot();
pastaPot.startBoiling();
console.log(pastaPot);
// Pot { boilStatus = 'boiling'; }
Note: I simplified the console.log statement to focus on the state update.
Here’s a visual representation of that logic:
Before
After
There are two states, and when the pastaPot is created via the Pot prototype, it initially has an empty boilStatus. But then, there is a state change.
We run pastaPot.startBoiling(), and now the boilStatus (local state) is “boiling”, since the global state of stoveTemp is over 400.
Now let’s go one step further. We will allow the pasta to become boiled due to the state of pastaPot.
Here’s the code we will add to the snippet above:
function Pasta (){
this.cookedStatus = false;
this.addToPot = function (boilStatus){
if(boilStatus == "boiling")
this.cookedStatus = true;
}
}
let myMeal = new Pasta();
myMeal.addToPot(pastaPot.boilStatus);
console.log(myMeal.cookedStatus);
// true
Woah! That’s a lot at once. Here’s what happened.
- We created a new prototype of “Pasta”, where every object will have a local state called cookedStatus
- We created a new instance of Pasta called myMeal
- We used the state from the pastaPot object that we created in the last snippet to determine if we should update the state called cookedStatus in myMeal to cooked.
- Since the state of boilStatus in pastaPot was “boiling”, our pasta is now cooked!
Here’s that process visually:
Before
After
So, we now have the local state of one object, that depends on the local state of another object. And that local state depended on some global state! You can see how this can be challenging. But, it is at least easy to follow for now, since states are updated explicitly.
Functional Method (without state)
In order to fully understand state, you should be able to find a way to accomplish the same outcome as the code above without actually modifying state. This is where functional programming helps!
Functional programming has two core values that separate it from OOP: immutability and pure functions.
I am not going to go into too much depth on those topics, but if you want to learn more, I encourage you to check out this guide to functional programming in JavaScript.
Both of these principles discourage the use of state modification in your code. That means that we can’t use local or global state.
Functional programming instead encourages us to pass in parameters to individual functions. We can use outside variables, but we can’t use them as state.
Here’s an example of a function that will boil the pasta.
const stoveTemp = 500;
const cookPasta = (temp) => {
if(temp > 400)
return 'cooked';
}
console.log(cookPasta(stoveTemp));
// 'cooked'
This code will successfully return a string of ‘cooked’. But notice- there is no object that we are updating. The function simply returns the value that will be used in the next step.
Instead, we are focused on the inputs and outputs of one function: cookPasta.
This perspective looks at the transformation of the food itself, rather than the tools that are used to cook it. It’s a little harder to visualize, but we don’t need to have the function depend on external state.
Here’s what it looks like.
Think of it as a “timeline view” for the progress of the meal- this particular function just covers the first part, the transition from dry pasta to cooked pasta.
Now let’s cover the second part as the food is served. Here’s the code that will serve the meal. It will come after the code block above.
const serveMeal = (pasta) => {
if (pasta == 'cooked')
return 'Dinner is ready.'
}
console.log( serveMeal(cookPasta(stoveTemp)) );
// 'Dinner is ready.'
Now, we are delivering the results of the cookPasta function directly into the serveMeal function. Again, we are able to do this without changing state, or changing data structures.
Here’s a diagram that uses the “timeline view” to show how these two functions work together.
Interested In More Visual Tutorials?
If you would like to read more visual tutorials about HTML, CSS and JavaScript, check out the main CodeAnalogies site for 50+ tutorials.
Top comments (5)
In this case I’d like to think of functional programming as transforming data, or specifically pasta. You’d take a pasta data structure, pass that to a ‘cook’ function, pass a sauce data structure to a ‘heat’ function, then both to a ‘serve’ function which results in a steamy plate of noodly nutrients.
Ramen 🙏
Hi Kevin, really nice article. I totally agree that there's an overall tendency of creating prototypes/classes where there's no need for them (at all). This seems to be the side product of the fact that most code tutorials focus on how to do exactly that. But there's a reason for that, most complex systems will require instances and other benefits provided by those prototypes and that's why they are present in nearly all programs. Seems like the problem lies on figuring out where those prototypes should be used. If you consider scaling up those two versions of code, think about cooking new meals, and using other tools to heat it up, and so on. You will soon realize that the best approach would be to have at least one class (maybe Food) where you would store the temperature parameter and many others. But again you are right in pointing out that most problems can be solved with pure functions and they should.
Thanks Rafael! Yeah, the way that I think about this is that the analogy focused article should only cover the very basics of the system. As you get more advanced and doing the real thinking that it will take to structure your code, it should be mainly code-based. So, I try to leave the later, more technical stuff up to people's imagination since it will inevitably become a more technical article in order to cover those considerations.
Hey Kevin,
I would love to help you expand codeanalogies with different categories and topics , for example i could help you with analogies for the most used OOP concepts and maybe some other programming languages / frameworks. Let me know if you are interested :-)
I'm open to it Norberth! Want to email me at kevin [at] codeanalogies.com and we can talk further?