CycleJS - subscribing click events of a child component - reactive-programming

I am new to CycleJS and I would like to subscribe 'click' events of a child component from its parent component; but, it's not working. I'm able to subscribe events inside the child component. Is it possible to subscribe events of a child component from its parent component? If it's possible, how can I do it? Here's the parent component:
import Rx from 'rx';
import Cycle from '#cycle/core';
import CycleDOM from '#cycle/dom';
import isolate from '#cycle/isolate';
import _ from 'underscore';
import Inboxmails from './../components/inboxmails';
const {div} = CycleDOM;
const Main = (sources) => {
const inboxmails=Inboxmails({DOM: sources.DOM});
sources.DOM.select('#inbox_1')
.events('click')
.do(event => event.preventDefault())
.subscribe(event => {
console.log(event);
});
const vtree$ = Rx.Observable.of(
div('.wrapper', [
inboxmails.DOM
]));
return {
DOM: vtree$
};
};
export default (sources) => isolate(Main)(sources);
And this is the child component
import Rx from 'rx';
import Cycle from '#cycle/core';
import CycleDOM from '#cycle/dom';
import isolate from '#cycle/isolate';
const { div} = CycleDOM;
const Inboxmails = function (sources) {
const inboxmails$ = Rx.Observable.of(div([
div("#inbox_1",[
"Click here"
])])
);
return {
DOM: inboxmails$
};
};
export default (sources) => isolate(Inboxmails)(sources);

