Setup custom bindings + altering data with js (VueVanilla)

I’m currently working on a custom string control renderer, and I’m facing a challenge with binding “” to the input and altering it with JavaScript. The goal is to have a simple string input that can trigger additional actions, such as searching for and directly connecting to existing options.

Initially, I successfully used :value for the input, but now I’ve introduced a buffer data structure to have more control over the data. When the user changes the buffer value, it should be assigned to I want to provide the option to clear the input with a button click.

I’ve attempted various approaches, including setting directly, updating the input value with document.getElementBy(), and the current approach, but unfortunately, none of them seem to work.

I’d appreciate your support and insights on how to set the output of my custom component. I’m open to suggestions on whether to bind it directly to the input or to provide the option to override the text input, including clearing it with a button click.

Additionally, if you have experience or knowledge of potential issues with the JsonForms architecture or the onChange method in this context, please share your insights.

Thanks in advance

  <div class="input-group me-3">
        <button v-if="!refToggle" class="btn btn-outline-warning input-group-prepend" data-bs-toggle="tooltip"
          data-bs-placement="bottom" title="Link to existing Resource or create new" @click="refToggle = true">
          <i class="fas fa-link"></i> Connect
        <button v-if="refToggle" class="btn btn-outline-primary input-group-prepend" data-bs-toggle="tooltip"
          data-bs-placement="bottom" title="Link to existing Resource or create new" @click="connectRef()">
          <i class="fas fa-link"></i> Connect Existing
        <button v-if="refToggle" class="btn btn-outline-success input-group-prepend" data-bs-toggle="tooltip"
          data-bs-placement="bottom" title="Link to existing Resource or create new" @click="createRef()">
          <i class="fas fa-plus"></i> Create New
        <button v-if="refToggle" class="btn btn-outline-danger input-group-prepend pe-3" data-bs-toggle="tooltip"
          data-bs-placement="bottom" title="Link to existing Resource or create new" @click="refToggle = false">
          <i class="fas fa-times pe-1"></i>
        <input :id="path + '-input'" class="form-control mb-3" type="text" :value="userInputBuffer.value" :disabled="null"
          :autofocus="appliedOptions.focus" :placeholder="appliedOptions.placeholder" @focus="isFocused = true"
          @blur="isFocused = false" @change="onChange" />
        <button v-if="userInputBuffer" class="btn btn-outline-danger input-group-append border-end"
          data-bs-toggle="tooltip" data-bs-placement="bottom" title="Remove current Reference" @click="removeRef()">
          <i class="fas fa-times pe-1"></i> Unlink

 setup(props) {
  const { control, handleChange } = useJsonFormsControl(props);
  const adaptTarget = (target) => target.checked;

  const {
  } = useVanillaControl({ control, handleChange }, adaptTarget);

  const { data } = toRefs(;
  const userInputBuffer = ref(data);

  watch(() => data.value, (newValue) => {
    if (userInputBuffer.value !== undefined) {
      userInputBuffer.value = newValue;

  watch(() => userInputBuffer.value, (newValue) => {
    if (userInputBuffer.value !== { = newValue;

  onMounted(() => {

  return {

  removeRef() {
      userInputBuffer.value = ""; = "";

Hi @nikohart,

Conceptually JSON Forms uses a reducer approach: It has a form wide state which is manipulated via actions. Based on the props coming in via dispatching and the form wide state, the respective control props are determined via the bindings, i.e. useJsonFormsControl.

Therefore in JSON Forms, the incoming props are “read-only” and should not be modified.

The handleChange is some sugar around dispatching a data modifying action. It will modify the value at the given path, and then each respective renderer will get a new if it was affected.

Therefore, if you want to maintain an own data state “buffer” in your renderer then:

  • It should be initialized with the value of
  • Whenever changes, your buffer should likely also change. Note that this is only necessary if other renderers change the very same data or someone outside the form has the power to change the form wide data. If that is not a use case, then this watching is not really needed as you control the data anyway
  • Whenever you want to “register” a change, i.e. store it in the form-wide state, potentially trigger other renderers and emit the change event to the user of the form, then you call handleChange with the value which you want to see in the form-wide data store.

Some specifics in regards to the code:

  • There is never a need to call handleChange because data.value changed. data.value is the value coming out of the form-wide store. So calling handleChange with it just re-sets the same value again
  • handleChange expects two parameters, the path and the value. You can get the path from the control, e.g. handleChange(control.value.path, newValue).
    • To make this even more convenient for the vue-vanilla renderers, there is another sugar onChange on top. It does nothing but calling handleChange already with the control.value.path and the adaptTarget handed over to the useVanillaControl composition.
  • If you want to implement a buffer between JSON Forms with your own data storage in your renderer, then the @change in your templates must be bound against your data buffer. It should not call onChange from JSON Forms as then you are again immediately handing over the data to JSON Forms which will trigger the form-widet data update and event emission.
  • Then whenever you are ready to “commit” your data, you can then either use handleChange or onChange as outlined above.