I'm calling the useAnimatedScrollHandler hook from react-native-reanimated to handle my onScroll function on an Animated.ScrollView. This hook works as expected but I now want to disable a custom button (My FlatButton) based on the currentIndex which is a sharedValue. But when the sharedValue changes the screen doesn't get rerendered, because the state doesn't change so the look of my button remains the same.
Is there a way to force a rerender inside of a worklet, or is it possible to use useState to force a rerender from inside a worklet?
const scrollHandler = useAnimatedScrollHandler((event) => {
translationX.value = event.contentOffset.x
if (event.contentOffset.x < width * 0.5 && currentIndex.value != 0) {
currentIndex.value = 0
} else if (
event.contentOffset.x > width * 0.5 &&
event.contentOffset.x < width * 1.5 &&
currentIndex.value != 1
) {
currentIndex.value = 1
} else if (event.contentOffset.x > width * 1.5 && currentIndex.value != 2) {
currentIndex.value = 2
}
})
<FlatButton
label="Next"
disabled={
(currentIndex.value == 0 && (!firstName || !lastName)) ||
(currentIndex.value == 1 && (!dateOfBirth || !sex)) ||
(currentIndex.value == 2 &&
(!streetNumber || !postalCode || !city || !state || !country))
}
onPress={() => {
if (currentIndex.value == 0) {
scrollRef.current
?.getNode()
.scrollTo({ x: width, animated: true })
} else if (currentIndex.value == 1) {
scrollRef.current?.getNode().scrollToEnd({ animated: true })
}
}}
/>
I just found out that reanimated offers the function runOnJS which makes it possible to run a javscript function like setState inside a worklet. So just create a wrapper function, like in my case toggleIndex in which you interact with your state and call this function inside runOnJS from your worklet.
const [currentIndex, setCurrentIndex] = useState(0)
const toggleIndex = (index: number) => {
setCurrentIndex(index)
}
const scrollHandler = useAnimatedScrollHandler((event) => {
translationX.value = event.contentOffset.x
if (event.contentOffset.x < width * 0.5 && currentIndex != 0) {
runOnJS(toggleIndex)(0)
} else if (
event.contentOffset.x > width * 0.5 &&
event.contentOffset.x < width * 1.5 &&
currentIndex != 1
) {
runOnJS(toggleIndex)(1)
} else if (event.contentOffset.x > width * 1.5 && currentIndex != 2) {
runOnJS(toggleIndex)(2)
}
})
Related
I have the following code, but block with else if doesn't work,
how can I change this code, to make the if else block work?
cy.get('#cards-list').then((list) => {
const valueFormList = list.find(`a:contains('${nameForSite}')`)
const nextArrow = list.find(`li.pagination-next`)
if (valueFormList.length > 0 && nextArrow.length > 0){
cy.get(`a:contains('${nameForSite}')`).as('card')
} else if (valueFormList.length < 0 && nextArrow.length > 0) {
cy.get('li.pagination-next').click()
cy.wait(200)
cy.findCardOnFacebook(nameForSite)
} else {
cy.get('h4.learning-opportunities-title').should('contain', 'Learning')
}
})
The error is because of the condition valueFormList.length < 0. The length can never be less than 0. Either it can be zero or more than zero.
cy.get('#cards-list').then((list) => {
const valueFormList = list.find(`a:contains('${nameForSite}')`)
const nextArrow = list.find(`li.pagination-next`)
if (valueFormList.length == 0 && nextArrow.length > 0) {
cy.get('li.pagination-next').click()
cy.wait(200)
cy.findCardOnFacebook(nameForSite)
} else if (valueFormList.length > 0 && nextArrow.length > 0) {
cy.get(`a:contains('${nameForSite}')`).as('card')
} else {
cy.get('h4.learning-opportunities-title').should('contain', 'Learning')
}
})
I have this Vue directive to detect if I scroll past a certain element. But it stopped working since I set overflow-x to hidden. So I logged all the comparisons with the client bounding rect. How can I make my scroll detection work again?
import Vue from 'vue'
Vue.directive(
"infocus" , {
bind: function (el, binding, vnode) {
let f = () => {
let clientBoundingRect = el.getBoundingClientRect();
console.log(clientBoundingRect.width > 0);
console.log(clientBoundingRect.height > 0);
console.log(clientBoundingRect.top >= 0);
console.log(clientBoundingRect.width <= (window.innerHeight || document.documentElement.clientHeight));
let isInView = (
clientBoundingRect.width > 0 &&
clientBoundingRect.height > 0 &&
clientBoundingRect.top >= 0 &&
clientBoundingRect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
)
if (isInView) {
if(typeof binding.value === "function")
binding.value(el);
else
throw new Error("v-infocus requires you to bind a method.");
window.removeEventListener('scroll', f)
}
}
window.addEventListener('scroll', f);
f();
}
}
)
I am trying to implement a drag and drop solution in react-native and I want to make my flatlist scroll if I drag an item in the upper 10 percent and bottom 10 percent of the view. So far the only way i can get it to happen is by calling a function that recursively dispatches a this._flatList.scrollToOffset({ offset, animated: false });. The recursive call stops when a certain condition is met but the scrolling effect is choppy. Any advice on making that smoother?
// on move pan responder
onPanResponderMove: Animated.event([null, { [props.horizontal ? 'moveX' : 'moveY']: this._moveAnim }], {
listener: (evt, gestureState) => {
const { moveX, moveY } = gestureState
const { horizontal } = this.props
this._move = horizontal ? moveX : moveY;
const { pageY } = evt.nativeEvent;
const tappedPixel = pageY;
const topIndex = Math.floor(((tappedPixel - this._distanceFromTop + this._scrollOffset) - this._containerOffset) / 85);
const bottomIndex = Math.floor(((tappedPixel + (85 - this._distanceFromTop) + this._scrollOffset) - this._containerOffset) / 85);
this.setTopAndBottom(topIndex, bottomIndex);
this.scrolling = true;
this.scrollRec();
}
}),
// recursive scroll function
scrollRec = () => {
const { activeRow } = this.state;
const { scrollPercent, data, } = this.props;
const scrollRatio = scrollPercent / 100;
const isLastItem = activeRow === data.length - 1;
const fingerPosition = Math.max(0, this._move - this._containerOffset);
const shouldScrollUp = fingerPosition < (this._containerSize * scrollRatio); // finger is in first 10 percent
const shouldScrollDown = !isLastItem && fingerPosition > (this._containerSize * (1 - scrollRatio)) // finger is in last 10
const nextSpacerIndex = this.getSpacerIndex(this._move, activeRow);
if (nextSpacerIndex >= this.props.data.length) { this.scrolling = false; return this._flatList.scrollToEnd(); }
if (nextSpacerIndex === -1) { this.scrolling = false; return; }
if (shouldScrollUp) this.scroll(-20);
else if (shouldScrollDown) this.scroll(20);
else { this.scrolling = false; return; };
setTimeout(this.scrollRec, 50);
}
/
Yeah, pass an options object to your Animated.event with your listener with useNativeDriver set to true
Animated.event([
null,
{ [props.horizontal ? "moveX" : "moveY"]: this._moveAnim },
{
listener: ...,
useNativeDriver: true
}
]);
Should make things significantly smoother.
edit: I feel I should add that there is probably a saner way to accomplish this, are you only using a FlatList because of it's "infinite" dimensions?
I want to create a checkout form which includes expiry date TextInput. It will look like a this (MM/YY). After adding first 2 digits it will automatically add / then the person can type last 2 digits for the year. I found this code on the other question. But it doesn't work. When you type inside the form nothing is typed. Here is the code. How can I make this code work as needed?
constructor() {
super()
this.state = {
isReady: false
}
}
componentDidMount() {
this.setState({
isReady: true
})
}
onChange(text) {
let newText = '';
let numbers = '0123456789';
for (var i = 0; i < text.length; i++) {
if ( numbers.indexOf(text[i]) > -1 ) {
newText = newText + text[i];
}
}
this.setState({myNumber: newText})
}
formatFunction(cardExpiry = ""){
//expiryDate will be in the format MMYY, so don't make it smart just format according to these requirements, if the input has less than 2 character return it otherwise append `/` character between 2nd and 3rd letter of the input.
if(cardExpiry.length < 2){
return cardExpiry;
}
else{
return cardExpiry.substr(0, 2) + "/" + (cardExpiry.substr(2) || "")
}
}
inputToValue(inputText){
//if the input has more than 5 characters don't set the state
if(inputText.length < 6){
const tokens = inputText.split("/");
// don't set the state if there is more than one "/" character in the given input
if(tokens.length < 3){
const month = Number(tokens[1]);
const year = Number(tokens[2]);
//don't set the state if the first two letter is not a valid month
if(month >= 1 && month <= 12){
let cardExpiry = month + "";
//I used lodash for padding the month and year with zero
if(month > 1 || tokens.length === 2){
// user entered 2 for the month so pad it automatically or entered "1/" convert it to 01 automatically
cardExpiry = _.padStart(month, 2, "0");
}
//disregard changes for invalid years
if(year > 1 && year <= 99){
cardExpiry += year;
}
this.setState({cardExpiry});
}
}
}
}
render (){
let {cardExpiry} = this.state;
return (
<Image style={styles.image} source={require('../img/cover.jpg')}
>
<Content style={styles.content}>
<Form>
<Item >
<Icon active name='card'/>
<Input keyboardType='numeric' maxLength={16} placeholder='Card Number'
onChangeText = {(text)=> this.onChange(text)}
value = {this.state.myNumber}/>
</Item>
<Grid>
<Row>
<Col>
<Item style={{ marginBottom:10}}>
<Icon active name='calendar' />
<Input keyboardType='numeric' placeholder='MM/YY'
value = {this.formatFunction(cardExpiry)}
onChangeText={this.inputToValue.bind(this)}/>
</Item>
</Col>
<Col>
<Item style={{ marginBottom:10}}>
<Icon active name='lock' />
<Input maxLength={3} secureTextEntry={true} placeholder='CVV'/>
</Item>
</Col>
</Row>
</Grid>
Use this code to handle your problem:
constructor(props) {
super(props);
this.state = { text: '' };
}
handleChange = (text) => {
let textTemp = text;
if (textTemp[0] !== '1' && textTemp[0] !== '0') {
textTemp = '';
}
if (textTemp.length === 2) {
if (parseInt(textTemp.substring(0, 2)) > 12 || parseInt(textTemp.substring(0, 2)) == 0) {
textTemp = textTemp[0];
} else if (this.state.text.length === 2) {
textTemp += '/';
} else {
textTemp = textTemp[0];
}
}
this.setState({text: textTemp})
}
render() {
return (
<TextInput
keyboardType={'numeric'}
onChangeText={this.handleChange}
value={this.state.text}
maxLength={5}
/>
);
}
After searching a lot for Picker with Month/Year, for the time being i have created a logic for the Expiry date.
Hope this will help somebody.
const onCardExpiryDateChange = (prevValue:string, currentValue: string) => {
if (currentValue?.includes(',') || currentValue?.includes('-') || currentValue?.includes('.')) {
return prevValue
} else {
let textTemp = currentValue
if (textTemp[0] !== '0' && textTemp[0] !== '1' && textTemp[0] !== '2' && textTemp[0] !== '3') {
textTemp = '';
} else if ((prevValue?.length === 5) && currentValue.length === prevValue.length-1) {
textTemp = textTemp?.slice(0, -3)
} else if (textTemp.length === 6 && (textTemp[5] == '0' || textTemp[5] == '1')){
textTemp = textTemp?.slice(0, -1)
}
else if (textTemp.length === 7 && textTemp[6] == '0') {
textTemp = textTemp?.slice(0, -1)
} else if (textTemp.length === 2) {
if (parseInt(textTemp?.substring(0, 2)) > 12 || parseInt(textTemp?.substring(0, 2)) == 0) {
textTemp = textTemp?.slice(0, -1)
} else if (textTemp?.length === 2) {
textTemp += ' / ';
} else {
textTemp = textTemp[0];
}
}
return textTemp
}
}
As #AndroConsis pointed out in #Vahid Boreiri's answer, the only problem with adding '/' after length 2 is when deleting the expiry date it keeps adding '/'. To fix this, one can add a conditional backspaceFlag.
const [backspaceFlag, setBackspaceFlag] = React.useState(false);
const [expiratoinDate, setExpirationDate] = React.useState('');
const handleExpirationDate = (text) => {
let textTemp = text;
if (textTemp[0] !== '1' && textTemp[0] !== '0') {
textTemp = '';
}
if (textTemp.length === 2) {
if (parseInt(textTemp.substring(0, 2)) > 12 || parseInt(textTemp.substring(0, 2)) == 0) {
textTemp = textTemp[0];
} else if (text.length === 2 && !backspaceFlag) {
textTemp += '/';
setBackspaceFlag(true);
} else if (text.length === 2 && backspaceFlag) {
textTemp = textTemp[0];
setBackspaceFlag(false);
} else {
textTemp = textTemp[0];
}
}
setExpirationDate(textTemp);
};
const [expiratoinDate, setExpirationDate] = useState(''); const [backspaceFlag, setBackspaceFlag] = useState(false);
const handleExpirationDate = (text) => {
if(backspaceFlag===false){
if(text.length==2){ setExpirationDate(text+"/"); setBackspaceFlag(true) }
`else{
setExpirationDate(text)
}`
}
else{
if(text.length==2){
let text2=expiratoinDate.slice(0,1)
`setExpirationDate(text2);`
`setBackspaceFlag(false)
}`
`else{
setExpirationDate(text)
}`
}
`};`
Adding an answer that resolves not being able to delete the '/' in above solution.
const setExpiry = (e) => {
const { name, value } = e.target;
var v = value;
if (v.includes('/') == false) {
if (v.length === 4) {
var a = v.substr(0, 2);
var ae = v.charAt(v.length - 2) + v.charAt(v.length - 1);
e.target.value = a + '/' + ae;
}
}
}
Is there a way in ExtJS 4 to add a background colour to the cells in the whole table, depending only on the value in the cell and not on the columns?
Add renderer to your column:
renderer: function (val, metadata, record) {
var backgroundColor = null;
if (val) {
if (val == 1) backgroundColor = "green";
if (val == 2) backgroundColor = "red";
}
metadata.style = 'background-color: ' + backgroundColor + ';';
return '';
}
Here is a working example: https://fiddle.sencha.com/fiddle/b73
This works for me...
renderer : function(value, meta) {
if(parseInt(value) > 0) {
meta.style = "background-color:green;";
} else {
meta.style = "background-color:red;";
}
}
Basicly what you want to do is:
Create 1 renderer for all columns:
columns:{
defaults: {
renderer: myrenderer
},
items:[
//your column definitions here...
]
}
The renderer is smth like this:
var myrenderer = function(value, metaData, record, rowIndex, colIndex, store, view) {
if (value >= 0 && value < 25) {
metaData.tdCls += 'x-change-cell-red';
} else if (value >= 25 && value < 50) {
metaData.tdCls += 'x-change-cell-orange';
} else if (value >= 50 && value < 75) {
metaData.tdCls += 'x-change-cell-yellow';
} else if (value >= 75 && value < 100) {
metaData.tdCls += 'x-change-cell-green';
} else if (value === 100){
metaData.tdCls +='x-change-cell-awesome-green';
}else {
metaData.tdCls += 'x-change-cell-violet';
}
return value + '%';
}