Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Readonly target in Vue composition API

My "Generateur" component is sending props to my "Visionneuse" component. Everything works fine inthe browser, but I have this message in the console:

Set operation on key "texteEnvoye" failed: target is readonly.

I really have no clue why I get this message, because I pass the prop to a ref. Here are my components: "Generateur"

<template>
  <div>
    <h1>Génération d'une carte de voeux</h1>
    <div class="board">
      <Visionneuse :texteEnvoye="texte" :taille="0.5"/>
    </div>
    <textarea
      :style="'width: 60%; resize:none;height: 100px;'"
      v-model="texte"
      placeholder="Écrivez votre texte ici">
    </textarea>
  </div>
  <div>
    <button
      v-if="lien.length == 0"
      id="boutonObtenirLien"
      v-bind:class="{ enCours: lienEnCours }"
      class="btn first"
      @click="obtenirLien">Obtenir le lien</button>
    <p
      v-if="lien.length > 0">
      Votre carte de voeux est accessible au lien suivant:<br/>
      <a :href="lien">{{ lien }}</a>
    </p>
  </div>
</template>

<script>
import Visionneuse from '@/components/Visionneuse.vue';

import axios from 'axios';

import {
  defineComponent, ref,
} from 'vue';

export default defineComponent({
  name: 'Générateur',
  components: {
    Visionneuse,
  },
  setup() {
    const texte = ref('');
    const lienEnCours = ref(false);
    const lien = ref('');

    function obtenirLien() {
      if (lienEnCours.value) {
        console.log('Je suis déjà en train de chercher!');
        return false;
      }
      lienEnCours.value = true;

      axios.post(`${process.env.VUE_APP_API_URL}/textes/creer/`, {
        texte: texte.value,
      },
      {
        headers: {
          'Content-Type': 'application/json',
        },
      })
        .then((response) => {
          console.log(response.data);
          lien.value = `${process.env.VUE_APP_URL}/carte/${response.data}`;
        })
        .catch((error) => {
          console.log(error);
        })
        .then(() => {
          lienEnCours.value = false;
        });
      return true;
    }

    return {
      texte,
      obtenirLien,
      lienEnCours,
      lien,
    };
  },
});

</script>

And the "Visionneuse"

<template>
  <div class="board">
    <canvas
      ref='carte'
      :width="size.w"
      :height="size.h"
      tabindex='0'
      style="border:1px solid #000000;"
    ></canvas>
  </div>
  <div id="texteRemplacement" v-if="petit">
    <p v-for="p in texte.split('\n')" v-bind:key="p">
      {{ p }}
    </p>
  </div>
</template>

<script>

import {
  defineComponent, onMounted, ref, reactive, nextTick, toRefs, watch,
} from 'vue';

export default defineComponent({
  name: 'Visionneuse',
  props: ['texteEnvoye', 'taille'],
  setup(props) {
    const myCanvas = ref(null);
    const carte = ref(null);
    const { texteEnvoye: texte, taille } = toRefs(props);

    const rapport = ref(0);
    const petit = ref((window.innerWidth < 750));

    const size = reactive({
      w: window.innerWidth * taille.value,
      h: window.innerWidth * taille.value,
    });

    function drawText() {
      const fontSize = 0.05 * size.w - 10;
      myCanvas.value.font = `${fontSize}px Adrip`;
      myCanvas.value.textAlign = 'center';
      myCanvas.value.fillStyle = 'lightgrey';
      myCanvas.value.strokeStyle = 'black';
      myCanvas.value.lineWidth = 0.006 * size.w - 10;
      const x = size.w / 2;
      const lineHeight = fontSize;
      const lines = texte.value.split('\n');
      for (let i = 0; i < lines.length; i += 1) {
        myCanvas.value.fillText(
          lines[lines.length - i - 1],
          x,
          (size.h * 0.98) - (i * lineHeight),
        );
        myCanvas.value.strokeText(
          lines[lines.length - i - 1],
          x,
          (size.h * 0.98) - (i * lineHeight),
        );
      }
    }

    function initCarte() {
      const background = new Image();
      background.src = '/img/fond.jpeg';
      background.onload = function () {
        rapport.value = background.naturalWidth / background.naturalHeight;
        size.h = size.w / rapport.value;
        nextTick(() => {
          try {
            myCanvas.value.drawImage(background, 0, 0, size.w, size.h);
          } catch (e) {
            console.log(`ERREUR DE CHARGEMENT D'IMAGE: ${e}`);
          }
          if (!petit.value) {
            drawText();
          }
        });
      };
    }

    function handleResize() {
      size.w = window.innerWidth * taille.value;
      size.h = size.w / rapport.value;
      petit.value = window.innerWidth < 750;
      initCarte();
    }

    window.addEventListener('resize', handleResize);

    watch(texte, (_, y) => {
      texte.value = y;
      initCarte();
    });

    onMounted(() => {
      const c = carte.value;
      const ctx = c.getContext('2d');
      myCanvas.value = ctx;
      initCarte();
    });

    return {
      myCanvas,
      size,
      texte,
      petit,
      carte,
    };
  },
});

</script>
like image 910
djcaesar9114 Avatar asked Jan 24 '26 20:01

djcaesar9114


1 Answers

I know you answered your own question. For the 'why', you aren't supposed to change props in the composition API, because props are used to pass reactive data from parent components to child components. The pattern is: events from child to parent, mutation from parent to child . toRef makes the data reactive, but it doesn't affect whether you can mutate it. So if you go:

const texteEnvoye = toRef(props, 'texteEnvoye');
texteEnvoye.value='foo'; // not allowed - texteEnvoye.value is read-only

if you go:

const texteEnvoye = ref('');
const texteEnvoyeRo = toRef(props,'texteEnvoye'); // react to prop
watch(texteEnvoyeRo, (value) => {
  textEnvoye.value = texteEnvoyeRo.value; // OK, textEnvoye is yours
});

now texteEnvoye is yours, and you can mutate it, and react to changes in textEnvoyeRo.

like image 133
Aaron Newman Avatar answered Jan 26 '26 12:01

Aaron Newman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!