Vue was the first JS framework I ever used. You could say that Vue was one of the first doors into the JavaScript world for me. For now, Vue is still a great framework. With the advent of the composition API, Vue is only going to get bigger. In this post, I’ll cover 10 useful custom hooks that will make our code look even better.

useWindowResize

This is a basic hook, as it is used in many projects.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { ref, onMounted, onUnmounted } from 'vue';

export function useWindowResize() {
  const width = ref(window.innerWidth);
  const height = ref(window.innerHeight);
  const handleResize = () => {
    width.value = window.innerWidth;
    height.value = window.innerHeight;
  }

  onMounted(() => {
    window.addEventListener('resize', handleResize)
  });

  onUnmounted(() => {
    window.removeEventListener('resize', handleResize)
  })

  return {
    width,
    height
  }
}

It’s even easier to use, just call this hook to get the width and height of the window.

1
2
3
setup() {
    const { width, height } = useWindowResize();
}

useStorage

You want to persist data by storing its value in session storage or local storage and binding that value to the view? With a simple hook - useStorage - this will be very easy. We just need to create a hook to return the data from the storage and a function to store the data in the storage when we want to change it. Here is my hook.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { ref } from 'vue';

const getItem = (key, storage) => {
  let value = storage.getItem(key);
  if (!value) {
    return null;
  }
  try {
    return JSON.parse(value)
  } catch (error) {
    return value;
  }
}

export const useStorage = (key, type = 'session') => {
  let storage = null;
  switch (type) {
    case 'session':
      storage = sessionStorage;
      break;
    case 'local':
      storage = localStorage;
      break;
    default:
      return null;
  }
  const value = ref(getItem(key, storage));
  const setItem = (storage) => {
    return (newValue) => {
      value.value = newValue;
      storage.setItem(key, JSON.stringify(newValue));
    }
  }
  return [
    value,
    setItem(storage)
  ]
}

In my code, I use JSON.parse and JSON.stringify to format the data. If you don’t want to format it, you can remove it. Here is an example of how to use this hook.

1
2
const [token, setToken] = useStorage('token');
setToken('new token');

useNetworkStatus

This is a useful hook that supports checking the status of network connections. To implement this hook, we need to add event listeners for the events “online” and “offline”. In the event, we just call a callback function with network status as parameter. Here is my code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { onMounted, onUnmounted } from 'vue';

export const useNetworkStatus = (callback = () => { }) => {
  const updateOnlineStatus = () => {
    const status = navigator.onLine ? 'online' : 'offline';
    callback(status);
  }

  onMounted(() => {
    window.addEventListener('online', updateOnlineStatus);
    window.addEventListener('offline', updateOnlineStatus);
  });

  onUnmounted(() => {
    window.removeEventListener('online', updateOnlineStatus);
    window.removeEventListener('offline', updateOnlineStatus);
  })
}

Call method.

