De-structuring Mouse Move event in ReasonReact - reason

Currently trying to pull clientX off a mouse move event in Reason React. Here is the component currently:
type state = {
count: int,
hover: bool,
mouseX: int,
mouseY: int,
};
type action =
| Hover
| MouseMove(ReactEventRe.Mouse.t);
let component = ReasonReact.reducerComponent("EventTracking");
let make = _children => {
...component,
initialState: () => { count: 0, hover: false, mouseX: 0, mouseY: 0 },
reducer: (action, state) =>
switch (action) {
| Hover => ReasonReact.Update({ ...state, hover: !state.hover })
| MouseMove(event) => ReasonReact.Update({ ...state, mouseX: state.mouseX + 1})
},
render: self =>{
let hoverString = "Hover State => " ++ string_of_bool(self.state.hover);
<div className="statistics" onMouseEnter={_event => self.send(Hover)} onMouseLeave={_event => self.send(Hover)} onMouseMove={event => self.send(MouseMove(event))}>
<p>
(ReasonReact.stringToElement(hoverString))
</p>
<p>
(ReasonReact.stringToElement(string_of_int(self.state.mouseX)))
</p>
</div>
},
};
The code I'm assuming I need to change is in my reducer's MouseMove action, mouseX needs to be updated to clientX but I can't seem to pull it off the event object without throwing errors.
Any advice would be great, I also have no idea if using these synthetic events is the right approach to tracking mouse position in ReasonReact.

You might be getting confused because ReactEventRe.Mouse.t is not a record or JS object type, but an abstract type which you manipulate with the functions in ReactEventRe.Mouse. It's pretty simple to translate though. To access the clientX property you use the clientX function. Fully qualified it would look like:
ReactEventRe.Mouse.clientX(event)
PS: You seem to be using an outdated version of ReasonReact. ReactEventRe has been replaced by ReactEvent and ReactReact.stringToElement with ReasonReact.string for example.

Related

Is there a useState concept so I can create a service which holds data that is accessed in multiple components?

I have 2 components who want to access the same data. Instead of each doing an HTTP Request independantly, I wanted to keep the items in parity. When doing react, we can easily do: const [ data, setData ] = useState(undefined) which will allow us to use data in our app and setData to change the global.
I was trying to think of how this might be doable in ReactScala, and Was thinking that there could be some overlap here since you can do something like:
useState[A]( data: A ): Pair[A, A=>A] = {
val d = data
return d, x => {
d = x
return d
}
}
or similar.
I have not seen the documentation on useState in Japgolly as much as defining the property in the component state and then using the state.copy() function to update the value.
The issue which occurred is that to me, state.copy is just 1 component, and wanted to know if there was a way to genericize.
https://github.com/japgolly/scalajs-react/blob/master/doc/HOOKS.md
Under the HOOKS file linked above, the top example shows how useState is translated. I will add it below in case the file is changed or deleted:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [fruit, setFruit] = useState("banana");
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<p>Your favourite fruit is a {fruit}!</p>
</div>
);
}
Compared to:
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import org.scalajs.dom.document
object Example {
val Component = ScalaFnComponent.withHooks[Unit]
.useState(0)
.useEffectBy((props, count) => Callback {
document.title = s"You clicked ${count.value} times"
})
.useState("banana")
.render((props, count, fruit) =>
<.div(
<.p(s"You clicked ${count.value} times"),
<.button(
^.onClick --> count.modState(_ + 1),
"Click me"
),
<.p(s"Your favourite fruit is a ${fruit.value}!")
)
)
}

Render a headline when search results are displayed

I didn’t find a way to output a headline (Search results:) in front of the search results that is only visible when results are displayed.
It wouldn’t be a problem to have it every time (part of the hit item template) but I don’t need one if the empty results template is displayed.
What would be a nice solution?
Here is my current hits widget configuration:
this.search.addWidget(
this.algoliaInstantSearch.widgets.hits({
container: '.js-algolia-hits',
escapeHits: true,
templates: {
item: document.querySelector('.js-algolia-hit-item-template').innerHTML,
empty: window.algoliaEmptyResultsText
},
cssClasses: {
root: 'c-search__hits-list',
empty: 'c-search__hits-empty',
item: 'c-search__hits-list-item'
}
})
);
You can use the allItems template option to have the total control of the rendering of the hits.
search.addWidget(
instantsearch.widgets.hits({
container: document.querySelector("#products"),
templates: {
allItems: function({ hits }) {
// No results message
if (hits.length === 0) return "";
const hitsMarkup = hits
.map(
hit =>
`<div class="ais-hits--item">${
hit._highlightResult.name.value
}</div>`
)
.join("");
return `
<div>
<h1>Search Results:</h1>
<div class="ais-hits--container">${hitsMarkup}</div>
</div>
`;
}
}
})
);
You can see the result here: https://codesandbox.io/s/8zxxvo84wl

