How can I update a value inside onMount() in Svelte? - javascript

Im trying to change the value of one variable inside onMount, but i cant, this is my attempt, how can I achieve to print, for example this... Here the REPL in svelte.dev
<script>
import { onMount } from "svelte"
let qrActive = false
console.log(qrActive)
const handleQr = () => {
qrActive = !qrActive
}
const qr = (qrActive) => {
if (qrActive) {
console.log("working");
} else {
console.log("Nothing :v")
}
}
$: onMount( () => qr(qrActive))
</script>
<button on:click={handleQr}>
Change!
</button>

onMount only runs once, it cannot be run again.
you might be able to use beforeUpdate or afterUpdate or just reactivity
$: qr(qrActive)
The above code will execute qr everytime qrActive changes

Related

Call a function inside a function and return a value

Is it possible to call a function on click a button, execute some code like adding an item to a database and also having access to a variable that was created in that function?
For example,
const handleAddToDb = () => {
let id = 123423r //note the ID is auto generated each time this function is called
addToDb()
return id
}
<button onClick={()=> {
handleAddToDb()
}}>Click</button>
console.log(id???) // any idea how to achieve this?
<body>
<button onclick="
handleAddToDb()
">
Click me
</button>
</body>
<script>
// let id is a global variable
let id = 10;
const handleAddToDb = () => {
addToDb()
console.log(id)
}
function addToDb(){
id++;
}
console.log(id) // any idea how to achieve this?
</script>
You could have your click handler return the id (as you're doing) and assign it to a variable that's accessible in your outer scope.
You could also establish a "store" that's responsible for managing all of the data, including doing the add and keeping track of the new id. See snippet below for a very skeletal example of how two separate buttons can access the same data using this approach.
const addButton = document.getElementById('add');
const logButton = document.getElementById('log');
class Store {
lastId = 0;
add() {
// do whatever you need to do.
// increment and return the id.
return this.lastId++;
}
}
const store = new Store();
addButton.addEventListener('click', () => {
store.add();
console.log(store.lastId);
})
logButton.addEventListener('click', () => {
console.log(store.lastId);
})
<button id="add">Add</button>
<button id="log">Log Last Id</button>

Why does React rerender when the state is set to the same value the first time via an onClick event on DOM, but not react-native?

I have what I thought would be a simple test to prove state changes, I have another test which does the change by timer and it worked correctly (at least I am assuming so) but this one is trigged by a click event and it's failing my rerender check.
it("should not rerender when setting state to the same value via click", async () => {
const callback = jest.fn();
function MyComponent() {
const [foo, setFoo] = useState("bir");
callback();
return (<div data-testid="test" onClick={() => setFoo("bar")}>{foo}</div>);
}
const { getByTestId } = render(<MyComponent />)
const testElement = getByTestId("test");
expect(testElement.textContent).toEqual("bir");
expect(callback).toBeCalledTimes(1);
act(() => { fireEvent.click(testElement); });
expect(testElement.textContent).toEqual("bar");
expect(callback).toBeCalledTimes(2);
act(() => { fireEvent.click(testElement); });
expect(testElement.textContent).toEqual("bar");
expect(callback).toBeCalledTimes(2); // gets 3 here
})
I tried to do the same using codesandbox https://codesandbox.io/s/rerender-on-first-two-clicks-700c0
What I had discovered looking at the logs is it re-renders on the first two clicks, but my expectation was it on re-renders on the first click as the value is the same.
I also did something similar on React native via a snack and it works correcty. Only one re-render. So it may be something specifically onClick on React-DOM #22940
Implement shouldComponentUpdate to render only when state or
properties change.
Here's an example that uses shouldComponentUpdate, which works
only for this simple use case and demonstration purposes. When this
is used, the component no longer re-renders itself on each click, and
is rendered when first displayed, and after it's been clicked once.
var TimeInChild = React.createClass({
render: function() {
var t = new Date().getTime();
return (
<p>Time in child:{t}</p>
);
}
});
var Main = React.createClass({
onTest: function() {
this.setState({'test':'me'});
},
shouldComponentUpdate: function(nextProps, nextState) {
if (this.state == null)
return true;
if (this.state.test == nextState.test)
return false;
return true;
},
render: function() {
var currentTime = new Date().getTime();
return (
<div onClick={this.onTest}>
<p>Time in main:{currentTime}</p>
<p>Click me to update time</p>
<TimeInChild/>
</div>
);
}
});
ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

How to write a "Delayed" svelte component?

I'm trying to write a Svelte component that hides its slot during the time specified.
For example, this next line should hide the paragraph during 3 seconds
<Delayed mili_time={3000}> <p>some text</p> <Delayed>
My attempt is not working and nothing is being shown (plus the log is shown though no errors are being raised)
I'd love to receive some help, tips or guidance.
The attempt:
<script>
import { onMount} from "svelte";
export let mili_time = 500;
let shown = false;
onMount(
setTimeout(() => {
console.log("delayed!");
shown = true;
}, mili_time)
);
</script>
{#if shown}
<slot />
{/if}
<style></style>
Thanks in advance to anyone reading or answering for getting till the end of the question ^^
your onMount is badly formatted, it takes as an argument a function while you have a function call.
The correct format would be:
onMount(() => setTimeout(....)
This way the function () => setTimeout(...) will be executed.
In your code, the setTimeout function is called and the return value of that function (a reference to the timeout) is passed into onMount
With a 100% native Component:
<delayed-content delay="1000" hidden>
<h1>Hello World</h1>
</delayed-content>
<script>
customElements.define("delayed-content", class extends HTMLElement {
connectedCallback() {
setTimeout(() => {
this.removeAttribute("hidden");
}, Number(this.getAttribute("delay")));
}
});
</script>
You can set a timeout after rendering and use a CSS class to hide the element as follows:
<script>
import { onMount } from 'svelte';
export let timeout = 3000;
export let hidden = false;
onMount(() => {
setTimeout(() => {
hidden = true;
}, timeout);
});
</script>
<div class:hidden>
<p>Some cool content here</p>
</div>
<style type="text/css">
.hidden {
display: none;
}
</style>

How to run a function when a prop updates within the same component?

I have a prop called transcript inside one of my components. It gets updated when I speak a voice intent. I would like to run a function anytime it changes which takes in the transcript as an argument
Here I am trying to do an OnChange={} function inside the span which I have loaded the transcript into but I know this method won't work, it was simply the easiest way of explaining what I wanted to accomplish
import React, { Component } from "react";
import SpeechRecognition from "react-speech-recognition";
import { Button } from 'react-bootstrap';
class Dictaphone extends Component {
highlightOnRead=(transcript, cursor)=>{
console.log("transcript",transcript)
console.log("cursor",cursor.anchorNode.parentNode.id) //we only need the word
if(transcript.includes(" ")){
transcript = transcript.split(" ").pop()
}
if(transcript = cursor.textContent.toLowerCase()){
cursor.style.backgroundColor = 'yellow'; //highlight the span matching the intent
}
cursor = document.getElementById(cursor.anchorNode.parentNode.id).nextSibling.nextElementSibling;
return cursor
};
render() {
const {transcript, resetTranscript, browserSupportsSpeechRecognition} = this.props
var cursor=''
if (!browserSupportsSpeechRecognition) {
return null
}
if(transcript==="notes"||transcript==="cards"||transcript==="home"|| transcript==="settings" || transcript==="read document"){
this.libraryOfIntents(transcript,resetTranscript);
}
return (
<div>
<span id="transcriptSpan"className="transcriptspan" onChange={()=>{
if(this.props.readAlongHighlightState===true){
if(cursor===''){
this.highlightOnRead(transcript,window.getSelection())
}else{
this.highlightOnRead(transcript,cursor)
}
}
}}> {transcript} </span>
<Button variant="outline-dark" onClick={resetTranscript}>Reset</Button>
</div>
)
}
}
export default SpeechRecognition(Dictaphone)
You can use lifecycle method called componentDidUpdate
componentDidUpdate(prevProps) {
if (this.props.transcript !== prevProps.transcript) { // check if your props is changed
// Make your function call here
}
}

Svelte Long Press

I need a long press event to bind to buttons in svelte 3. I want to do this in the least "boilerplaty" way possible.
I've tried with a long press function but this seems a little convoluted and hacky, also seems a little slow.
function longPress(node, callback) {
console.log(node)
function onmousedown(event) {
const timeout = setTimeout(() => callback(node.innerHTML), 1000);
function cancel() {
clearTimeout(timeout);
node.removeEventListener("mouseup", cancel, false);
}
node.addEventListener("mouseup", cancel, false);
}
node.addEventListener("mousedown", onmousedown, false);
return {
destroy() {
node.removeEventListener("mousedown", onmousedown, false);
}
};
}
</script>
<div>
<Video />
{#each Object.entries(bindings) as [id, value]}
<button on:click = {()=>longPress(this,addImage)}> {id} </button>
{/each}
</div>
This works but I'm sure there is a better way.
For this sort of thing I would use an action, which is a function that runs when an element is created (and can return functions that are run when parameters are changed, or the element is destroyed): https://svelte.dev/tutorial/actions
In this case you could create a reusable longpress action, much like your function above, which dispatches a custom longpress event on the target element that you can listen for like a native DOM event:
<script>
import { longpress } from './actions.js';
let pressed;
</script>
<button use:longpress on:longpress="{e => pressed = true}">
longpress me
</button>
export function longpress(node, threshold = 500) {
// note — a complete answer would also consider touch events
const handle_mousedown = () => {
let start = Date.now();
const timeout = setTimeout(() => {
node.dispatchEvent(new CustomEvent('longpress'));
}, threshold);
const cancel = () => {
clearTimeout(timeout);
node.removeEventListener('mousemove', cancel);
node.removeEventListener('mouseup', cancel);
};
node.addEventListener('mousemove', cancel);
node.addEventListener('mouseup', cancel);
}
node.addEventListener('mousedown', handle_mousedown);
return {
destroy() {
node.removeEventListener('mousedown', handle_mousedown);
}
};
}
The advantage of this approach is that you've separated the definition of 'longpress' from the thing that handles it, so the addImage/node.innerHTML logic can be cleanly separated, and you can re-use the action elsewhere in your app.
Full demo, including passing a parameter to the action: https://svelte.dev/repl/f34b6159667247e6b6abb5142b276483?version=3.6.3
I would recommend using 'press' action in svelte-gestures library if you want consistent support on desktop and mobile devices. It allows setting up duration time.

Categories