1
2
3
useNetworkStatus((status) => { 
    console.log(`Your network status is ${status}`);
}

useCopyToClipboard

The clipboard is a relatively common function that we can also encapsulate as a hook, with the code shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function copyToClipboard(text) {
  let input = document.createElement('input');
  input.setAttribute('value', text);
  document.body.appendChild(input);
  input.select();
  let result = document.execCommand('copy');
  document.body.removeChild(input);
  return result;
}

export const useCopyToClipboard = () => {
  return (text) => {
    if (typeof text === "string" || typeof text == "number") {
      return copyToClipboard(text);
    }
    return false;
  }
}

Use the following.

1
2
const copyToClipboard = useCopyToClipboard();
copyToClipboard('just copy');

useTheme

Just a short hook to change the theme of the website. It helps us to switch the theme of the site easily by simply calling this hook with the theme name. Here is an example of the CSS code I used to define the theme variables.

1
2
3
4
5
6
7
8
html[theme="dark"] {
   --color: #FFF;
   --background: #333;
}
html[theme="default"], html {
   --color: #333;
   --background: #FFF;
}

To change the theme, just make a custom hook which returns a function to change the theme by theme name. The code is as follows.

1
2
3
4
5
export const useTheme = (key = '') => {
  return (theme) => {
    document.documentElement.setAttribute(key, theme);
  }
}

Use the following.

1
2
const changeTheme = useTheme();
changeTheme('dark');

usePageVisibility

Sometimes we need to do something when a customer is not focused on our site. To do this, we need something that lets us know if the user is paying attention. This is a custom hook. I call it PageVisibility and the code is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { onMounted, onUnmounted } from 'vue';

export const usePageVisibility = (callback = () => { }) => {
  let hidden, visibilityChange;
  if (typeof document.hidden !== "undefined") {
    hidden = "hidden";
    visibilityChange = "visibilitychange";
  } else if (typeof document.msHidden !== "undefined") {
    hidden = "msHidden";
    visibilityChange = "msvisibilitychange";
  } else if (typeof document.webkitHidden !== "undefined") {
    hidden = "webkitHidden";
    visibilityChange = "webkitvisibilitychange";
  }

  const handleVisibilityChange = () => {
    callback(document[hidden]);
  }

  onMounted(() => {
    document.addEventListener(visibilityChange, handleVisibilityChange, false);
  });

  onUnmounted(() => {
    document.removeEventListener(visibilityChange, handleVisibilityChange);
  });
}

The usage is as follows.

1
2
3
usePageVisibility((hidden) => {
   console.log(`User is${hidden ? ' not' : ''} focus your site`);
});

useViewport

Sometimes we use the width to detect the current user device so that we can process the corresponding content according to the device. For this scenario, we can also encapsulate it in a hook with the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { ref, onMounted, onUnmounted } from 'vue';

export const MOBILE = 'MOBILE'
export const TABLET = 'TABLET'
export const DESKTOP = 'DESKTOP'

export const useViewport = (config = {}) => {
  const { mobile = null, tablet = null } = config;
  let mobileWidth = mobile ? mobile : 768;
  let tabletWidth = tablet ? tablet : 922;
  let device = ref(getDevice(window.innerWidth));
  function getDevice(width) {
    if (width < mobileWidth) {
      return MOBILE;
    } else if (width < tabletWidth) {
      return TABLET;
    }
    return DESKTOP;
  }

  const handleResize = () => {
    device.value = getDevice(window.innerWidth);
  }

  onMounted(() => {
    window.addEventListener('resize', handleResize);
  });

  onUnmounted(() => {
    window.removeEventListener('resize', handleResize);
  });

  return {
    device
  }
}

Use the following.

1
const { device } = useViewport({ mobile: 700, table: 900 });

useOnClickOutside

When the model box pops up, we want to be able to click on other areas to close it, this can be used clickOutSide, this scenario we can also encapsulate as hooks, the code is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { onMounted, onUnmounted } from 'vue';

export const useOnClickOutside = (ref = null, callback = () => {}) => {
  function handleClickOutside(event) {
    if (ref.value && !ref.value.contains(event.target)) {
      callback()
    }
  }

  onMounted(() => {
    document.addEventListener('mousedown', handleClickOutside);
  })

  onUnmounted(() => {
    document.removeEventListener('mousedown', handleClickOutside);
  });
}

The usage is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<template>
    <div ref="container">View</div>
</template>
<script>
import { ref } from 'vue';
export default {
    setup() {
        const container = ref(null);
        useOnClickOutside(container, () => {
            console.log('Clicked outside'); 
        })
    }
}
</script>

useScrollToBottom

In addition to paged lists, load more (or lazy loading) is a friendly way to load data. Especially for mobile devices, almost all applications running on mobile devices apply load more in their UI. to do this, we need to detect that the user scrolled to the bottom of the list and trigger a callback for that event. useScrollToBottom is a useful hook that supports you in doing so. The code is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { onMounted, onUnmounted } from 'vue';

export const useScrollToBottom = (callback = () => { }) => {
  const handleScrolling = () => {
    if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
      callback();
    }
  }

  onMounted(() => {
    window.addEventListener('scroll', handleScrolling);
  });

  onUnmounted(() => {
    window.removeEventListener('scroll', handleScrolling);
  });
}

The usage is as follows.

1
useScrollToBottom(() => { console.log('Scrolled to bottom') })

useTimer

The code of useTimer is a bit longer than the other hooks. useTimer supports running a timer with some options like start, pause/resume, stop. To do this, we need to use the setInterval method. Here, we need to check the pause status of the timer. If the timer is not paused, we just need to call a callback function which is passed by the user as an argument. To support the user in knowing the current paused state of the timer, in addition to the action useTimer, give them a variable isPaused whose value is the paused state of the timer. The code is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { ref, onUnmounted } from 'vue';

export const useTimer = (callback = () => { }, step = 1000) => {
  let timerVariableId = null;
  let times = 0;
  const isPaused = ref(false);
   
  const stop = () => {
    if (timerVariableId) {
      clearInterval(timerVariableId);
      timerVariableId = null;
      resume();
    }
  }
  
  const start = () => {
    stop();
    if (!timerVariableId) {
      times = 0;
      timerVariableId = setInterval(() => {
        if (!isPaused.value) {
          times++;
          callback(times, step * times);
        }
      }, step)
    }
  }

  const pause = () => {
    isPaused.value = true;
  }

  const resume = () => {
    isPaused.value = false;
  }

  onUnmounted(() => {
    if (timerVariableId) {
      clearInterval(timerVariableId);
    }
  })

  return {
    start,
    stop,
    pause,
    resume,
    isPaused
  }
}

The usage is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function handleTimer(round) {      
    roundNumber.value = round;    
}
const { 
    start,
    stop,
    pause,
    resume,
    isPaused
} = useTimer(handleTimer);

This article shares 10 useful Vue custom hooks. Vue. is a great framework and I hope you can use it to build more great things.