How to encapsulate state with an update method and a mithril component - javascript

I want to render state that is updated every request animation frame.
I want to encapsulate state with an update method and corresponding component:
But that fails because it's not correct usage of mithril components.
import * as Vnode from 'mithril/render/vnode';
import * as h from 'mithril/hyperscript';
export default function Play() {
// background is another encapsulation like Play
let background = new Background(this);
let data;
this.init = d => {
data = d;
background.init();
};
this.update = delta => {
background.update(delta);
};
this.component = ({
view() {
return h('div.' + data,
[Vnode(background.component)]
);
});
}
Render code:
import mrender from 'mithril/render';
import * as Vnode from 'mithril/render/vnode';
export default function MRender(mountPoint) {
this.render = (component) => {
mrender(mountPoint, Vnode(component));
};
}
Usage:
let mrender = new MRender(element);
let play = new Play();
function step() {
play.update();
mrender.render(Vnode(play.component));
requestAnimationFrame(step);
};
step();
I want state mutations and render code to be in the same place, because state is concerned with view animations.

If I understand correctly you want to be able to manage the internal state of the component while it's being updated by requestAnimationFrame? The following might get you on the right track:
const m = require('mithril');
//Use a closure to manage internal state of component
const play = initialVnode => {
const {
timestamp
} = initialVnode.attrs;
const start = timestamp;
return {
view: vnode => m('ul',[
m('li',`Start: ${start}`),
m('li',`Current timestamp: ${vnode.attrs.timestamp}`),
])
}
};
let reqID;
const step = timestamp => {
if( timestamp ){ //Start animating when timestamp is defined
m.render(document.body, m(play,{
timestamp,
}));
}
reqID = requestAnimationFrame(step);
if( reqID === 60 ){ //Add condition to stop animating
cancelAnimationFrame(reqID);
}
};
step();
I hope that helps.

Related

Can we use expose method to return other reactive variables and computed properties like methods in vue 3?

I am changing my application from vue 2 to vue 3. By using composition api,i have changed my previous render function in that setup hook. After checking some documentation,I got to know that i can expose methods by using context.expose({}).
Now my questions are:
How to replace created method in vue 3 composition api? (As we know, setup hook occurs beforeCreate hook but not able to understand how to do those operations inside setup hook?)
Can we return reactive variables and computed properties by using context.expose?
Here is my setup script:
<script>
import {h,ref,computed,provide} from 'vue';
export default {
name: 'something',
props: some props,
setup(props,context) {
const currIdx = ref(0);
const tabCnt = ref(0);
const idxMap = ref(new Map());
const idxs = ref([]);
// other variables
// computed properties
const $_size = computed(() =>{
// 1. check this.size
if(props.size) {//'medium','small'
if(props.size === 'medium') return 'medium'
if(props.size === 'small' ) return 'small'
}
// 2. check flags
if(props.medium) return 'medium'
if(props.small ) return 'small'
// 3. default value : 'medium'
return 'medium';
});
// [COMPUTED] Props normalize : SHAPE
const $_shape = computed(() =>{
// 1. check this.shape
if(props.shape) { // 'border','underline','none'
if(props.shape === 'border' ) return 'border'
if(props.shape === 'underline') return 'underline'
if(props.shape === 'none' ) return 'none'
}
// 2. check flags
if(props.border ) return 'border'
if(props.underline) return 'underline'
// x. default value : 'border'
return 'border';
});
// [COMPUTED] - [END REGION]
const getLabelNode = (props) => {
var label = props.label, idx = props.idx, disabled = !!(props.disabled || props.disable)
return h('vu-tab-label',{props:{idx, disabled}},[label]);
};
// 2. make window area -> label + navi(?)
var labelWindow = [];
labelWindow.push(h("div",{"class":"vu-tab__label-wrapper","ref":"scroll"}, labels));
if(props.navigation || props.addBtn) {
labelWindow.push(h(tabNavi))
}
// 3. do something
idxs.value = Array.from(idxMap.value.keys());
// 4. make class
let tabClass = [];
tabClass.push("vu_tab-box");
tabClass.push(`vu-tab-box--${this.$_shape}`);
// methods
const onAddClick =(e) => {
context.emit('add-tab',e);
};
context.expose({
onAddClick,
});
// x. return all nodes
return h("div",{"class":tabClass},[
h("div",{"class":"vu-tab__label-window","ref":"window"},labelWindow),
h("div",{"class":"vu-tab__content-wrapper"},contents)
]);
},
}
</script>
For question 1, It is my created hook and i want to do those operations inside setup.
created() {
// 1. Check default index
if( (this.defaultIdx === 0) || this.defaultIdx ) {
this.currIdx = this.defaultIdx;
return;
}
// 2. check slots
var slots = this.$slots['default']
if(!!slots) {
slots.find(vNode => {
if (!vNode.componentOptions) { return false }
var idx = vNode.componentOptions.propsData.idx;
if (idx === undefined) { return false }
this.currIdx = idx;
return true;
});
}
},
created hook in the composition api
This one is simple, there is no created or beforeCreate hook in the composition api. It is entirely replaced by setup. You can just run your code in the setup function directly or put it in a function you call from within setup.
Are properties exposed using expose reactive
Yes. While accessing values of child components using template refs is not really the "Vue"-way, it is possible and the values passed keep their reactivity. I couldn't find any documentation on this, so I quickly implemented a small code sandbox to try it out. See for yourself.
https://codesandbox.io/s/falling-breeze-twetx3?file=/src/App.vue
(If you encounter an error similar to "Cannot use import outside a module", just reload the browser within code sandbox, there seems to be an issue with the code sandbox template)

Can svelte use composable functions?

I am coming from vue and used to composable functions. I am trying to figure out the way to do this in svelte
So I make a js file and import store and then was trying to make a function that I could call on multiple components and act individually
swipe.js file
import { writable, derived, get } from 'svelte/store';
function createSwipe() {
const dyFromStart = writable(0)
function moveEvent(eventType, val){
console.log('moveEvent', eventType, val, get(dyFromStart))
dyFromStart.update(n => n + 1);
}
const dxScore = derived(dyFromStart, $dyFromStart => $dyFromStart + 3)
const dyScore = derived(dyFromStart, $dyFromStart => Math.round($dyFromStart + 100));
return {
moveEvent,
dxScore,
dyScore,
};
}
export const swipe = createSwipe();
then in .svelte component import function in script and decompose into subparts
<script>
import { swipe } from "$lib/swipe";
let { moveEvent, dxScore, dyScore } = swipe
</script>
<p>{$dxScore}{$dyScore}</p>
<button on:click="() => moveEvent">button</button>
Well eventually I want to turn into a swipe component hence name but trying to get fundamentals down. So I want to be able to have unique store for each component and for this if I use multiple of this .svelte component the state is shared amongst all.
And not just like three idk modal.svelte components I want to use swipe for a bunch of diff components maybe a photoViewer.svelte right just generic swipe function and use same code for all.
or would I just have to keep the state like const dyFromStart = writable(0) be just let dyFromStart = 0 in each .svelte component and pass it into a pure js function that returns results and update local .svelte variables
Adding this as the non store more pure js things I was trying but couldn't get to be reactive so accepting the answer below on store method that worked and sounds like is the correct approach
export function createSwipe() {
let dyFromStart = 0
function moveEvent(eventType, val){
console.log('moveEvent', eventType, val, dyFromStart, dxScore(), dyScore())
dyFromStart++
}
function dxScore(){ return dyFromStart + 3 }
// const dzScore = derived(dyFromStart, $dyFromStart => $dyFromStart + 3)
const dyScore = () => Math.round(dyFromStart + 100)
return {
moveEvent,
dxScore,
dyScore,
dyFromStart
};
export function createSwipe() {
let dyFromStart = 0
let dxScore = dyFromStart + 3
let dyScore = Math.round(dyFromStart + 100)
function moveEvent(eventType, val){
console.log('moveEvent', eventType, val, dyFromStart, dxScore, dyScore)
dyFromStart++
dxScore = dyFromStart + 3
dyScore = Math.round(dyFromStart + 100)
}
return {
moveEvent,
dxScore,
dyScore,
dyFromStart
};
I suppose that works fine just not reactive with $ and need to call to update a diff local var if doing that
this would seem most sveltey to me or something like it as far as composable function type style not store type
export function createSwipe() {
let dyFromStart = 0
function moveEvent(eventType, val){
console.log('moveEvent', eventType, val)
dyFromStart++
}
$: dxScore = dyFromStart + 3
$: dyScore = Math.round($dyFromStart + 100)
return {
moveEvent,
dxScore,
dyScore,
};
}
I don't understand the question fully, so I try to reiterate first what I think you want:
You want to use your swipe function in multiple places
Each usage of that swipe function should be independent of all others
If that's correct, then the answer is simple: Don't do export const swipe = createSwipe(). Delete that part and instead export the create function to use directly within your components. That way you create a new independent instance each time:
<script>
import { createSwipe } from "$lib/swipe";
let { moveEvent, dxScore, dyScore } = createSwipe()
</script>
<p>{$dxScore}{$dyScore}</p>
<button on:click="() => moveEvent">button</button>

Strange behavior when generating an id for a React component

I am trying to generate a unique id number for each React component, this id will be assigned to the component when it is mounted.
I am using the code below, the function callOnce takes a function and ensure that the function is only executed once per component.
import React, { useRef } from "react";
// Helper function that makes fn execute only once
const callOnce = (fn) => {
let called = false;
let result;
return (...params) => {
if (called) return result;
called = true;
result = fn(...params);
return result;
};
};
let lastId = 0;
const getId = () => {
console.log("incrementing");
lastId = lastId + 1;
return lastId;
};
export default function App() {
const { current: getIdOnce } = useRef(callOnce(getId));
const id = getIdOnce();
return <div>{id}</div>;
}
When I execute this code, the id that is displayed is 2, but logically it should be 1.
The getId function is only called once (the message incrementing is printed once), so there is no other way that the variable lastId is incremented again.
Why is the value 2 ?
codesandbox link
I not know what the problem, but the fix can be that:
import React, { useRef } from "react";
// Helper function that makes fn execute only once
const callOnce = (fn) => {
let called = false;
let result;
return (...params) => {
if (called) return result;
called = true;
result = fn(...params);
return result;
};
};
let lastId = 0;
const getId = () => lastId++;
export default function App() {
const { current: getIdOnce } = useRef(callOnce(getId));
const id = getIdOnce();
return <div>{id}</div>;
}
If I found the problem reason, I will update my comment ☺
I found the answer in another thread. Basically when using React with React.StrictMode wrapper, some functions are executed twice.
The reason the id is 2 is because the function getId is executed twice
Link of the answer with more details

will export function() be called multiple times in javascript?

I wonder if this
const getData = () => {
const data = 1; // simplified complicated operations
return data;
};
export default getData();
is any performance difference than this:
const getData = () => {
const data = 1;
return data;
};
const toexport = getData(); // cache to a variable before exporting
export default toexport;
The question boils down to how export actually works. I read many articles and I can manage to make it work, but I haven't understood what it does under the hood (couldn't find an article about my question).
What if an export is imported from difference import, will the getData() be called once or being called for each import?
function getData() {
console.log('getData');
return 1;
}
// will `export default getData();` be like this
const importSomeWhere = getData();
const importSomeWhereElse = getData();
// or this?
const exportCached = getData();
const importSomeWhere2 = exportCached;
const importSomeWhereElse2 = exportCached;
It will be evaluated only once. Here's example from What Happens When a Module Is Imported Twice?
In increment.js, we declare a variable called counter
let counter = 0;
counter++;
export default counter;
In consumer.js we import 2 times, but the counter is evaluated once for the first import
import counter1 from './increment';
import counter2 from './increment';
counter1; // => 1
counter2; // => 1

How to look for changes in Map() when item is set or delete?

I am setting up a class for adding items to a shopping cart. The cart items are stored in a Map(). Now I want to save items as cookies, every time the map is changed. So when I call map.set() or map.delete() I want to fire a save function. But I want to call it automatically not in every line of code where "change stuff" is happening.
I already tried to use proxies but as I need to support at least IE11, I cant use it.
import Product from "./product";
export default class Cart {
constructor() {
this.items = new Map();
}
watchForChanges(id) {
// watch if this.item is changed
// call saveItem if is changed
}
saveItem () {
// save item
}
addItem(id, count, callback) {
if (this.items.has(id)) {
this.items.get(id).count += count;
callback( this.items.get(id) );
return true;
}
this.newItem(id, count, callback);
}
newItem(id, count, callback) {
let product = new Product(id);
product.then((resolvedProduct) => {
this.items.set(id, {} = resolvedProduct );
this.watchForChanges(id);
callback(this.items.get(id));
}).catch((reject) => {
Error("Network Error")
});
}
removeItem(id, count = this.items.get(id).count, callback) {
if (!this.items.has(id)) return false;
let newCount = this.items.get(id).count -= count;
if (newCount <= 0) this.items.delete(id);
callback();
}
//some more functions
}
Right now I can call saveItem() every time i change something. How could I create something like a eventListener for setting and deleting items in Map(); ?
After some research:
I found out that babel doesnt have polyfills for extending built in classes like
https://babeljs.io/docs/en/caveats/#classes
class MyMap extend Map {
//...
}
What i do right now is a function for the prototype of Map() and call the a save function... not really nice but it works...
constructor() {
this.items = new Map();
let onupdate = this;
Map.prototype.setItem = function (key, val) {
this.set(key, val);
onupdate.saveItem();
return true;
}
Map.prototype.deleteItem = function (key,) {
this.delete(key);
onupdate.saveItem();
return true;
}
}

Categories

Resources