Have the child return a sink of events that the parent needs.
const Inboxmails = function (sources) {
const inboxmails$ = Rx.Observable.of(div([
div("#inbox_1",[
"Click here"
])])
);
return {
DOM: inboxmails$,
clicks: sources.DOM.select('#inbox_1').events('click')
};
};
Then the parent can use inboxmails.clicks.
However, in Cycle.js there should never be any subscribe in your code (unless it's for debugging). Subscribe calls should only be in drivers.

Related

How to make number of visibleSlides responsive in CSS in pure-react-carousel

How/can we make number of visibleSlides responsive in CSS? / not have to use JS to alter visibleSlides based on breakpoints.
For example;
Each slide has min-width: 100px; min-height: 100px; ie. image we want to see detail so shouldn't be smaller than 100px.
We set visibleSlides to 8 (for desktop). On mobile we want to show only 2 slides. Because we don't want the individual slides to be less than 100px height and width, nor have the slides overlapping.
I know we could use react to check screen width and set visibleSlides, however it's not easy for all apps to have access to this, especially server side rendered like next.js.
See this sandbox https://codesandbox.io/s/pure-react-carousel-responsive-visible-slides-k8cui
(Forked from https://codesandbox.io/s/withered-wood-4bx36?fontsize=14&hidenavigation=1&theme=dark)
I had that problem for my app and actually couldn't find a way to do this with CSS. However, I've implemented this using ResizeObserver and react-hooks.
P.S. I'm using next js, and it's not a big issue to implement it on the server-side.
Here's my solution, hope it could help.
Step 1. Create an observer hook to listen for resize events from the app.
import { useEffect, useState, RefObject } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
interface DOMRectReadOnly {
readonly bottom: number;
readonly height: number;
readonly left: number;
readonly right: number;
readonly top: number;
readonly width: number;
readonly x: number;
readonly y: number;
}
interface useResizeObserverProperties {
ref?: RefObject<Element> | null;
element?: Element | null | undefined;
callback?: (entry: ResizeObserverEntry) => void;
}
const IS_BROWSER = typeof window !== 'undefined';
/**
* Watch for the resizing of a React component or Element.
*
* #param hookProperties - Configuration optinos for the hook.
*
* #returns The `DOMRect` for the observed element.
*/
export const useResizeObserver = ({
ref,
element,
callback,
}: useResizeObserverProperties) => {
const [sizes, setSizes] = useState<DOMRectReadOnly>({
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
x: 0,
y: 0,
});
const handleResize = (entries: ResizeObserverEntry[]) => {
const [entry] = entries;
if (callback) callback(entry);
setSizes(entry.contentRect);
};
const [resizeObs] = useState(() =>
IS_BROWSER ? new ResizeObserver(handleResize) : undefined,
);
useEffect(() => {
if (!resizeObs) return;
let domNode;
if (ref) {
domNode = ref.current;
} else if (element) {
domNode = element;
}
if (domNode) {
resizeObs.observe(domNode);
}
return () => resizeObs.disconnect();
}, [ref, resizeObs, element]);
return sizes;
};
Step 2. In your component.tsx
import React, { useState, useRef } from 'react';
import { CarouselProvider, Slider, Slide } from 'pure-react-carousel';
import { useResizeObserver } from 'from previously created file';
const YourComponent = () => {
const [visibleSlides, setVisibleSlides] = useState(1);
const ref = useRef<HTMLDivElement>(null);
// Current width of element
const { width } = useResizeObserver({ ref });
switch (true) {
case width > 768 && width < 1280:
setVisibleSlides(2);
break;
/**
* Switch your cases here
*/
}
return (
<div ref={ref}>
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={125}
totalSlides={3}
visibleSlides={visibleSlides}
>
<Slider>
<Slide index={0}>Slide 1</Slide>
<Slide index={1}>Slide 2</Slide>
<Slide index={2}>Slide 3</Slide>
</Slider>
</CarouselProvider>
</div>
);
};
export default YourComponent;
Btw I'll recommend you throttle setVisibleSlides calls to avoid too much re-renders while resizing the window from dev-tools.
not an answer, but i'm trying to do the same.
How about using React hooks useState for the visibleSlides integer, and creating a window listener that listens for breakpoints and changes the state as needed....
How about setting a state and using useEffect to update the state on resize of window, then passing down the window size as a prop to the carousel component and choosing the number of visibleslides using the prop?
https://codesandbox.io/s/pure-react-carousel-responsive-visible-slides-forked-q29c1d?file=/src/App.js

My geosearch is not working with leaflet onClick

I am trying to have search control and onClick event handler to add Marker on my leaflet map. But search control is not working with onClick, but working fine when I don't have any onClick handlers.
class GeoSearch extends MapControl {
createLeafletElement(opts) {
const provider = new OpenStreetMapProvider()
const searchControl = new GeoSearchControl({
provider: provider,
position: 'bottomleft',
style: 'button',
})
return searchControl
}
componentDidMount() {
const {map} = this.props.leaflet
map.addControl(this.leafletElement)
}
}
export default withLeaflet(GeoSearch)
<Map
...
onClick={this.addMarker}
>
<Search />
</Map>
I guess addMarker event is also triggered once control is clicked, right? If so, this is the expected behavior since control elements emit events that are propagated to map. To suppress control events from propagation L.DomEvent.disableClickPropagation method could be utilized, here is a modified version of GeoSearch component:
class GeoSearch extends MapControl {
constructor(props, context) {
super(props);
}
createLeafletElement(opts) {
const provider = new OpenStreetMapProvider();
const searchControl = new GeoSearchControl({
provider: provider,
position: "topleft"
});
return searchControl;
}
componentDidMount() {
const { map } = this.props.leaflet;
map.addControl(this.leafletElement);
//To suppress control events from propagation
const containerDiv = this.leafletElement.getContainer();
L.DomEvent.disableClickPropagation(containerDiv);
}
}
Here is a demo

How do I automatically redirect(after a 2 second delay) from a splash page to a registration page using React Native?

I have my StackNavigator set up like this:
const Navigation = StackNavigator({
Splash:{screen: Splash},
Registration:{screen:Registration},
HomeScreen:{screen: HomeScreen},
Login:{screen: Login},
Lobby:{screen: Lobby},
Wifi:{screen: Wifi},
Mobile:{screen:Mobile},
}, {
mode: 'modal',
headerMode: 'none'
});
I'd like to redirect the user from the Splash page(which only contains a logo) to the Registration page after 2 seconds. I want to avoid using buttons(hence the automatic redirect) so that the user gets a brief look at the logo.
My Splash page:
import React,{Component} from 'react'
import {View, Text, Image, StyleSheet} from 'react-native'
import config from '../components/config/index';
import { StackNavigator, DrawerNavigator } from 'react-navigation';
export default class Splash extends Component{
render(){
const logo = config.images.logo;
const {navigate} = this.props.navigation;
return(
<View style={styles.mainContainer}>
<Image
source={logo}
style={styles.logo}
/>
</View>
);
}
}
I'm not sure I would put this.navigator.redirect('Registration') because the only way I've changed pages so far was using onPress={}
try with componentDidMount function.
import React,{Component} from 'react'
import {View, Text, Image, StyleSheet} from 'react-native'
import config from '../components/config/index';
import { StackNavigator, NavigationActions, DrawerNavigator } from 'react-navigation';
const EntityAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'screen:Registration' }),
]
});
export default class Splash extends Component{
componentDidMount {
setTimeout( () => {this.load()}, 2000);
}
load = () => {
this.props.navigation.dispatch(EntityAction);
}
render(){
const logo = config.images.logo;
const {navigate} = this.props.navigation;
return(
<View style={styles.mainContainer}>
<Image
source={logo}
style={styles.logo}
/>
</View>
);
}
}

React Leaflet: Show popup on mouseover

Has anyone been able to use the React Leaflet Popup element to show a popup on mouseover rather than on click?
I can't seem to find a way to achieve this.
I've recently solved this problem using React Refs and the Leaflet API.
A barebones example:
import React, { Component } from 'react';
import { Circle } from 'react-leaflet';
class Foo extends Component {
render() {
const { center, radius } = this.props;
return (
<Circle
ref={circle => { this.circle = circle; }}
center={center}
radius={radius}
onMouseOver={() => {
this.circle.leafletElement.bindPopup('foo').openPopup();
}}/>
);
}
}
export default Foo;

