DEV Community

loading...

Create a Web Component to keep track of your input's draft with StencilJS

David Dal Busco
Creator of DeckDeckGo | Organizer of the Ionic Zürich Meetup
Updated on ・7 min read

Create a Web Component to keep track of your input's draft with StencilJS


Grab a coffee or tea, open a terminal and let’s code a clever with StencilJS

Earlier this week, I saw a tweet of Ali Spittel who was trying to figure out how GitHub, Twitter and others, were able to keep track of the draft of your inputs respectively how such platforms were saving and restoring the content of your input or textarea before you would actually submit them and if the browser would refresh.

Long story short, James Turner is clever and had a look to the minified source code and discovered that your inputs are saved in the session storage of your browser quickly before its refresh and are loaded once the page is displayed again.

I thought that this discovery was really interesting, specially because I always assumed that this was a browser feature and not something which needed implementation, but also because I thought that this could be an interesting use case for a new Web Component compatible with any modern frameworks, or even without, and that’s why I’m writing this article.

For the purpose of this tutorial, I selected *StencilJS as a compiler, mostly because I’m a bit more experienced with it, as I developed my pet project DeckDeckGo with, but also, to be truly honest, just because I’m in ❤️ with Stencil *😉

Let’s get started

To get started we are going to initialize a new project, a new Web Component. Using a command line, run npm init stencil , pick component for the starter type and enter a name for the project (I used clever-textarea for the purpose of this tutorial).


npm init stencil


select the starter “component”


enter a project name

That’s it, our new project is initialized. We could now jump into the newly created folder, cd clever-textarea , and start the component runing the command linenpm run start in order to test if everything is alright by opening a browser and accessing the url http://localhost:3333 😎


start the local server for the component


access the component

To continue this tutorial, I suggest you to keep the component running, Stencil will automatically pick the changes we are going to make to the code and will trigger a refresh of the browser on new build.

Let’s code

We are now all set, we could begin to code our component 🚀 As we want to develop a clever textarea, I suggest that we begin first by removing the default demo code from the component with the goal to just render a dummy blank textarea. For that purpose, we are going to edit the file src/components/my-component/my-component.tsx as Stencil components are built using JSX and Typescript.

Per default, the component’s name and namespace are set to *my-component respectively mycomponent . For simplicity reason, I’ll stick to these names for this article. If you would create a component you would use in a real project, I advise you to rename these informations *😉

import {Component} from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {

  render() {
    return <textarea></textarea>;
  }
}

The above code render() a textarea which should update our browser as the following:

Saving your inputs before refresh

As James Turner discovered, the trick consists of saving your inputs before the browser would refresh. To do so we could hook the window event beforeunload which we are going to declare once our component is loaded, respectively in one of the lifecycles provided by Stencil.

Furthermore to this, in order to retrieve the current value of our textarea, we could use the Stencil’s reference Element to perform a query on the DOM elements of the host and save its value in the sessionStorage.

Per default, see your component definition *@Component , the component is going to be shadowed, that’s why we are going to use the selector shadowRoot in our query.*

import {Component, Element} from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {

  @Element() el: HTMLElement;

  componentDidLoad() {
    const textarea = this.el.shadowRoot.querySelector('textarea');
    // Save input value before refresh
    window.addEventListener('beforeunload',  (_event) => {
      if (textarea) {
        sessionStorage.setItem(
           'clever-textarea-value', textarea.value
        );
      }
    });
  }

  render() {
    return <textarea></textarea>;
  }
}

Once implemented, you could go back to your browser and have a try. Don’t forget to enter a value in your textarea, perform a browser refresh and observe your session storage, you should now find the value your previously entered.


enter a value in the textarea and refresh the browser


open the debugger and find your value in the session storage

Loading your inputs after refresh

If you are still here, I hope so, and have tested the above steps by yourself, I guess you already know what’s coming next and how to code it 😅

Now that we have save our inputs when the browser refresh, we could hook the loading of the page, retrieve our value from the sessionStorage and display it. As previously, we are going to use the same Stencil lifecycle to perform this operation and to use again the element reference to manipulate the DOM.

