Why you should write Javascript in a declarative style ?

Why you should write Javascript in a declarative style ?

What is declarative Programming?

Declarative programming is a programming paradigm that focuses on describing what you want to achieve, rather than how to achieve it. Instead of caring about giving step-by-step instructions to execute a task the way it has been done in an imperative style like in C, C++ or Java, we just describe the desired outcome or result.

This approach is really handy when we have to work on large-scale applications handling complex interactions. This allows you to work at a higher level of abstraction, making the code more expressive, concise, and readable.

Consider a simple task of filtering even numbers from a list of integers. The imperative code for it will look like this in javascript.

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    evenNumbers.push(numbers[i]);
  }
}
console.log(evenNumbers); // Output: [2, 4]

If I use the filter method, it'll look something like this

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // Output: [2, 4]

In the declarative example, we use the filter function to create a new array with only the even numbers, abstracting away the iteration and filtering logic.

Declarative programming emphasizes expressing the desired outcomes without specifying implementation details, resulting in clear, maintainable, and reusable code. It aligns with functional programming, eases testing, optimizes performance, and enhances adaptability. Declarative code fosters maintainability, scalability, and parallel execution, making it ideal for long-term development and complex projects.

React, the most widely used frontend library is based on declarative principles. You just declare the user interface and the react handles rendering and updates.

But you don't need to jump to react to write code declaratively. You can do that in vanilla javascript itself.

Creating a todo list app in a declarative style

We want to create a todo list. we want to add items to our list. delete items, mark them as complete.

First, we'll write some HTML for it

  <!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel='stylesheet' href='style.css' />
        <title>codedamn Project</title>
    </head>
    <body id="root">
        <h1 id="title">ADD TODOS</h1>
    <form id='todo-form'>
        <input type="text" id='todo-input' placeholder="Add task...">
        <button type="submit" id='submit-button'>+</button>
    </form>
     <div id="todo-list">
    </div>
    </body>
     <script src='script.js'> </script>
</html>

Here we have our container div with id todo-list, where we'll be adding our todos.

Now, let's go to the javascript.

I want to have a list, so it's a good idea to have an array to store my todo items

let todos = []
const todoForm = document.getElementById('todo-form')

todoForm.addEventListener('submit', addToDo);

I've also got a reference to my todo-input element.

Now, when the user submits a to-do, the addToDo function will be called. This function does just one thing and that is add the item to my todo array.

function addToDo(event) {

    event.preventDefault()
    const todoInput = document.getElementById('todo-input')
    const todo = todoInput.value.trim();
    todoInput.value = ''

    if (todo !== '' && todo) {
        const todoItem = {
            id: Date.now(),
            value: todo,
            done: false
        }
    todos = todos.concat(todoItem)
    } 
    updateDom()
}

I just add my item to my to-do list and I give the responsibility of updating Dom to updateDom function. what that function is, we'll see later. For now just understand, it handles rendering and updating Dom.

Same thing for the delete function.

function deleteToDo(event, index) {
    todos = todos.filter((todo, idx) =>  index !== idx)
    updateDom()
}

I just update my todo array and offload the task of updating the array to updateDom function.

checkToDo is also similar

function checkToDo(event, index) {
    event.preventDefault()
    todos = todos.map((todo, idx) => {
        if (idx !== index) {
            return todo
        } else {
            let updatedStatus = !todo.done
            return {
                ...todo,
                done: updatedStatus
            }
        }
    })
    updateDom()
}

So, I created functions to add new items to the to-do list, delete those items and mark them as complete or incomplete. and I did not care about how Dom is being updated.
It's being handled by updateDom function.
That's what declarative programming is. Emphasis on what, not how.

Now, let's take a look at updateDom function

function updateDom() {
    const todoList = document.getElementById('todo-list')

    while(todoList.hasChildNodes()) {
        todoList.removeChild(todoList.firstChild)
    }

    const ul = document.createElement('ul')

    todos.forEach((todo, index) => {
        const li = document.createElement('li')
        li.setAttribute('class', 'todo')
        li.setAttribute('id', todo.id)

        const todoTask = document.createElement('p')
        todoTask.textContent = todo.value
        li.append(todoTask)

        const checkButton = document.createElement('button')
        checkButton.setAttribute('class', `check-button ${todo.done ? 'done' : ''}`)
        checkButton.setAttribute('key', todo.id)
        checkButton.addEventListener('click', (e) => checkToDo(e, index))
        checkButton.innerHTML = '&#x2611;'
        li.append(checkButton)

        const deleteButton = document.createElement('button')
        deleteButton.setAttribute('key', todo.id)
        deleteButton.addEventListener('click', (e) => deleteToDo(e, index))
        deleteButton.innerHTML = '&#x2612;'
        li.append(deleteButton)

        ul.append(li)
    })

    todoList.append(ul)
}

This function gets a reference to the todo list div, and creates a ul element. then it maps over the todo array and creates a li element containing a p element which displays the todo, a delete button and a check to do button. it also assigns event listeners to these buttons calling the functions we described above. It appends li elements to ul element. At last, the ul element is appended to the div.

As it creates the dom for every item in the todo array from scratch, it makes sense to remove what was previously there, that's why we removed all the child nodes before appending a new node.

Summary

The given JavaScript code showcases declarative programming in a to-do list app. Event listeners enable behaviour abstraction, form submissions are handled functionally, and rendering is done declaratively.

Event delegation optimizes dynamic element handling. Clear intentions and descriptive names enhance code readability. This approach emphasizes expressing goals over low-level details, ensuring concise and understandable code.

Array manipulation uses functional approaches for simplicity. By adopting declarative principles, the code becomes more maintainable, scalable, and easier to comprehend for other developers, promoting productivity and efficient code development.

We've also done things in a non-mutating way. In declarative programming, we strive to minimize mutable states and prefer immutability where possible, reducing potential side effects and enhancing code clarity. We don't add or remove elements to our array, but create and return a new array every time any function is called. We neither change the current state of dom. Instead, we destroy the current state and reconstruct a new state from scratch.

Writing javascript this way, you'll face fewer bugs and it'll be very easier to grasp the concepts of react when you migrate to it.