Use reactive Variables Between Components in Svelte - components

I have a question regarding using reactive variables from component3 in my example in component 2. I have two checkboxes in component2 and I am having a reactive array that I want to use in component 2 but it does not quite work the way I want it to work. The repl can be found here: https://svelte.dev/repl/b8109591f22541949309be8404336afa?version=3.49.0
This is the main app
<script>
import Component2 from './Component2.svelte';
import Component3 from './Component3.svelte';
</script>
<Component3/>
<Component2/>
Component 2:
<script>
import a2 from './Component3.svelte';
</script>
{#if a2[0]}
<p>
Hi
</p>
{:else}
<p>
Not Hi
</p>
{/if}
Component 3
<script>
let initial_categories = [
{name: 'SPINS', checked: true},
{name: 'TRAIN', checked: true}
]
let vec_names = [
'SPINS', 'TRAIN'
]
export let a2 = [];
$: {
a2 = [];
for(let i = 0; i < initial_categories.length; i++) {
let stringify = JSON.stringify(initial_categories[i]);
stringify = JSON.parse(stringify)["checked"];
a2 = a2.concat(stringify);
}
}
</script>
{#each initial_categories as { name, checked }}
<label class="cats">
<input type=checkbox bind:group={vec_names} bind:checked={checked} name="Category" value={name}>
{name}
</label>
{/each}
{a2}

This does not work:
<script>
import a2 from './Component3.svelte';
</script>
a2 is simply the constructor of Component3 with a bad name.
You can only export variables that are not bound to component instances from the context="module" script, and those variables will not be reactive, so you need to use stores to preserve reactivity across components.
So something like this in #3:
<script context="module">
import { writable } from 'svelte/store';
export let a2 = writable([]);
</script>
<script>
// ...
$a2 = [];
for(let i = 0; i < initial_categories.length; i++) {
// ...
$a2 = $a2.concat(stringify);
}
</script>
And the import will be named instead of the default in #2:
<script>
import { a2 } from './Component3.svelte';
</script>
{#if $a2[0]}
<p>Hi</p>
{:else}
<p>Not Hi</p>
{/if}
REPL
I would recommend defining a shared state higher in the hierarchy and passing it down to the components instead of doing the above. This can be done via props or via a context. With props the store does not need to used as long as the props are using bind: on the components that modify state.
E.g. in the main component:
<script>
import Component2 from './Component2.svelte';
import Component3 from './Component3.svelte';
let state = [];
</script>
<Component3 bind:state />
<Component2 {state} />
Then just add export let state to the other two.
REPL

Related

How to get the v-model of component that called the function in Vuetify?

I happen to do the form in which each text-field has to cooperate with each other for example:
<template>
<v-app>
<v-text-field v-model="foo1" #input="updateForm">
<v-text-field v-model="foo2" #input="updateForm">
</v-app>
</template>
<script>
export default {
data() {
return {foo1:0, foo2:0}
},
methods:{
updateForm(foo){
foo1=foo1/foo1+foo2
foo2=foo2/foo1+foo2
//Can we get the v-model of foo which called the function to make a special update?? like
// foo=foo/2
}
}
}
</script>
Im using Vue2
Using an array to hold all the values of your inputs and passing the array index to the event handler method is the most common way of solving your problem. With an array you can also utilize v-for to dynamically render your input elements, which cuts down on duplicate code.
<template>
<v-app>
<v-text-field
v-for="(foo, i) in foos" :key="i"
type="number"
v-model.number="foos[i]"
#input="updateForm(i)"
/>
</v-app>
</template>
<script>
export default {
data() {
return {
foos: [0, 0]
};
},
methods: {
updateForm(fooIndex) {
this.foos[fooIndex] += 1;
}
}
};
</script>

Svelte: Store Data Not Being Reactive When Component Changes Data and Vice Versa

I'm sure this is a super easy fix, but I'm having an issue where I setup a writable store, and it's mostly reactive, except when a component changes the data, the reactivity in the App file doesn't fire and vice versa. Here's the code:
App.svelte:
<script>
import { data } from './store.js'
import Component from './Component.svelte'
let localData
data.subscribe((value) => {
localData = value;
});
</script>
<h2>In App.svelte</h2>
<p>Hello {localData.name}!</p>
<input name="name" type="text" bind:value={localData.name}>
<p>
{localData.details.number}
</p>
<h2>In Component.svelte</h2>
<Component />
Component.svelte:
<script>
import { data } from './store.js'
let localData
data.subscribe((value) => {
localData = value;
});
</script>
<input type="number" bind:value={localData.details.number}>
<p>Hello {localData.name}!</p>
<p>{localData.details.number}</p>
store.js:
import { writable } from 'svelte/store'
export let data = writable({
name: 'Bob Smith',
details: {
dob: '1982/03/12',
favoriteFoods: ['apples', 'pears', 'bourbon'],
number: 1
},
})
And, if you want to use it in the Svelte REP: https://svelte.dev/repl/164227336d6c4cc29f7ea0a15e89c584?version=3.44.3
You are subscribing to the data and putting it into a local variable and then bind to that. This means the store does not know that anything changed and updates won't be propagated. Two options:
First option: You get rid of the two way binding and update the store explicitely like this:
<script>
import { data } from './store.js'
import Component from './Component.svelte'
let localData
data.subscribe((value) => {
localData = value;
});
function updateName(evt) {
const newName = evt.target.value;
data.update(value => ({...value, name: newName }));
}
</script>
<h2>In App.svelte</h2>
<p>Hello {localData.name}!</p>
<input name="name" type="text" value={localData.name} on:input={updateName}>
<p>
{localData.details.number}
</p>
<h2>In Component.svelte</h2>
<Component />
This is very explicit but also a bit boilerplate-y. We have Svelte's handy auto subscription feature, so let's use that instead. Second and prefered option:
<script>
import { data } from './store.js'
import Component from './Component.svelte'
</script>
<h2>In App.svelte</h2>
<p>Hello {$data.name}!</p>
<input name="name" type="text" bind:value={$data.name}>
<p>
{$data.details.number}
</p>
<h2>In Component.svelte</h2>
<Component />
Notice how we got rid of all the subscription boilerplate. $data accesses the current state of the store and since it's a writable store you can also write back to it that way. You can read more about stores in the docs: https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values

How to use text between the tags in a svelte component?

Let's say I have two components:
Bold1.svelte:
<script>
external let t="";
</script>
<b>{t}</b}
Usage:
<Bold1 t="my text 1" />
Works like expected.
Bold2.svelte:
<script>
</script>
<b>???</b>
Usage:
<Bold2>
my text 2
</Bold2>
What do I have to write instead of ??? to get a bold my text 2? I have tried <b>{this}</b>, but without success.
Get the slot content.
App.svelte:
<script>
import Child from './Child.svelte';
</script>
<Child>Hi</Child>
Child.svelte
<script>
import { onMount } from 'svelte';
let thisObj;
let text = '';
onMount(() => {
text = thisObj.textContent;
});
</script>
<div bind:this={thisObj}>
<slot />
</div>
<h3>
Slot content-1: {text}
</h3>
Are you trying to pass HTML and have it render as HTML?
If var t has HTML, you can render it like this:
{#html t}
https://svelte.dev/docs
Just watch out for XSS risk.

How to send data from parent to child component in Vue.js

I am new to vue.js and currently I am building an app for learning purposes.
What I want to do:
I have a parent component which has a bunch of buttons with different id's.
The child component will wait for those id's to be sent by the parent and it will decide what to display based on the id. Thats all.
I wont post the full code because it's too large but I have tried a bunch of stuff like props and state but honestly it is so confusing.
I come from React background and I am still confused.
Parent component
<template>
<button id="btn1">Show banana</button>
<button id="btn2">Show orange</button>
</template>
<script>
export default {
name: 'Parent',
data: function {
//something
},
props: {
// ?????
}
};
</script>
**Child component**
<template>
<p v-html="something.text">
</template>
<script>
export default {
name: 'Child',
data: function() {
something: ''
if(id from parent === id I want) {
something = object.withpropIneed
}
},
};
</script>
You need to map the data from parent and pass it to child, thats it!
In example i make passing a html string and binding that html received through 'fromParentHtml' prop mapped on child, so inside child component 'this.fromParentHtml' pass to exists because it is defined in props and every time you click in parent button executes the 'show' function and change the value from passed prop to child through parent 'html' data .. =)
<template>
<div>
Current html sent to child '{{html}}'
<br>
<button #click="show('banana')">Banana</button>
<button #click="show('orange')">Orange</button>
<button #click="show('apple')">Apple</button>
<!-- Create child component -->
<child-component :fromParentHtml="html"></child-component>
</div>
</template>
<script>
export default {
name: "test3",
components: {
'child-component': {
template: "<div>Child component... <br> <span v-html='fromParentHtml'></span> </div>",
//Child component map a prop to receive the sent html from parent through the attribute :fromParentHtml ...
props: {
fromParentHtml: {
required: true,
type: String
}
}
}
},
data(){
return {
html: ''
}
},
methods: {
show(fruit){
this.html = '<span>The fruit is ' + fruit + ' !</span>';
}
}
}
</script>
<style scoped>
</style>
If helped you please mark as correct answer! Hope it helps.
Edit 1:
Assuming you have webpack to work with single file components, to import another component just do:
<template>
<div>
<my-child-component></my-child-component>
</div>
</template>
<script>
//Import some component from a .vue file
import ChildComponent from "./ChildComponent.vue";
export default {
components: {
//And pass it to your component components data, identified by 'my-child-component' in the template tag, just it.
'my-child-component': ChildComponent
},
data(){
},
methods: {
}
}
</script>
Just for the sake of it, I think you were looking for this:
<template>
<button id="btn1" #click = "id = 1">Show banana</button>
<button id="btn2" #click = "id = 2">Show orange</button>
<child-component :childid = "id"></child-component>
</template>
<script>
import childComponent from 'childComponent'
export default {
name: 'Parent',
data () {
return {
id: 0
}
},
components: {
childComponent
}
};
</script>
**Child component**
<template>
<p v-html="something.text">
</template>
<script>
export default {
name: 'Child',
props: {
childid: String
},
data: function() {
something: ''
if(this.childid === whatever) {
something = object.withpropIneed
}
},
};
</script>
Solved my problem by taking a different approach.
I have implemented state and my component behaves exactly as I wanted to.
I found this link to be helpful for me and solved my problem.
Thank you.

Meteor - button click not updating value

I am trying to get a random document in the collection and display it on the page. It is successful every time I load the page, but I want a button to do the work as well.
main.html
<head>
<title>test</title>
</head>
<body>
<h1>Random Question</h1>
{{> question}}
</body>
<template name="question">
<button>Click Me</button>
{{#each object}}
{{question}}
{{a}}
{{b}}
{{c}}
{{d}}
{{answer}}
{{points}}
{{/each}}
</template>
main.js
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';
Resolutions = new Mongo.Collection('quiz');
Template.question.created = function () {
var random = get_random();
this.question = new ReactiveDict();
this.question.set('object', random);
};
function get_random(){
var collection_size = Resolutions.find().count();
var random = Math.floor(Random.fraction() * collection_size);
// choose a random item by skipping N items
var item = Resolutions.findOne({},{
skip: random
});
var objArray = $.makeArray(item);
return objArray;
}
Template.question.helpers({
object: function () {
return get_random();
}
});
Template.question.events({
'click button': function (event, template) {
// increment the counter when button is clicked
var random = get_random();
template.question.set('object', random);
}
});
There is no error message when I load the page or click the button.
Any help is appreciated.
Btw, what is the object inside "this.question.set('object', random);". Maybe that's where my issue is.
You can considerably simplify your code and also solve your problem by not picking a random object in your helper - that will run many times, even when you don't expect it to. Also since you're only viewing a single object, use {{#with }} instead of {{#each }} - this will avoid the array conversion step.
html:
<template name="question">
<button>Click Me</button>
{{#with object}}
{{question}}
{{a}}
{{b}}
{{c}}
{{d}}
{{answer}}
{{points}}
{{/with}}
</template>
js:
import { Template } from 'meteor/templating';
import './main.html';
Resolutions = new Mongo.Collection('quiz');
Template.question.created = function () {
setRandom(); // initialize the random selection
};
function setRandom(){
var collection_size = Resolutions.find().count();
var random = Math.floor(Random.fraction() * collection_size);
Session.set('random',random);
}
Template.question.helpers({
object: function () {
return Resolutions.findOne({},{ skip: Session.get('random') });
}
});
Template.question.events({
'click button': function (event, template) {
setRandom();
}
});

Resources