Passing props to child component Cyclejs - cyclejs

I m studying CycleJs and I m looking for a proper way to handle passing props to child component.
Actually, I m having the following stuff :
import {div, input} from '#cycle/dom'
export function App(sources) {
const inputOnChange$ = sources.DOM.select('input').events('input')
const streamofResult = inputOnChange$
.map(e => e.target.value)
.startWith('')
.map(defaultInput => {
const title = Title({value: defaultInput})
return div([
title,
input({attrs: {type: 'text'}})
])
})
const sinks = {DOM: streamofResult}
return sinks
}
export function Title(sources) {
return div(sources.value)
}
It simply allows to make some inputs, and to display it in a child component called Title.
I think I should use a stream to handle passing props to my child.
But I don't understand why it would be a better solution in this simple to use a stream instead of a primitive ?
There is something that I probably have not understood.

You haven't misunderstood anything. There is no right answer. If you know for a fact you'll never want to change the props after initialization then you could pass the props as a primitive, but the more common convention is to send a props$ since it's not much costlier to do something like O.of(x) vs x (assuming RxJS) and using streams everywhere is consistent with the philosophy of the framework. Additionally, there are occasions when you'll want to change the properties dynamically after component initialization, where a stream is appropriate.
Keeping a consistent props or props$ convention for all your components can make reading the code easier since you won't have to think, "Does this component use a primitive or a stream for props...?"

Related

OOP in Svelte and its reactivity