Drag and drop with RXjs

I'm struggling with a drag and drop behavior with RXJS. I would like to start drag an element after 250ms mouse down for not hijack click events on that element.
So far the start drag works but stop drag never get called. Anyone know why?
let button = document.querySelector('.button');
let mouseDownStream = Rx.Observable.fromEvent(button, 'mousedown');
let mouseUpStream = Rx.Observable.fromEvent(button, 'mouseup');
let dragStream = mouseDownStream
.flatMap((event) => {
return Rx.Observable
.return(event)
.delay(250)
.takeUntil(mouseUpStream)
});
let dropStream = mouseUpStream
.flatMap((event) => {
return Rx.Observable
.return(event)
.skipUntil(dragStream)
});
dragStream.subscribe(event => console.log('start drag'));
dropStream.subscribe(event => console.log('stop drag'));
JSBin
I've updated your code-sample to make it run, what I did:
exchanged the flatMaps with switchMaps (switchMap is an alias for flatMapLatest) this will ensure that it only takes the latest events and in case a new event is emitted, it will cancel any old subevent => in this case flatMap might work okay, but it is safer to use switchMap, also a rule of thumb: when in doubt: use switchMap
dropStream is based on/initiated by dragStream now
removed the skipUntil, which was a racing-condition issue because it would have first triggered after the next dragStream-emission after some mouseUp (which would require traveling back in time)
exchanged the mouseUp-target from button to document (more a convenience-thing, and not really essential for the while thing to work)
let button = document.querySelector('.button');
let mouseDownStream = Rx.Observable.fromEvent(button, 'mousedown');
let mouseUpStream = Rx.Observable.fromEvent(document, 'mouseup');
let dragStream = mouseDownStream
.switchMap((event) => {
return Rx.Observable
.return(event)
.delay(250)
.takeUntil(mouseUpStream)
});
let dropStream = dragStream
.switchMap(() => mouseUpStream.take(1))
dragStream.subscribe(event => console.log('start drag'));
dropStream.subscribe(event => console.log('stop drag'));
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<button class="button">Button</button>
</body>
</html>
Universal drag event with TypeScript and newer rxjs
export const DragEvent = (selector:string):Observable<boolean> => {
//element where the drag event should be recorded (make sure element is in the dom)
const elem = document.querySelector(selector);
// touch events to handle mobile devices
const touchStart$ = fromEvent<TouchEvent>(elem, 'touchstart');
const touchEnd$ = fromEvent<TouchEvent>(elem, 'touchend');
const touchMove$ = fromEvent<TouchEvent>(elem, 'touchmove');
// mouse events for desktop users
const mouseDown$ = fromEvent<MouseEvent>(elem, 'mousedown');
const mouseUp$ = fromEvent<MouseEvent>(elem, 'mouseup');
const mouseMove$ = fromEvent<MouseEvent>(elem, 'mousemove');
//Mouse drag event
const mouseDragging$ = mouseMove$.pipe(
skipUntil(mouseDown$),
takeUntil(mouseUp$)
);
//
const mapToBoolean = (bool) => map(() => bool);
//universal drag event will emit true on drag (desktop/mobile) optional:add touchStart$ to the merge.
const move$ = merge(mouseDragging$, touchMove$).pipe(mapToBoolean(true);
//universal end of drag event will emit false on drag end (desktop/mobile)
const end$ = merge(mouseUp$, touchEnd$).pipe(mapToBoolean(false);
//merged to return true or false depending on user dragg
return merge(move$, end$).pipe(distinctUntilChanged());
}

React onClick event is not fired when element is created in for loop

I was trying to solve this strange problem all day, but didn't managed to. This is one of the first days I am trying out React, so maybe I am missing something.
ParentComponent.cjsx
module.exports = React.createClass
getInitialState: ->
{
items: []
}
componentDidMount: ->
request.get(constants.API_ROOT + #props.source)
.end((err, res) =>
#setState({items: res.body})
)
render: ->
`
// First try: DOES NOT WORK
var items = [];
for(var i = 0; i < this.state.items.length; i++) {
var item = this.state.items[i];
items.push(<ChildItem key={item.id} id={item.id} name={item.name} src={item.image_url_640x480} />)
}
console.log(['items1', items]);
// Second try: DOES NOT WORK
var origThis = this;
var items2 = this.state.items.map(function (item) {
return (<ChildItem key={item.id} id={item.id} name={item.name} src={item.image_url_640x480} />);
}.bind(origThis), origThis);
console.log(['items2', items2]);
`
// Creating elements by hand (WORKS, but not helpful at all)
items3 = [
<ChildItem key=23 id=31 name='ddd' src='adasdas' />,
<ChildItem key=12 id=13 name='bg' src='/media/cache/de/ba/deba6d1545e209b0416b501c61fe031f.jpg' />
]
console.log(items3)
<div id="image-layer-selector" className="pure-g">{items1} {items2} {items3}</div>
ChildItem.cjsx
module.exports = React.createClass
getInitialState: ->
selected: false
handleClick: ->
console.log 'clicked'
#setState selected: true
render: ->
elemClasses = classnames('pure-u-1-2', 'selector-element', {'selected': #state.selected})
<div className={elemClasses} onClick={#handleClick}>
{#props.name} - {#props.id}
<img className="pure-img" src={constants.API_ROOT + #props.src}/>
</div>
ChildItem onClick handler is fired only when elements are set by hand. What am I missing? I tried a lot of possible ways in .cjsx, plain .jsx, .map function, plain JS for loop etc. None of these seemed to work. Console doesn't contain any errors.
Using react 13.3.
EDIT. Seems like onClick handler doesn't work only when items are set in componentDidMount using setState. Identical problem without solution is here: React - Attaching click handler to dynamic children
Finally found the problem. I haven't done any deeper investigation why this didn't work, but the problem was that in my main file I imported React as require('React'), but on other components as require('React/addons'). After importing React everywhere from react/addons everything works as expected.

Angular app wont load (JSFiddle)

I have a simple angular app here
<div ng-app="WhereToMeet" ng-controller="MapCtrl">
<leaflet shape="shape"></leaflet>
<button ng-click="clicked()">Clicked</button>
</div>
app = angular.module("WhereToMeet", [])
app.directive "leaflet", ->
restrict: "E"
replace: true
transclude: true
template: "<div id=\"map\"></div>"
scope:
shape: "=shape"
link: (scope, element, attrs, ctrl) ->
scope.$watch attrs.shape,( (newValue, oldValue) ->
watched newValue
), true
watched = (newValue) ->
alert newValue
#MapCtrl = ($scope) ->
clicked = (clicked) ->
$scope.shape = "Clicked"
alert "clicked"
I have it in a JSFiddle http://jsfiddle.net/charliedavi/bezFB/22/ but it wont run. Really odd. I think its an error with my coffee script but I can not see it
error:
Uncaught SyntaxError: Unexpected string fiddle.jshell.net:22
Uncaught Error: No module: WhereToMeet
in pure JS
var app;
app = angular.module("WhereToMeet", []);
app.directive("leaflet", function() {
var watched;
({
restrict: "E",
replace: true,
transclude: true,
template: "<div id=\"map\"></div>",
scope: {
shape: "=shape"
},
link: function(scope, element, attrs, ctrl) {
return scope.$watch(attrs.shape, (function(newValue, oldValue) {
return watched(newValue);
}), true);
}
});
return watched = function(newValue) {
return alert(newValue);
};
});
this.MapCtrl = function($scope) {
var clicked;
return clicked = function(clicked) {
$scope.shape = "Clicked";
return alert("clicked");
};
};
http://jsfiddle.net/charliedavi/gsPx3/2/
i dont know coffee script but angular. i just tried to solve it. ;-)
Select no-wrap body, under select framework
Select no-library(pure-js)
Add angular js as resources
Manually initialize angular using this angular bootstrap
angular.bootstrap document, ['WhereToMeet']
The generated javascript code is in another scope. You have to solve this
by either adding the -b parameter to the coffeescript compiler or export your function
explicitly via
root = exports ? this
root.clicked = ->
alert "clicked"
$scope.shape = "Clicked"
It is working now Fiddle Here
I had a similar issue with jsfiddle and angular yesterday. I had to do a couple of things to make it work:
Jsfiddle is adding the tags for html and body, so just write the markup that should end up inside the body tag.
Add a wrapping div with ng-app="myApp" instead of trying to specify another html-tag
Select no-wrap body, under select framework
I don't know what your "leaflet" is doing but I have updated your fiddle so that the click will trigger an alert
I've had to change the how the controller is instantiated to get the onclick to work.
http://jsfiddle.net/t9nsY/2/
app.controller("MapCtrl", function ($scope) {
$scope.clicked = function (clicked) {
console.log("clicked");
$scope.shape = "Clicked";
return alert("clicked");
};
});