svelte prop value not working with if statement - javascript

this is my svelte component code
<script>
export let canCascade = true;
let show = true;
function cascade() {
if (canCascade) {
show = !show;
}
}
</script>
{#if show}
<div class="shade" on:click|self={cascade}>
shade
</div>
{/if}
When I use the component as <Component canCascade=false /> the 'if block' doesn't work.
But hard-coding the value inside just works fine.
Am I missing something here - some conceptual error?

Like #Corrl pointed out in the comments, you need to use {brackets}. If you don't, the variable will follow the rules of an html attribute.
Working repl https://svelte.dev/repl/d13df678eab243e9a13fb705da197219?version=3

In Svelte when we want to pass JavaScript value / expression to an attribute of a component we need to wrap the value / expression with curly brackets {}.
Otherwise, it will be used as a string.
As an example, take a look at the following code:
Component.svelte:
<script>
export let test = true;
$test: console.log(`typeof test = ${typeof test}`);
</script>
App.svelte:
<script>
import Component from "./Component.svelte";
</script>
<Component test=true />
<div />
When you will open the console inside the browser developer tools,
the output will be:
typeof test = string

Related

Can you use functions from an imported JavaScript library such as Change Case directly in a Vue component's template?

I understand how to import and use Change Case within the <script></script> element of a Vue component, which is just the standard Javascript import covered in the Change Case Github page. However, I would like to use the Change Case functions directly in the template if possible.
Currently, it is my understanding that for dynamic content in the template, in this case generated by v-for running through an array, I must render the return value of a intermediary method from the component's methods section which applies the Change Case function. A method is required for each case type (e.g. camelCase, snakeCase, etc.) I want to render, in this instance one (capitalCase). For example:
// ...
<div
v-for="location in locations"
:key="location.name"
>
<input
type="checkbox"
:id="`select-${location.name}`"
:value="capitalCaseLocationName(location.name)"
v-model="locationsInput"
/>
<label :for="`select-${location.name}`">
{{ capitalCaseLocationName(location.name) }}
</label>
</div>
// ...
methods: {
capitalCaseLocationName(name) {
return capitalCase(name)
}
},
// ...
It would be preferable to somehow import Change Case into the template logic so I could write it like this (no intermediary methods needed):
// ...
<div
v-for="location in locations"
:key="location.name"
>
<input
type="checkbox"
:id="`select-${location.name}`"
:value="capitalCase(location.name)"
v-model="locationsInput"
/>
<label :for="`select-${location.name}`">
{{ capitalCase(location.name) }}
</label>
</div>
// ...
Any chance of that being possible?
As long as you register the imported function as a method you should be able to use it directly in the template.
According to the code, you use Options API, so something like this should do the trick:
import {capitalCase} from "change-case";
...
methods: {
capitalCase,
myOtherMethod () => {...}
}
...
And in the <template>:
<input
type="checkbox"
:id="`select-${location.name}`"
:value="capitalCase(location.name)"
v-model="locationsInput"
/>
The functions need to be defined and passed to the template, that is why even console.log won't work from a template.
You already have an answer with an example, but here's another thing you could do that might make things easier.
You can create a helper like this:
template-helpers.js
export function capitalCase(str) {
return str.split(" ").map(wrd => wrd[0].toUpperCase() + wrd.slice(1)).join(" ")
}
export default {
capitalCase
}
this would make it so that you could use it in a composition/setup like this
import templateHelpers from "../utils/template-helpers.js";
setup(){
return{
...templateHelpers
}
}
in an options API component you could just include it like this
import templateHelpers from "../utils/template-helpers.js";
// ...
methods: {
...templateHelpers,
// other methods
}
// ...
Example
by exporting functions in export default you can destructure them by using methods: { ...templateHelpers
the downside is that it would all the methods every time, but it would make for a more convenient solution. Alternatively, you can pick and chose, since the functions are also exported
import {capitalCase} from "../utils/template-helpers.js";
// ...
methods: {
capitalCase,
// other methods
}
// ...
Vue does have a way to add global definitions, but it's discouraged. This would be done by assigning it to config.globalProperties
https://vuejs.org/api/application.html#app-config-globalproperties
app.config.globalProperties.capitalCase = (str) => {
return str.split(" ").map(wrd => wrd[0].toUpperCase() + wrd.slice(1)).join(" ")

Template Literals - Changing the Inner HTML of an element (onInput event)

So I´ve never used Template Literals before, but I need to work with a template now that seemingly includes a form with Template Literals
This is one of my inputs:
<input
type="number"
className="mf-input "
min="1900"
max="2099"
step="1"
id="curyear"
name="currentyear"
placeholder="${ parent.decodeEntities(`Current Year`) } "
onInput=${parent.handleChange}
aria-invalid=${validation.errors['currentyear'] ? 'true' : 'false'}
ref=${el => parent.activateValidation({"message":"This field is required.","minLength":1900,"maxLength":2099,"type":"by_character_length","required":false,"expression":"null"}, el)}
/>
<${validation.ErrorMessage}
errors=${validation.errors}
name="currentyear"
as=${html`<span className="mf-error-message"></span>`}
/>
What I am trying to do is, in the onInput method, instead of handling the validation, I also want to change the innerHTML of an element:
<h2 class="elementor-heading-title elementor-size-default" id="curyeartext">Current Year</h2>
I´ve been trying to do it for hours, but I can't get it to work.
EDIT: turns out, they are template literals and not reactJS
Avoid setting innerHTML inside React, use state instead. This is because React will overwrite your modified DOM when it re-renders, if the html is in a node that React is controlling.
export default function MyReactComponent() {
var [ currentInput, setCurrentInput ] = React.useState();
return <>
<input
type="number"
className="mf-input"
min="1900"
max="2099"
step="1"
name="currentyear"
onInput={(e) => setCurrentInput(e.target.value)}
value={currentInput}
/>
<h2 class="elementor-heading-title elementor-size-default" id="curyeartext">
{currentInput}
</h2>
</>;
}
However, if you have a situation where it is unavoidable, you can tell React to ignore a node by only specifying a ref on it - i.e. no other props or child JSX:
export default function MyReactComponent() {
return <div ref={divRef => {
divRef.innerHTML = "Hello <b>world!</b>";
}} />
}
This technique is typically used when integrating non-React specific JS libraries into React, as you can do whatever you want with the contents of that DOM node.
I managed to do this by adding a function above the return:
function myfunction() {
document.querySelector('#curyeartext').innerHTML = document.querySelector('#curyear').value;
document.querySelector('#lastyear').innerHTML = document.querySelector('#curyear').value-1;
document.querySelector('#yearbefore').innerHTML = document.querySelector('#curyear').value-2;
}
And call it in the onInput event handler
onInput=${myfunction}

Dynamically made strings for id in JSX

I want to make id like this dynamically.
<div id="track_1"></div>
<div id="track_2"></div>
So I gave the id like this from parent component.
export default function Components(props) {
return (
<AudioTrack trackNum="1"/>
<AudioTrack trackNum="2"/>
)
}
then in my AudioTrack Component I got the trackNum and want to use like this
const AudioTrack = (props) => {
return(
<div id="track_{props.trackNum}" ></div>
);
}
Howeber it doesn't work.
Is there any good way?
Since the div prop isn't a constant string, you need {} to indicate an expression, and then either use + to concatenate or a template literal with ${}:
<div id={`track_${props.trackNum}`}></div>

Svelte each block rendering only the last item

I'm trying to make a tag input component in svelte and I've encountered a strange #each block behavior. I have the following svelte code
<script>
import Input from './Input.svelte'
import Tag from './Tag.svelte'
export let tagValues = []
let currentTag = ''
function handleEnter({ key }) {
if (key === 'Enter' && currentTag) {
if (tagValues.indexOf(currentTag) < 0) {
tagValues = [...tagValues, currentTag]
}
currentTag = ''
}
}
</script>
<div class="relative">
<Input bind:value={currentTag} on:keyup={handleEnter}></Input>
{#each tagValues as t (t)}
<Tag value={t}></Tag>
{/each}
{JSON.stringify(tagValues, null, 2)}
</div>
The idea is to get the input value when the user hits Enter key and add that value as a tag if it isn't present already. Input component behaves the same as input Html element. Tag component only has a value property which is text, this should render all the values of the tagValues array, but only shows the last one. I can confirm that the array includes correctly new tags, because I pasted a stringify version of it to HTML. This is how it looks with 1 tag
And this is with two tags
How can I make this component to render all tags? I have tried to make my each block keyed but nothing changed.

Declaring function inside class component vs inside functional component

I am playing with function syntax inside and outside class components. Can anyone explain to me why the print function works when written like this
const UploadButton = (props)=> {
const fileName = 'myfile';
props.getFileName(fileName)
function print(){console.log('onClick worked')}
return(
<div>
<input onClick= {print()} type="file" id = {fileName}/>
</div>
)
}
but when i write it like i would when declaring it inside a class component:
print(){console.log('onClick worked')}
i get this error
Line 10: Parsing error: Unexpected token, expected ";"
8 | props.getFileName(fileName)
9 |
> 10 | print(){console.log('onClick worked')}
| ^
This behavior is not tied with React but fundamentally is a method vs. function thing in JavaScript.
When you declare functions with some context it becomes a method. So, In a class setup, the functions are actually methods.
In Javascript, it is possible to declare a function within another function, that is why this works
const UploadButton = (props)=> {
const fileName = 'myfile';
props.getFileName(fileName)
function print(){console.log('onClick worked')}
return(
<div>
<input onClick= {print()} type="file" id = {fileName}/>
</div>
)
}
But when you don't specify the function keyword and the declaration is not inside of class it throws error.
print(){console.log('onClick worked')}
Parsing error: Unexpected token, expected ";"
If you rather used an arrow function here print=()=>{console.log('onClick worked')}, it would work because its a function expression and is treated as a normal variable declaration scoped to the enclosing function.
print(){console.log('onClick worked')}
I think when you write this in a functional component the compiler does not know that you are trying to define a function and it is rather trying to execute the print function, hence it is expecting a ';'
However, In class based components when you define a function using the above syntax, when the class is converted to a function, the print method will be added to its prototype.
One issue that you're having with your functional component is that you're calling the print function, then passing whatever it returns, which is undefined in this case, to the onClick handler of your input element.
Your JSX for the input element should look like this:
const UploadButton = (props)=> {
// ...
return (
<div>
<input onClick={print} type="file" id={fileName}/>
</div>
)
}
When dealing with class components, however, your UploadButton component, should look like the following:
class UploadButton extends React.Component {
print() {
console.log('onClick worked')
}
render() {
// ...
this.props.getFileName(fileName)
return (
<div>
<input onClick={this.print} type="file" id = {fileName}/>
</div>
)
}
}
Also, you probably shouldn't be using an input element as your UploadButton. Just use a button element instead, something like the following example:
<form>
<div>
<label for="file">Choose file to upload</label>
<input type="file" id={fileName} />
</div>
<div>
<!--Should be something along the lines of `this.handleSubmit`
rather than `this.print`, but you get the idea-->
<button onClick={this.print}>Submit</button>
</div>
</form>

Categories