DEV Community

loading...
Cover image for Smart solutions to avoid JS problems and hacks

Smart solutions to avoid JS problems and hacks

faisalpathan profile image faisal khan ・5 min read

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!


Showtime


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'
}
Enter fullscreen mode Exit fullscreen mode

solved


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
  }
}

Enter fullscreen mode Exit fullscreen mode

shock


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)
Enter fullscreen mode Exit fullscreen mode
  • 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)
Enter fullscreen mode Exit fullscreen mode
  • 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)
Enter fullscreen mode Exit fullscreen mode

Protip: document.addEventListener('scroll', cbFuncOnScroll, { passive: true }), here passive which is set to true 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
}
Enter fullscreen mode Exit fullscreen mode
  • Mozilla target CSS query
@-moz-document url-prefix() {
   // add styles here to override for mozilla firefox
}
Enter fullscreen mode Exit fullscreen mode
  • IE11 target CSS query
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
  // add styles here to override for IE11
}
Enter fullscreen mode Exit fullscreen mode

This is a simple way to override or add styles for specific browsers without JS.

easy


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;
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

done that's it


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

adios amigos

Discussion (16)

Collapse
jonrandy profile image
Jon Randy

Since the browser will always scroll to the previous scroll position and not on the top as expected.

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

Collapse
faisalpathan profile image
faisal khan Author • Edited

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 to auto 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.

Collapse
jonrandy profile image
Jon Randy

If this causes an issue for your page, you're doing things wrong

Thread Thread
faisalpathan profile image
faisal khan Author

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 :)

Collapse
danielcamargo profile image
Daniel Camargo

that is awesome! thanks for sharing

Collapse
faisalpathan profile image
faisal khan Author

Thanks Daniel, keep sharing it with your dev friends.

Collapse
saraogipraveen profile image
Praveen Saraogi

Very helpful and relatable. Bookmarked

Collapse
faisalpathan profile image
faisal khan Author

Thanks mate, glad you liked it, keep sharing praveen

Collapse
nikhilmwarrier profile image
Collapse
faisalpathan profile image
faisal khan Author

Thanks Nikhil, keep sharing it with your dev friends.

Collapse
ronaksonigara profile image
Ronak Sonigara

Great article 👍

Collapse
faisalpathan profile image
faisal khan Author

Thanks ronak, appreciated

Collapse
sarahob profile image
Sarah 🦄

Awesome collection of tips 👏🏻

Collapse
faisalpathan profile image
faisal khan Author

Thanks Sarah, keep sharing it with your dev friends.

Collapse
kkyusufk profile image
kkyusufk

Learnt a lot from this! Looking forward to your next article buddy :)

Collapse
faisalpathan profile image
faisal khan Author

Thanks yusuf, always keep learning

Forem Open with the Forem app