import {Component, Element} from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {

  @Element() el: HTMLElement;

  componentDidLoad() {
    const textarea = this.el.shadowRoot.querySelector('textarea');

    // Save input value before refresh
    window.addEventListener('beforeunload',  (_event) => {
      if (textarea) {
        sessionStorage.setItem(
          'clever-textarea-value', textarea.value
        );
      }
    });

    // Retrieve value after refresh
    const previousValue = sessionStorage.getItem(
          'clever-textarea-value'
    );

    if (textarea) {
      textarea.value = previousValue;
    }
  }

  render() {
    return <textarea></textarea>;
  }
}

If you refresh your browser you should now find a pre-filled textarea which should contains the last value you would have entered before refresh.


textarea should be pre-filled with your previous value after refresh

Add the support for multiple inputs in the same page

Well, that’s neat, we were able to save and load the draft of your input, but what would happen if we would use multiple times the same component in a page as we use a unique name to save the entry in the storage? Yes, right, it would be weird and contains a unique value…

To overcome this problem we are going to improve our code to add and use a variable value for our session storage key. For that purpose we are going to add a Stencil properties which expose a public attribute to the component.

import {Component, Element, Prop} from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {

  @Element() el: HTMLElement;
  @Prop() key: string;

  componentDidLoad() {
    const textarea = this.el.shadowRoot.querySelector('textarea');

    // Save input value before refresh
    window.addEventListener('beforeunload',  (_event) => {
      if (textarea && this.key) {
        sessionStorage.setItem(
          this.key, textarea.value
        );
      }
    });

    // Retrieve value after refresh
    const previousValue = sessionStorage.getItem(this.key);

    if (textarea) {
      textarea.value = previousValue;
    }
  }

  render() {
    return <textarea></textarea>;
  }
}

Once the code modified, we could now modify the HTML page we are using for test purpose in order to specify this attribute and even add another component to the page. For that purpose, you could modify src/index.html like the following:

<body>

  <my-component key="clever-textarea-value-1"></my-component>

  <my-component key="clever-textarea-value-2"></my-component>

</body>

As for the JSX code, you could safely remove the previous demo attributes “last” and “first” which comes with the Stencil starter component as we don’t use them in this tutorial.

If we go back to your browser you should now find two textarea respectively two components. You could now try to fill them and again try to refresh your browser.


fill the two components before refresh


after refresh of the browser

Hooray we were able to use two clever textarea Web Components in our page 🎉

In conclusion

Of course the above code would still need a bit of improvements, I would notably separate the code in methods, add some promises, because there are never enough promises 😋, and maybe even clear the storage after having read the value but I hope that this article would have give you some ideas about how to implement such a “clever” input or textarea and furthermore to that, if you never tried Stencil before, made you a bit curious about it because again, this compiler is amazing 🤘

Cherry on the cake 🍒🎂

Web Components could be integrated in any modern frameworks (the Stencil documentation provide examples of framework integration for Angular, React, Vue and Ember) or even without any framework (like I do in the DeckDeckGo, give it a try for your next presentation 👉 npm init deckdeckgo).

To infinity and beyond 🚀

David

Discussion (5)

Collapse
vasanthanaidu profile image
PVNaidu

Hi David Can you please help on this issue...
dev.to/vasanthanaidu/dynamic-unord...

Collapse
daviddalbusco profile image
David Dal Busco Author • Edited

Hi,

First thing I notice in the related code/article you posted, as far as I know, you can't pass an array as property of your component in html (<list-component list-object='[... don't work I think). For complex type you have to use javascript. Here you will find an example on how to do that, see property share: github.com/fluster/web-social-shar...

Then I would remove the @Watch('listObject'), I don't think you could have a watch method with parameters. If you still want to use a watcher, try to remove the parameters of the function and do the job inside it.

If it still not works, maybe I would try to change the componentWillLoad in componentDidLoad

Final thought, I don't think you need a method to manipulate the Dom in order to create ul and li elements, you kind of don't use the power of the tool doing so. I would suggest to just iterate on your listObject, which btw. should be declare as an array (string[]) not a a string, in your render method and just render the tags

Hope that helps, have fun

Collapse
vasanthanaidu profile image
PVNaidu

Hi David,

Thank you for the inputs, its very helpful. let me try to fix.

Collapse
aspittel profile image
Ali Spittel

This is awesome! I love working with web components, and the collaborative reverse engineering has been a lot of fun!

Collapse
daviddalbusco profile image
David Dal Busco Author

Agree with you, loved the reverse engineering and I was also so surprised by the outcome, I truly was convinced that the feature was a built-in browser feature...I love to learn new stuffs, probably why I love Web Components and Stencil ;)