Vue - auto-resize (textarea) — 3 different approaches

Adam Orłowski
4 min readSep 6, 2019
Screenshot from codesandbox presenting four textareas. There is a smile in the last one.

Last week my colleague at work had a task to make all textareas autoresizing while typing. Great small idea to make UI more user friendly.

If you want to jump I embed a codesandbox at the end of an article.

1'st.

His solution (after couple of updates) looked like this:

// View or Component where you want all textareas be auto-resizing
// in my example files name is ResizeByClass.vue
<template>
<textarea class="textarea js-autoresize"></textarea>
</template>
<script>
import { setResizeListeners } from "../helpers/auto-resize.js";
export default {
name: "ResizeByClass",
mounted() {
setResizeListeners(this.$el, ".js-autoresize");
}
};
</script>
// auto-resize.jsfunction resize() {
this.style.height = "auto";
this.style.height = `${this.scrollHeight}px`;
}
export const setResizeListeners = ($el, query) => {
const targets = $el.querySelectorAll(query);
targets.forEach(target => {
target.style.height = `${target.scrollHeight}px`;
target.addEventListener("input", resize);
});
};

I need to say, that is a working solution and it was good for our needs.

What I like about it is that you call the method onces and it works even on components grandchild. All you need to do is add a proper class name to the target (textarea).

Here is few things I don’t like about it:

  • If your textarea components are added dynamicaly (after the loop was fire) it won’t add event listeners to them. Unless somehow, you call the loop again. Then make sure you won’t double event listeners on textareas you already set.
  • Event listeners are never taken off and I think they should be.
    So we’d need to add another loop in destroy hook to take them off.
  • We are waiting for Component to mount before reaching for textareas. This is slow. Vue gives us tools to avoid this.
  • There is a need to remember to add proper class name to every target we want a method to work on. Which is not very clean way, because this class-name is not strictly binded with a Styles. It is binded with a JavaScript. This might lead to class naming collisions.

So, not very optimal solution.

2'nd — mixin

My first thought was that this textarea tag should look like this:

<textarea
class="textarea"
@input="autoResize"
></textarea>
  • This way we set an event listener ourselfs on every textarea we want to.
  • Using Vue’s listeners we don’t have to trouble about removing it (Vue is doing that for us)
  • All we need to remember is not CSS class name but actualy event and method name. Which is more closer to true thing happening.

To achieve that we can use mixin.

// ResizeByMixin.vue<template>
<div class="wrapper">
<textarea
class="textarea"
@input="mixin_autoResize_resize"
></textarea>
</div>
</template>
<script>
import mixinAutoResize from "../mixins/autoResize.js";
export default {
name: "ResizeByMixin",
mixins: [mixinAutoResize]
};
</script>
// autoResize.js
export default {
methods: {
mixin_autoResize_resize(event) {
event.target.style.height = "auto";
event.target.style.height = `${event.target.scrollHeight}px`;
}
},
mounted() {
this.$nextTick(() => {
this.$el.setAttribute("style", "height",
`${this.$el.scrollHeight}px`);
});
}
};

Much cleaner.

Cons?

  • There migh be method name colision (typical for mixins). That is why my method name is so long (`mixin_autoResize_resize`) and includes mixins name, so you know exactly where that method is comming from.
  • Need to add mixin in every component you want textarea to auto resize. Solution to this would be making a global instance method or use provide & inject.

Additional pros?

  • You will add mixin only in components you want textarea to auto resize ;)
  • Will work in dynamicaly added components.
  • We are not waiting for Component to mount before reaching for textareas (targets) to add event listeners. Vue is doing that while creating component. Means it’s faster.

3'rd — renderless component

Similar to mixin we can create reusable, renderless component that will provide us with the same functionality but we can use it in a template.

Usage:

// Wrapper component<template>
<div class="wrapper">
<ResizeAuto>
<template v-slot:default="{resize}">
<textarea
class="textarea"
@input="resize"
></textarea>
</template>
</ResizeAuto>
</div>
</template>
<script>
import ResizeAuto from "./components/ResizeAuto";
export default {
name: "App",
components: { ResizeAuto },
};
</script>

So instead registering mixin we register component ResizeAuto. Which looks like this:

// ResizeAuto.vue
export default {
name: "ResizeAuto",
methods: {
resize(event) {
event.target.style.height = "auto";
event.target.style.height = `${event.target.scrollHeight}px`;
},
},
mounted() {
this.$nextTick(() => {
this.$el.setAttribute(
"style",
"height",
`${this.$el.scrollHeight}px`
);
});
},
render() {
return this.$scopedSlots.default({
resize: this.resize
});
}
};

It doen’t render anything (that is why it is called renderless component) but we use render method to return everything we put inside it (slot). Using scoped slot we provide access to method resize. Which is exactly the same as the one we used in mixin.

Pros?

  • Same as in mixins
  • We set event listener on every textarea we want to.
  • Using Vue’s listeners we don’t have to trouble about removing it.
  • Will work in dynamicaly added components.
  • We are not waiting for Component to mount Vue is adding the event while creating component. Means it’s faster.
  • Unlike in mixins, using this solution you won’t meet many name conflicts. This is because you can always name upcoming scopedSlots differently in the template. There is no need to refactor your code.
  • Going through the templat from up down it is much easier to read and understand what is going on.

Cons?

  • Need to register in every component you want to use it. Solution - would be registering as global component.
  • Harder to use method resize in script part. It can be done but it is harder.

Conclusion

I hope those three solutions gave you at least some perspective on how many approaches there is for solving simple problem.

I’m not the one to tell which one is the best but for most cases using renderless components works for me pretty well. They help me keep my code clean and organized.

As long as I can keep code clean, I stick to that solution.

Promised codesandbox

--

--