I wanna use svelte for a little app that Im making. The app was half finished using plain html/css/js when I stumbled upon Svelte.
I was using a lot of javascript classes and aimed for object oriented programming.
Now looking at svelte, it looks like its not made for OOP at all. Am I wrong? Properties of classes wont be tracked and updated by svelte.
Maybe my approach is wrong. I basicly used a View/Model pattern, where I have a model class object that Im feeding the svelte component. Using the object's properties in html wont update obviously. (This works great with angular i.e.)
<script lang="ts">
import type { Key } from "../key";
export let key: Key
const onTrigger = () => {
key.trigger()
}
const onRelease = () => {
key.release()
}
</script>
<div
class="key"
class:black={key.note[1] == '#' || key.note[1] === 'b'}
class:pressed={key.isPressed}
on:pointerdown={onTrigger}
on:pointerup={onRelease}
on:pointerleave={onRelease}
style={key.isPressed ? 'transform: scale(1.5); transform-origin: center;' : ''}>
<div class="key-mapping">{#html key.mapping.toLocaleUpperCase() + '<br/>' }</div>
<div class="key-note">{ key.note + (key.octave ? key.octave.toString() : '') }</div>
</div>
(Key represents a piano key sitting inside a piano component, things like key.isPressed or key.octave wont update, because they are changed in the model class)
Demo here
I really dont wanna use the store for ALL properties of my classes that I use in html, because I think this is not the purpose of the store. I was hoping to save some code by using Svelte and not make it weird and complex.
I saw the trick to reassign the whole object like this
object.property = 'some value'
object = object
to trigger reactivity, but this wont work when changing the properties outside of the component.
Also using the reactive marking $: { ... } I wasnt able to update any class' property (Only when changing it directly from a html event)
Also saw a decorator function to make classes reactive to svelte, but the decorator makes the class singleton too, which makes it useless to me.
So there are a few questions I wanna ask:
Is there any proper way to update class properties in Svelte?
If not, whats the prefered coding style? Functional?
Will there be OOP support in the future?
You don't need dummy assignments as soon as you assign to a property (rather than invoking a method) and there is no issue with using classes as long as you do not "hide" changes from Svelte's compiler.
E.g. this will work just fine (inside the component):
const onTrigger = () => {
key.isPressed = true;
}
const onRelease = () => {
key.isPressed = false;
}
In general your components should be fairly specialized so they do not have to deal with deeply nested data and complex modifications which makes it easy to lose reactivity.
Ideally you just have some very simple local state via component properties rather than objects. Here your Key component should just use properties for all its state e.g. isPressed should just be a property that then can be bound on the level of the parent component.

VueJS 3 create/pass hyperscript to template at runtime?

I wanted to try something for performance/convenience purposes, I understand the gains will be minimal but understanding how/if/why this works would also just be helpful to learn.
I have a some custom data types (defined as classes) that are used to identify certain properties throughout my application. I want to use a static function on the type to define a display function. (stripped down) Example:
class Email extends String{
static display = (value) => {
return `<a href='mailto${value}'>${value}</a>`;
}
}
Call it like you do:
Email.display("test#test.com");
And that works in the template, so long as it’s in a v-html attribute. This is perfectly acceptable.
It’s probably important to specify I’m working with Vue-CLI and single-file components, so all that sweet hyperscript gets created at compile time.
But it got me thinking, is there a way I can pass a freshly-created hyperscript to the template at render? Preferably in a way that works in the {{mustache}} if at all possible.
I tried doing it with h but that just displays the ol’ [object Object].
class Email extends String{
static display = (value) => {
return h('a', {innerHtml: value});
}
}
Update: also tried
I thought maybe going around the Vue render functions could get the job done, but they don't seem to like document fragments either.
static display = (value) => {
var fragment = document.createDocumentFragment();
var a = document.createElement('a');
a.textContent = value;
fragment.appendChild(a);
return fragment;
}
Question
Is there a way create hyperscript at runtime and utilize it in a vue template? Bonus points if it works in {{mustache}} and v-html.
Generally component templates and render functions that use JSX or h (hyperscript) are mutually exclusive.
It's really possible to do this, in this case display is actually functional component, and it needs to be output as any other dynamic component:
setup() {
const display = (props) => {
return h(...);
};
return { display };
}
and
<component :is="display" :value="..."/>
The return of display is a hierarchy of vnode objects, they can't be used as is in v-html without being previously rendered to HTML.

Vue.js extend component and data updates

I'm using vue.js extends for the first time. I have a component that extends another and it needs to read the root components data to update the status in its own component.
What I'm finding is that the component that extends the other only seems to take a copy of the root's data when it's rendered but if I update a property in the root component it's not updated in the extended component.
So I might not be going about this the right way if the extended component doesn't update when the root does. For example I want to check the length of an array on the root component and update another data value. It updates the value on the root but not on the extended component.
Is this the expected behaviour and is there a way I can send the updated data down to the extended component?
Sample code:
<a inline-component>
<input type="text" v-model="myArray" />
<button v-on:click="saveData">Save</button>
</a>
<b inline-component>
<div v-if="myArray.length > 0">On Target</div>
</b>
var a = Vue.component('a', {
data: function () {
return {
myArray: [],
}
},
methods: {
saveData : function(){
var vm = this;
axios.post('/save', {
})
.then(function (response) {
vm.myArray = response.data;
})
.catch(function (error) {
console.log(error);
});
},
}
});
Vue.component('b', {
extends: a,
});
I have a component that extends another and it needs to read the root components data to update the status in its own component.
For the purposes of my answer I'm going to assume that you have two component definitions, A and B, and B extends A. I assume that when you say root you just mean A.
What I'm finding is that the component that extends the other only seems to take a copy of the root's data when it's rendered but if I update a property in the root component it's not updated in the extended component.
Rendering is not really relevant here. The data properties are set up when a component instance is created. Typically rendering will happen just after creation but merging any data happens much earlier in the component life-cycle. Even if the component isn't rendered the data will still be initialised.
No copying takes place. Let's consider a data function on component A:
data () {
return {
myArray: []
}
}
Every time this function is invoked it is going to return a new object, each containing a new array. This is precisely what happens if you create an instance of A directly. For each instance, Vue will call this function and get a new object defining the data. Generally that's what you'd want, rather that having components sharing data.
Now let's consider B. That might define its own data function. When an instance of B is created Vue will call the data function for both A and B and then merge the objects. No copying takes place, just merging. If you want to know more about how Vue handles merging in general see the documentation but for data the strategy is pretty simple. Properties from both objects will be combined with B taking precedence over A if there's a clash of property names. There is no recursive merging of properties.
So the idea of updating 'a property in the root component' is not particularly well-defined. You might be thinking of it as a bit like a prototype chain, where modifying a superclass would impact the subclass, but that isn't what's going on here. The data functions are invoked when the component is created and that's that. There isn't a lasting link back to the component definition like there is with a prototype chain.
If you really want all your component instances to share the same data value then it can be done, you just need to make sure that the data function is returning the same object/array every time. e.g.
const myArray = []
export default {
name: 'A',
data () {
return {
myArray
}
}
}
Written this way all instances of A will share the same array for myArray. So long as B doesn't define it's own value for myArray it will share it too.
For example I want to check the length of an array on the root component and update another data value. It updates the value on the root but not on the extended component.
I'm struggling a bit to understand what that means. It seems there are lots of assumptions about things being shared, single instances here. It's not entirely clear how you update the 'root' given it's a component definition and not a component instance.
If possible you should use a computed property for this. That would be inherited by B. Each instance of A (or B) would have their own value for this computed property, which might be a little wasteful if they're all going to be the same, but it's probably still the best way to go.
You could in theory use a watch. That should be inherited too but keep in mind it would be manipulating values for that particular instance.
Reading between the lines a little, if you wanted to update something on the 'root' so that it magically appeared in the subcomponents you could use the same shared reference-type trickery that I demonstrated earlier for myArray. You may need to be careful with how you update it though. If, for example, you used a watch you might find the you end up updating the same object many times, once for each instance of the component.
Update:
Based on the code you've posted it could be made to work something like this:
var myArray = [];
var a = Vue.component('a', {
data: function () {
return {
myArray: myArray // Note: using the same, shared array
}
},
methods: {
saveData : function(){
var vm = this;
axios.post('/save', {
})
.then(function (response) {
// Note: Updating the array, not replacing it
var myArray = vm.myArray;
myArray.splice(0, myArray.length);
myArray.push.apply(myArray, response.data);
})
.catch(function (error) {
console.log(error);
});
},
}
});
Vue.component('b', {
extends: a,
});
Your example didn't include any ES6 so I've refrained from using it but it would be a bit simpler if that were available.
The example above works by sharing the same array between all instances of the component and then mutating that instance. Assigning a new array to that property won't work as it would only update that particular component instance.
However, all that said, this is increasingly looking like a case where you should give up on trickery and just use the Vuex store instead.

Large components as sections in VirtualizedList/etc?

If I want to display a bunch of heterogenous data in a virtualized list, it seems like the default way to do it is have the parent component gather up all the data so that it can create the sections to supply to the list component.
Is there any way to avoid requiring the parent component from doing this? I'd like to decouple the parent component from the data gather part, so that all it has to do is declare it has such and such components, and then those components would be responsible for gathering the data.
This would be exceedingly simple if it were a ScrollView:
<ScrollView>
<SectionA>
<SectionB>
<SectionC>
</ScrollView>
However, to leverage the performance gains of a VirtualizedList, if each section is large, I would need to pass in the individual data of each section into the VirtualizedList. I'm not sure how to do this or if it's possible.
Not sure if this is idiomatic or a gross React anti-pattern, but the way I solved it was to implement each section as purely headless data Component.
export type SectionDataComponentProps = {
onDataChanged?: () => void, // call this whenever the data updates.
}
export class SectionDataComponent<P : SectionDataComponentProps, S, ItemT> extends React.PureComponent<P, S> {
// Implemented by subclasses
getSectionData() : Array<SectionT<ItemT>> {
// returns an array of sections for a SectionList...
}
render() {
// business logic component only.
return null;
}
}
The parent component keeps track of them through the use of ref, and then calls getSectionData() as needed.

Sending static props to component via selector, best practice

I sometimes have need to send static props to a component, but the data actually comes from my Redux store. I.e. I need a access to state to fetch the data.
With static, I mean that this data won't change during the life of the component, so I don't want to select it from the store on each render.
This is how I solved it at first (the mapStateToProps part):
(state, ownProps) => ({
journalItemType: selectJournalItemType(state, ownProps.journalItemTypeId)
})
The component gets a JournalItemTypeId and the mapStateToProps looks it up in the store and sends the journalItemType to the component. JournalItemType is static metadata and won't change very often, and certainly not during the life of the component.
static propTypes = {
journalItemType: ImmutablePropTypes.map.isRequired,
}
The problem with this is that I call the selector at each render. Not a big performance hit, but feels wrong anyway.
So, I changed to this:
(state, ownProps) => ({
getJournalItemType: () => selectJournalItemType(state, ownProps.journalItemTypeId)
})
The first thing I do in the components constructor is to call getJournalItemType and store the result in the local state. This way the selector is only called once.
static propTypes = {
getJournalItemType: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
journalItemType: props.getJournalItemType()
}
}
Question:
Is this the right way to do this?
Another way would be to let the component know about state so the component could call the selector itself. But I think it's cleaner to keep the state out of the component.
I could also call the selector and fetch the static data earlier in the call chain, but I don't have state naturally available there either.
Clarification:
Why would I store JournalItemTypes in the Redux store if it is static data? All of the apps metadata is in my redux store so it can be easily refreshed from the server. By keeping it in Redux I can treat metadata in the same way as all other data in my synchronisation sagas.
Added clarification after Mika's answer
I need to use the local state because the component is a quite complex input form with all sorts of inputs (input fields, camera, qr-reader, live updated SVG sketch based on input).
A JournalItem in my app is "all or nothing". I.e. if every required field is filled in the user is allowed to save the item. My store is persisted to disk, so I don't want to hit the store more often than needed. So the JournalItem-object (actually an Immutable.map) lives in state until it's ready to be saved.
My selectors are memoized with reselect. This makes my first solution even less impacting on performance. But it still feels wrong.
The component gets updated via props due to other events, so it's re-rendered now and then.
You have a few different options here:
Option 1: the original way
This is the most basic and most 'Redux' way of doing it. If your selectJournalItemType function is moderately light, your app won't suffer much of a performance hit as mapStateToProps is only called when the store is updated according to react-redux docs.
Option 2: the constructor
It is generally recommended to avoid using the Component's state with Redux. Sometimes it is necessary (for example forms with inputs) but in this case it can, and in my opinion should, be avoided.
Option 3: optimizing option 1
If your function is computationally expensive, there are at least a few ways to optimize the original solution.
In my opinion one of the simpler ones is optimizing the react-redux connect. Short example:
const options = {
pure: true, // True by default
areStatesEqual: (prev, next) => {
// You could do some meaningful comparison between the prev and next states
return false;
}
};
export default ContainerComponent = connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
options
)(PresentationalComponent);
Another possibility is to create a memoized function using Reselect