Exchange Data between multi step forms in Angular2: What is the proven way?

I can imagine following approaches to exchange Data between multi step forms:
1) Create a component for each form step and exchange data between components over #input, #output (e.g. you cannot change from step5 to 2)
2) Use the new property data in the new router (see here) (e.g. you cannot change from step5 to 2))
3) A shared Service (Dependency Injection) to store data (Component Interaction) (e.g. you can change from step5 to 2)
4) New rudiments with #ngrx/store (not really experienced yet)
Can you give some "gained experience values", what do you use and why?
See my edit below.
Using SessionStorage is not strictly the 'angular' way to approach this in my opinion—a shared service is the way to go. Implementing routing between steps would be even better (as each component can have its own form and different logic as you see fit:
const multistepRoutes: Routes = [
{
path: 'multistep',
component: MultistepComponent,
children: [
{
path: '',
component: MultistepBaseComponent,
},
{
path: 'step1',
component: MultistepStep1Component
},
{
path: 'step2',
component: MultistepStep2Component
}
]
}
];
The service multistep.service can hold the model and implement logic for components:
import { Injectable, Inject } from '#angular/core';
import { Router } from '#angular/router';
#Injectable()
export class MultistepService {
public model = {};
public baseRoute = '/multistep';
public steps = [
'step1',
'step2'
];
constructor (
#Inject(Router) public router: Router) { };
public getInitialStep() {
this.router.navigate([this.baseRoute + '/' + this.steps[0]]);
};
public goToNextStep (direction /* pass 'forward' or 'backward' to service from view */): any {
let stepIndex = this.steps.indexOf(this.router.url.split('/')[2]);
if (stepIndex === -1 || stepIndex === this.steps.length) return;
this.router.navigate([this.baseRoute + '/' + this.steps[stepIndex + (direction === 'forward' ? 1 : -1)]]);
};
};
Good luck.
EDIT 12/6/2016
Actually, now having worked with the form API for a while I don't believe my previous answer is the best way to achieve this.
A preferrable approach is to create a top level FormGroup which has each step in your multistep form as it's own FormControl (either a FormGroup or a FormArray) under it's controls property. The top level form in such a case would be the single-source of truth for the form's state, and each step on creation (ngOnInit / constructor) would be able to read data for its respective step from the top level FormGroup. See the pseudocode:
const topLevelFormGroup = new FormGroup({
step1: new FormGroup({fieldForStepOne: new FormControl('')}),
step2: new FormGroup({fieldForStepTwo}),
// ...
});
...
// Step1Component
class Step1Component {
private stepName: string = 'step1';
private formGroup: FormGroup;
constructor(private topLevelFormGroup: any /* DI */) {
this.formGroup = topLevelFormGroup.controls[this.stepName];
}
}
Therefore, the state of the form and each step is kept exactly where it should be—in the form itself!
Why not use session storage? For instance you can use this static helper class (TypeScript):
export class Session {
static set(key:string, value:any) {
window.sessionStorage.setItem(key, JSON.stringify(value));
}
static get(key:string) {
if(Session.has(key)) return JSON.parse(window.sessionStorage[key])
return null;
}
static has(key:string) {
if(window.sessionStorage[key]) return true;
return false;
}
static remove(key:string) {
Session.set(key,JSON.stringify(null)); // this line is only for IE11 (problems with sessionStorage.removeItem)
window.sessionStorage.removeItem(key);
}
}
And using above class, you can put your object with multi-steps-forms data and share it (idea is similar like for 'session helper' in many backend frameworks like e.g. php laravel).
The other approach is to create Singleton service. It can look like that (in very simple from for sake of clarity) (I not test below code, I do it from head):
import { Injectable } from '#angular/core';
#Injectable()
export class SessionService {
_session = {};
set(key:string, value:any) {
this._session[key]= value; // You can also json-ize 'value' here
}
get(key:string) {
return this._session[key]; // optionally de-json-ize here
}
has(key:string) {
if(this.get(key)) return true;
return false;
}
remove(key:string) {
this._session[key]=null;
}
}
And then in your main file where you bootstrap application:
...
return bootstrap(App, [
...
SessionService
])
...
And the last step - critical: When you want to use you singleton service in your component - don't put int in providers section (this is due to angular2 DI behavior - read above link about singleton services). Example below for go from form step 2 to step 3:
import {Component} from '#angular/core';
import {SessionService} from './sessionService.service';
...
#Component({
selector: 'my-form-step-2',
// NO 'providers: [ SessionService ]' due to Angular DI behavior for singletons
template: require('./my-form-step-2.html'),
})
export class MyFormStep2 {
_formData = null;
constructor(private _SessionService: SessionService) {
this._formData = this._SessionService.get('my-form-data')
}
...
submit() {
this._SessionService.set('my-form-data', this._formData)
}
}
It should looks like that.