In this day and age being a developer and being in a fast-paced environment we have to deliver as quickly as possible.
While trying to deliver quickly, we end up building functionality in an un-optimised
way. We start using the first solution on stack-overflow which might not always be the most efficient or correct solution and may even be a hack
.
I have listed down some of the inefficient code snippets and hacks that i have seen in multiple codebases and the correct way to tackle those. Let's Begin!
1. Native way to reset the window scroll position when navigating between web pages
Today many modern browsers have a tendency to remember the scroll position when navigation between pages on a website, while sometimes that can be very helpful, but at the same time it can cause a problem as well.
When you want to reset the page data or make a API call every time the page is loaded to keep the page updated, this can cause major problems.
Since the browser will always scroll to the previous scroll position and not on the top as expected.
Now in multiple codebases I have seen this handled using window.scrollTo(0,0)
on the page mounts. It's a bit laggy since it works after the first paint has happened.
But if we can disable the functionality of the browser to remember the scroll position, then we don’t need to add the hack. That's it.
if (window.history.scrollRestoration) {
window.history.scrollRestoration = 'manual'; //default is 'auto'
}
2. Easy and precise way to validate a URL without regex
I think one of the most searched questions and the most answered one is how to validate a basic URL in JS. And I have seen many different types of regex, string matching solutions for it.
But there is a simpler solution using new native URL constructor.
const validateURL = url => {
try {
new URL(url)
return true
} catch {
return false
}
}
3. Always add throttle or debounce on event listeners like scroll or resize
Whenever you’re listening for events on the page, it’s important to make sure that the event listeners don’t get overwhelmed with processing incoming requests.
Otherwise they can quickly become a bottleneck and cause an unnecessary performance hit.
Where this often turns into an issue is when you have listeners that fire off events in rapid succession, like for scroll on mouse-move or keydown events.
Since scroll events, for instance, can fire off at such a high rate, it’s critical to make sure that the event handler isn’t doing computationally expensive operations. Because if it is, it will be all the more difficult for the browser to keep up.
- Throttled Version:
const throttle = (fn, wait) => {
let time = Date.now()
return () => {
if ((time + wait - Date.now()) < 0) {
fn()
time = Date.now()
}
}
}
const cbFuncOnScroll = () => {
console.log('scroll')
}
const throttledfunc = throttle(cbFuncOnScroll, 200)
document.addEventListener('scroll', throttledfunc)
- Debounced Version:
const debounce = (func, delay) => {
let timeout = ''
return function() {
clearTimeout(timeout)
const context = this
const args = arguments
timeout = setTimeout(() => {
func.apply(context, args)
}, delay || 500)
}
}
const cbFuncOnScroll = () => {
console.log('scroll')
}
const debouncedFunc = debounce(cbFuncOnScroll, 200)
document.addEventListener('scroll', debouncedFunc)
- Bonus: Debounce with Window RequestAnimation Frame (Best)
const debounceUsingAnimationFrame = (fn, ...args) => {
// Setup a timer
let timeout
// Return a function to run debounced
return () => {
// Setup the arguments
const context = this
// If there's a timer, cancel it
if (timeout) {
window.cancelAnimationFrame(timeout)
}
// Setup the new requestAnimationFrame()
timeout = window.requestAnimationFrame(() => {
fn.apply(context, args)
})
}
}
const cbFuncOnScroll = () => {
console.log('scroll')
}
const debouncedAnimationFrameFunc =
debounceUsingAnimationFrame(cbFuncOnScroll, 200)
document.addEventListener('scroll', debouncedAnimationFrameFunc)
Protip:
document.addEventListener('scroll', cbFuncOnScroll, { passive: true })
, here passive which is set totrue
will tell the browser that you just want to do your stuff and you are not gonna call preventDefault. Here is the video showing the performance improvement caused by this property - https://youtu.be/NPM6172J22g
4. Cross browser styling can be achieved from CSS
Cross browser development is one of the most important skills that a Frontend developer should have, and we have always been there when we might need to tweak the styling of a component on a different browser, due to incompatibility of certain css properties.
What do you do to achieve this, the most common solution I have seen is via JS where we extract the UserAgent or Platform and based upon that, we apply styles on the component.
But is that the correct and only way to do it ?
Here is my solution
- Safari target CSS query
@supports (-webkit-touch-callout: none) {
// add styles here to override for safari
}
- Mozilla target CSS query
@-moz-document url-prefix() {
// add styles here to override for mozilla firefox
}
- IE11 target CSS query
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
// add styles here to override for IE11
}
This is a simple way to override or add styles for specific browsers without JS.
5. Lazy render the components using CSS
We have worked on large components which consist of multiple small components, among those small components not every component is actually visible inside the viewport initially.
But only visible when a user scrolls, but we normally load all the components and render them on the viewport.
A good JS solution here is to use IntersectionObserver API to handle the rendering of the component only when they are in focus. This solution is a good one since Observers work on a different thread and not hamper the performance on the main thread.
But what if i tell you there is a better solution to it without using JS but only CSS.
Here comes content-visibility
property which enables the user agent to skip an element's rendering work, including layout and painting, until it is needed.
Because rendering is skipped, if a large portion of your content is off-screen, leveraging the content-visibility property makes the initial user load much faster.
It also allows for faster interactions with the on-screen content. Pretty neat.
.element {
content-visibility: auto;
}
Protip: Lots of detailed explanation on content-visibility - https://web.dev/content-visibility/
6. Avoid code redundancy when adding try catch to API side-effect calls
The most common task we always perform when developing features is to make API calls in order to fetch data to display it on the page.
But since it's a side-effect and we have dependency on other services.
We tend to always wrap our API calls inside a try and catch statement to be on the safer side and handle errors gracefully.
But don’t you feel it adds too much boiler-plate code to every API call that we make ?
Here is a simple promise based solution to avoid excessive use of try-catch block
const sideEffectAPIWrapper = (promise) =>
promise
.then(result => [result, null])
.catch(err => [null, err])
const sampleFunc = async () => {
const [result, error] = await sideEffectAPIWrapper(callAPI())
if (result) {
console.log('success')
return
}
if (error) {
console.log('failure')
return
}
}
Conclusion
All the points I have mentioned are problems that I have faced and seen in development of web applications. I am sure you might have also encountered these points in your codebases.
One simple way to avoid hacks and redundancy is to ask yourself can there be a better alternate way to achieve this functionality.
This simple question when you ask yourself while writing or reviewing the code will always help you make a good decision and avoid future problems on performance and code efficiency in your codebase.
That's all folks, adios amigos
Top comments (16)
No. The expected behaviour when you click 'back' is to be returned to the place from where you went 'forward' - scroll position and all. If you prevent this from happening, you are breaking the normal, expected behaviour of the browser. You shouldn't do this, and it is extremely irritating for the user
Thanks Jon for commenting, the situation which i have discussed here is different, hence i mentioned not scroll to the top as expected.
For eg:
You have a header and a footer in between you have multiple small components and all the data is powered by making API calls its dynamic in nature.
Let us say you click on a small component which is just above the footer and you navigated to a new page, on the new page when you click on back you come back to the page and your scroll would be near the footer.
Since the page is very dynamic and we make API call every time the page loads and till the API has finished we show a loader or shimmer, since the page has scrolled down near the footer the user does not know whats happening above, then new data would get loaded, hence confusing the user more.
I agree with your point completely that resetting the scroll position is extremely irritating for the user, its a valid argument for page which is not very dynamic.
That is the reason by default browser set
window.history.scrollRestoration
toauto
in order to maintain the user behaviour, but yes the case i have mentioned is a different one where we need to reset it to manual.If this causes an issue for your page, you're doing things wrong
No problem, i hope you write a good article on how to resolve this in future so that i can look and make things right. We are here to learn always :)
that is awesome! thanks for sharing
Thanks Daniel, keep sharing it with your dev friends.
Very helpful and relatable. Bookmarked
Thanks mate, glad you liked it, keep sharing praveen
Awesome!
Thanks Nikhil, keep sharing it with your dev friends.
Great article 👍
Thanks ronak, appreciated
Awesome collection of tips 👏🏻
Thanks Sarah, keep sharing it with your dev friends.
Learnt a lot from this! Looking forward to your next article buddy :)
Thanks yusuf, always keep learning