Effective solution: Move the state variable outside of the click handler's scope
Rearrange your JavaScript code to place the number
variable outside of the click handler function. By doing this, you prevent resetting the number
variable to 1 every time the click event is triggered.
var number = 1;
function incrementNumber() {
var counterElement = document.getElementById('count');
number++;
counterElement.textContent = number.toString();
}
Check out an example here: https://jsfiddle.net/hLymhak7/6/
Enhance performance by moving the element reference outside of the click handler
For elements that persist and are not dynamically created or destroyed, it is advisable to keep the element reference outside of the click handler's scope. This practice helps optimize your application's performance.
var number = 1;
var counterElement = document.getElementById('count');
function incrementNumber() {
number++;
counterElement.textContent = number.toString();
}
While DOM query lookups are inexpensive nowadays, excessive queries can impact your app's performance negatively.
Example: https://jsfiddle.net/hLymhak7/8/
Explicitly define element dependency for easier testing
A convenient approach involves passing the counterElement
to the click handler function, enhancing testability and code maintainability.
JavaScript Code
var number = 1;
function incrementNumber(counterElement) {
number++;
counterElement.textContent = number.toString();
}
HTML Structure
<div>
<span id="count">1</span>
</div>
<div>
<button onclick="incrementNumber(count)">
+
</button>
</div>
The span
element is attached to a global variable accessible within the scope of the button
element similar to the incrementNumber
click handler. Hence, both count
and window.count
could be utilized to access the span
element across examples.
Example: https://jsfiddle.net/hLymhak7/12/
Optimal approach: Implement as event listener
Avoid binding the click handler directly through the onclick
attribute of the button
element. Utilize Element#addEventListener
for more flexibility in attaching multiple event listeners.
HTML Structure
<div>
<span id="count">1</span>
</div>
<div>
<button id="incrementor">
+
</button>
</div>
JavaScript Code
var number = 1;
var counterElement = document.getElementById('count');
var incrementButton = document.getElementById('incrementor');
incrementButton.addEventListener('click', incrementNumber);
function incrementNumber() {
number++;
counterElement.textContent = number.toString();
}
Find more insights on onclick implementations here.
Example: https://jsfiddle.net/hLymhak7/13/
Blend best practices with explicit element dependency
Include a click listener which passes the counterElement
explicitly to the incrementNumber
function, harmonizing testability and code maintenance.
var number = 1;
var counterElement = document.getElementById('count');
var incrementButton = document.getElementById('incrementor');
incrementButton.addEventListener('click', function onClickHandler() {
incrementNumber(counterElement);
});
function incrementNumber(counterElement) {
number++;
counterElement.textContent = number.toString();
}
This methodology advances towards sustaining easily testable code.
Example: https://jsfiddle.net/hLymhak7/14/
Finalized robust solution emphasizing maintainability and testability
Culminate the solution by elucidating the secondary dependency - the number
state variable, heightening simplicity in testing and comprehending the functionality.
HTML Structure
<div>
<span id="count">1</span>
</div>
<div>
<button id="incrementor">
+
</button>
</div>
JavaScript Code
var number = 1;
var counterElement = document.getElementById('count');
var incrementButton = document.getElementById('incrementor');
incrementButton.addEventListener('click', function onClickHandler() {
number = incrementNumber(counterElement, number);
});
function incrementNumber(counterElement, number) {
number++;
counterElement.textContent = number.toString();
return number;
}
Though lengthier, this method clarifies dependencies and segregates business logic into the incrementNumber
function, facilitating unit tests and comprehensive understanding of its operation.
Test suite prototype
import { plusOne } from './plus-one';
describe('incrementNumber', () => {
let countElement;
let initialState;
let currentState;
beforeEach(() => {
initialState = 1;
currentState = initialState;
countElement = {
textContent: initialState.toString(),
};
})
it('returns an incremented state value', () => {
currentState = plusOne(countElement, currentState);
expect(currentState).toBe(initialState + 1);
});
it('does not modify the existing state value', () => {
plusOne(countElement, currentState);
expect(currentState).toBe(initialState);
})
it('updates the counter element with the new state value', () => {
currentState = plusOne(countElement, currentState);
expect(countElement.textContent).toEqual(currentState.toString());
});
});
Example: https://jsfiddle.net/hLymhak7/15/
Error-prone practice: Keeping state data in the DOM
Maintaining state information in the DOM structure is commonly employed but not recommended. While it simplifies code by reducing mutable state, retrieving the state data from the DOM at various locations disrupts proper code organization.
Conversely, the ideal scenario dictates leveraging JavaScript for managing business logic while letting the DOM reflect the updated state, optimizing code separation and ease of testing.
This strategy also minimizes dependence on DOM intricacies, streamlining maintenance and testing processes.
// Caution: Preserving state data in the DOM should be avoided, but if necessary...
var count = document.getElementById('count');
function incrementNumber() {
var currentNumber = Number(count.textContent);
currentNumber++;
count.textContent = currentNumber.toString();
}
Example: https://jsfiddle.net/hLymhak7/9/