How would you convert this JSX into a Hyperstack Component?
const Stopwatch = () => (
<ReactStopwatch
seconds={0}
minutes={0}
hours={0}
limit="00:00:10"
onChange={({ hours, minutes, seconds }) => {
// do something
}}
onCallback={() => console.log('Finish')}
render={({ formatted, hours, minutes, seconds }) => {
return (
<div>
<p>
Formatted: { formatted }
</p>
<p>
Hours: { hours }
</p>
<p>
Minutes: { minutes }
</p>
<p>
Seconds: { seconds }
</p>
</div>
);
}}
/>
);
This syntax render={({ formatted, hours, minutes, seconds }) is new to me. Are these props?
I am trying to use this NPM module:
https://www.npmjs.com/package/react-stopwatch
The render prop renders a functional component. A functional component is a function, such that the arguments are props, and the return value is the component to be rendered.
Therefor, we need to convert your render prop to opal.
DIV do
P { "Formatted: #{native_props.formatted}" }
P { "Hours: #{native_props.hours}" }
P { "Minutes: #{native_props.minutes}" }
P { "Seconds: #{native_props.seconds}" }
end.to_n
There's one gotcha here. The first is that props is being passed as a javascript object. We need that to be ruby, so we wrap it in Native.
Step two is exposing the stopwatch class to Hyperstack.
Under app/javascript/packs, edit hyperstack.js to include react-stopwatch.
import ReactStopwatch from 'react-stopwatch';
global.ReactStopwatch = ReactStopwatch;
Now you can use Stopwatch in your code.
Putting this together, you get:
class Stopwatch < HyperComponent
render do
ReactStopwatch(seconds: 0, minutes: 0, hours: 0, limit: "00:00:10").on("<render>") do |props|
DIV do
P { "Formatted: #{props.formatted}" }
P { "Hours: #{props.hours}" }
P { "Minutes: #{props.minutes}" }
P { "Seconds: #{props.seconds}" }
end.to_n
end
end
end
Related
I'd like my alpine js to x-show something for only one second, repeatedly, every 60 seconds. I've got a js timer (likely more robust than I need) and I can get x-text to show me the seconds counting down and then resetting, but I can't quite figure out what to put into x-show to get the div to display at the time I've designated. Here's my html/alpine
<p x-text="time().seconds"></p>
<div x-show="time().seconds.value === 48">
hello
</div>
and my javascript timer:
function timer(expiry) {
return {
expiry: expiry,
remaining:null,
init() {
this.setRemaining()
setInterval(() => {
this.setRemaining();
}, 1000);
},
setRemaining() {
const diff = this.expiry - new Date().getTime();
this.remaining = parseInt(diff / 1000);
},
days() {
return {
value:this.remaining / 86400,
remaining:this.remaining % 86400
};
},
hours() {
return {
value:this.days().remaining / 3600,
remaining:this.days().remaining % 3600
};
},
minutes() {
return {
value:this.hours().remaining / 60,
remaining:this.hours().remaining % 60
};
},
seconds() {
return {
value:this.minutes().remaining,
};
},
format(value) {
return ("0" + parseInt(value)).slice(-2)
},
time(){
return {
days:this.format(this.days().value),
hours:this.format(this.hours().value),
minutes:this.format(this.minutes().value),
seconds:this.format(this.seconds().value),
}
},
}
}
</script>
I changed the format function to a string:
format(value) {
return ("0" + value).slice(-2)
}
and then x-show worked fine with this modification:
<div x-show="time().seconds === '48'">
hello
</div>
(There's a PD)
I have a page where I show account movements.
In the DB there is the date and the amount.
A separate table holds a saldo for beginning of each month.
Aditionally the table is filtrable by start and end date
The filtered entries from the bank account movements have a date and an amount and this is working fine.
I am trying now to fusion that with the account-saldo.
The start is easy: take and show startSaldo from DB
On render side I have a v-for loop going though each movement
strange is I am getting an infinite loop on an unexpected method
Rendering:
<tr v-for="movimiento in this.filteredEntries">
...
<td>{{calculateSaldo(movimiento.cobro_id,movimiento.monto)}}</td>
...
</tr>
saldo is a simple property (not a calculated one), at the moment initiated with 0 and only used within the method
and this is the method
calculateSaldo(cobro_id,monto){
//cobro_id and monto are
return 0; // <<<< without this I get the infinite loop error. But why?
if (cobro_id){
this.saldo=this.saldo+parseInt(monto);
} else {
this.saldo= this.saldo-parseInt(monto);
}
console.log(this.saldo);
// <<< a return 0 here produces also an infinite loop error
return this.saldo;
},
I have no clue where to futher look...
PD: After some more tests, apparently as soon as I reassign a value to this.saldo a re-render is initiated. How overcome that?
PPD: Apparently the re-render happens after reaching the last (by Date) filtered Entity
As described here when you reassign a value in state render triggered and it causes an infinite loop.
So I tried to recreate a basic version of your component and I think you can solve this on your filteredEntries computed property.
computed: {
filteredEntries() {
let lastSaldo = this.saldo;
return this.entries.map((item) => {
if (item.cobro_id) {
lastSaldo = parseInt(lastSaldo) + parseInt(item.monto);
} else {
lastSaldo = parseInt(lastSaldo) - parseInt(item.monto);
}
item.saldo = lastSaldo;
return item;
});
},
},
Complete code of my recreation of your component example:
<template>
<div>
<table>
<tr v-for="(movimiento, index) in this.filteredEntries" :key="index">
<td>{{ movimiento.saldo }}</td>
</tr>
</table>
</div>
</template>
<script>
export default {
data() {
return {
saldo: 0,
entries: [
{
cobro_id: null,
monto: 5,
},
{
cobro_id: 2,
monto: 10,
},
{
cobro_id: null,
monto: 5,
},
{
cobro_id: 1,
monto: 25,
},
],
};
},
computed: {
filteredEntries() {
let lastSaldo = this.saldo;
return this.entries.map((item) => {
if (item.cobro_id) {
lastSaldo = parseInt(lastSaldo) + parseInt(item.monto);
} else {
lastSaldo = parseInt(lastSaldo) - parseInt(item.monto);
}
item.saldo = lastSaldo;
return item;
});
},
},
};
</script>
I'm trying to render out my calendar's event summaries, using a .map function. I've stored my calendar events object in state, but can't find a way to .map out the different event summaries. Any suggestions?
export default class Container extends React.Component{
calendarID="xxx"
apiKey="zzz";
state = { events: [] };
setEvents = (a) => {
this.setState(a);
}
componentDidMount() {
ajax.get(`https://www.googleapis.com/calendar/v3/calendars/${this.calendarID}/events?fields=items(summary,id,location,start)&key=${this.apiKey}`)
.end((error, response) => {
if(!error && response ) {
this.setEvents({events: response.body});
console.log("success");
console.log(this.state.events);
} else {
console.log("Errors: ", error);
}
});
}
render(){
let lista = this.state.events;
let arr = Object.keys(lista).map(key => lista[key])
return (
<div class = "container">
{arr.map((event, index) => {
const summary = event.summary;
return (<div key={index}>{summary}</div>);
})}
</div>
);
}
}
EDIT:
Thanks for your answers! This is the data that the ajax call returns when I console log this.state.items:
Object {items: Array[1]}
items: Array[1]
0: Object
id: "cmkgsrcohfebl5isa79034h8a4"
start: Object
summary: "Stuff going down"
If I skip the ajax call and create my own state, the mapping works:
state = { items: [
{ items: { summary: "testing"} },
{ items: { summary: "12"} },
{ items: { summary: "3"} }
]};
To get this working, however, I change my render-function to:
render(){
let lista = this.state.items;
let arr = Object.keys(lista).map(key => lista[key])
return (
<div class = "container">
{arr.map((item, index) => {
const summary = item.items.summary;
return (<div key={index}>{summary}</div>);
})}
</div>
);
}
So maybe it has something to do with the object that this.state.items returns from the ajax call?
Edit2: #Andrea Korinski, you were right! I changed my render function to this, and now it works:
render(){
let list = this.state.items;
const arr = (list.items || []).map((item, index) => {
const summary = item.summary;
return (<div key={index}>{summary}</div>);
});
return (
<div class = "container">
{arr}
</div>
);
}
}
The whole component:
export default class Container extends React.Component{
calendarID="xxx";
apiKey="zzz";
state = {items: []};
setEvents = (a) => {
this.setState(a);
}
componentDidMount() {
ajax.get(`https://www.googleapis.com/calendar/v3/calendars/${this.calendarID}/events?fields=items(summary,id,location,start)&key=${this.apiKey}`)
.end((error, response) => {
if(!error && response ) {
this.setEvents({items: response.body});
console.log("success");
console.log(this.state.items);
} else {
console.log("Errors: ", error);
}
});
}
render(){
let list = this.state.items;
const irr = (list.items || []).map((item, index) => {
const summary = item.summary;
return (<div key={index}>{summary}</div>);
});
return (
<div class = "container">
{irr}
</div>
);
}
}
I have a ReactJS component that I want to have different behavior on a single click and on a double click.
I read this question.
<Component
onClick={this.onSingleClick}
onDoubleClick={this.onDoubleClick} />
And I tried it myself and it appears as though you cannot register both single click and double click on a ReactJS component.
I'm not sure of a good solution to this problem. I don't want to use a timer because I'm going to have 8 of these single components on my page.
Would it be a good solution to have another inner component inside this one to deal with the double click situation?
Edit:
I tried this approach but it doesn't work in the render function.
render (
let props = {};
if (doubleClick) {
props.onDoubleClick = function
} else {
props.onClick = function
}
<Component
{...props} />
);
Here is the fastest and shortest answer:
CLASS-BASED COMPONENT
class DoubleClick extends React.Component {
timer = null
onClickHandler = event => {
clearTimeout(this.timer);
if (event.detail === 1) {
this.timer = setTimeout(this.props.onClick, 200)
} else if (event.detail === 2) {
this.props.onDoubleClick()
}
}
render() {
return (
<div onClick={this.onClickHandler}>
{this.props.children}
</div>
)
}
}
FUNCTIONAL COMPONENT
const DoubleClick = ({ onClick = () => { }, onDoubleClick = () => { }, children }) => {
const timer = useRef()
const onClickHandler = event => {
clearTimeout(timer.current);
if (event.detail === 1) {
timer.current = setTimeout(onClick, 200)
} else if (event.detail === 2) {
onDoubleClick()
}
}
return (
<div onClick={onClickHandler}>
{children}
</div>
)
}
DEMO
var timer;
function onClick(event) {
clearTimeout(timer);
if (event.detail === 1) {
timer = setTimeout(() => {
console.log("SINGLE CLICK");
}, 200)
} else if (event.detail === 2) {
console.log("DOUBLE CLICK");
}
}
document.querySelector(".demo").onclick = onClick;
.demo {
padding: 20px 40px;
background-color: #eee;
user-select: none;
}
<div class="demo">
Click OR Double Click Here
</div>
I know this is an old question and i only shoot into the dark (did not test the code but i am sure enough it should work) but maybe this is of help to someone.
render() {
let clicks = [];
let timeout;
function singleClick(event) {
alert("single click");
}
function doubleClick(event) {
alert("doubleClick");
}
function clickHandler(event) {
event.preventDefault();
clicks.push(new Date().getTime());
window.clearTimeout(timeout);
timeout = window.setTimeout(() => {
if (clicks.length > 1 && clicks[clicks.length - 1] - clicks[clicks.length - 2] < 250) {
doubleClick(event.target);
} else {
singleClick(event.target);
}
}, 250);
}
return (
<a onClick={clickHandler}>
click me
</a>
);
}
I am going to test this soon and in case update or delete this answer.
The downside is without a doubt, that we have a defined "double-click speed" of 250ms, which the user needs to accomplish, so it is not a pretty solution and may prevent some persons from being able to use the double click.
Of course the single click does only work with a delay of 250ms but its not possible to do it otherwise, you have to wait for the doubleClick somehow...
All of the answers here are overcomplicated, you just need to use e.detail:
<button onClick={e => {
if (e.detail === 1) handleClick();
if (e.detail === 2) handleDoubleClick();
}}>
Click me
</button>
A simple example that I have been doing.
File: withSupportDoubleClick.js
let timer
let latestTouchTap = { time: 0, target: null }
export default function withSupportDoubleClick({ onDoubleClick = () => {}, onSingleClick = () => {} }, maxDelay = 300) {
return (event) => {
clearTimeout(timer)
const touchTap = { time: new Date().getTime(), target: event.currentTarget }
const isDoubleClick =
touchTap.target === latestTouchTap.target && touchTap.time - latestTouchTap.time < maxDelay
latestTouchTap = touchTap
timer = setTimeout(() => {
if (isDoubleClick) onDoubleClick(event)
else onSingleClick(event)
}, maxDelay)
}
}
File: YourComponent.js
import React from 'react'
import withSupportDoubleClick from './withSupportDoubleClick'
export default const YourComponent = () => {
const handleClick = withSupportDoubleClick({
onDoubleClick: (e) => {
console.log('double click', e)
},
onSingleClick: (e) => {
console.log('single click', e)
},
})
return (
<div
className="cursor-pointer"
onClick={handleClick}
onTouchStart={handleClick}
tabIndex="0"
role="button"
aria-pressed="false"
>
Your content/button...
</div>
)
}
onTouchStart start is a touch event that fires when the user touches the element.
Why do you describe these events handler inside a render function? Try this approach:
const Component = extends React.Component {
constructor(props) {
super(props);
}
handleSingleClick = () => {
console.log('single click');
}
handleDoubleClick = () => {
console.log('double click');
}
render() {
return (
<div onClick={this.handleSingleClick} onDoubleClick={this.handleDoubleClick}>
</div>
);
}
};
I have an array on my controller
$scope.arr = [......], a
nd in html I want to do
ng-repeat = "item in arr | color:'blue'" //this line works, filter done in the app.filter way.
where color is an attribute of all objects in arr.
How Do I make this color:'blue' customizable and sub in color:'red'?
also if I want to do controller filtering instead of html, what would be the syntax, right now I have
$scope.filteredArr = $filter('color')($scope.arr,'blue');
which is giving error
http://jsfiddle.net/x3azn/YpMKX/
posted fiddle, please remove -1
You can customize color:'blue' with any expression in the format filter:expression, so something like color:myColor would work fine provided a color filter has been defined and myColor exists in the current scope.
In your controller, you can do the same.
$scope.filteredArr = $filter('color')($scope.arr,myColor);
Here is an example based on your jsFiddle example.
Javascript:
angular.module('app', [])
.filter('eyecolor', function () {
return function ( people, color ) {
var arr = [];
for ( var i = people.length; i--; ) {
if ( people[i].eyeColor === color ) {
arr.push( people[i] );
}
}
return arr;
}
})
.controller('ctrl', function ($scope, $filter) {
$scope.desiredColor = 'Brown';
$scope.people = [
{
name: 'Bob',
eyeColor: 'Brown'
}, {
name: 'Katherine',
eyeColor: 'Yellow'
}, {
name: 'Chun',
eyeColor: 'Black'
}
];
$scope.peopleFilter = $filter('eyecolor')( $scope.people, $scope.desiredColor );
});
Html:
<div ng-app="app">
<div ng-controller="ctrl">Color Desired:
<input ng-model="desiredColor" /><br/>
<div ng-repeat="person in people | eyecolor: desiredColor">
HTML filter: {{person.name}} has eye color {{person.eyeColor}}</div>
<div ng-repeat="person in peopleFilter">
Controller filter: {{ person.name }} has eye color {{ person.eyeColor }}</div>
</div>
</div>