Are there any issues using Arrow Key Stepper to scroll when the rows have tabIndex or contentEditable enabled? - contenteditable

We've implemented React-Vitualized using single column Grid with rows that are enabled for keyboard event (onKeyUp/onKeyDown/onKeyPress). We are using Arrow-Key-Stepper to enable ArrowUp/ArrowDown scrolling of the rows.
All works quite well even with PgUp/PgDn, Home/End, Space and Shift-Space. However, when we add either tabIndex and/or contenteditable attributes to the rows (required for keyboard events), scrolling freezes when the focused rows scrolls out-of-view and removed from the DOM. We can regain control by using Tab key and/or the mouse.
QUESTION: Why is tabIndex/contenteditable attributes causing scrolling failure?
I am not allowed to reproduce the code publicly. Not asking for solution nor code but more an opinion from a more experienced source. This is the last issue for our implementation of this widget library, which has been very good thus far.
Appreciate any suggest/opinion.

However, when we add either tabIndex and/or contenteditable attributes to the rows (required for keyboard events), scrolling freezes when the focused rows scrolls out-of-view and removed from the DOM.
I'm curious why the rows themselves need this attribute? The outer container rendered by ArrowKeyStepper has a tab-index, in order to be focusable. This is how ArrowKeyStepper listens to keyboard events.
If the row is focused, then when it goes out of the viewport, ArrowKeyStepper is left listening to ... nothing. The way to fix this is to use a class component to render your cell and conditionally self-focusing:
class Cell extends Component {
componentDidMount = () => this._maybeFocus();
componentDidUpdate = () => this._maybeFocus();
render() {
const {
columnIndex,
rowIndex,
scrollToColumn,
scrollToRow,
style,
} = this.props;
return (
<div
style={style}
ref={this._setRef}
tabIndex={1}
>
{/* Your content here... */}
</div>
);
};
_maybeFocus = () => {
const {
columnIndex,
rowIndex,
scrollToColumn,
scrollToRow,
} = this.props;
if (
columnIndex === scrollToColumn &&
rowIndex === scrollToRow
) {
this._ref.focus();
}
}
_setRef = ref => {
this._ref = ref;
};
}

Related

Vuetify, how to close drawer?

I have added navigation to the drawer, everything works fine except the case when the link is the same as the page is. The drawer left open after clicking on the link and it isn't obvious for the visitor that the page is the current page.
I have tried to do something like this #click.stop="(this.router.path != '/' ?
this.router.push('/') : drawer = !drawer)" Vue doesn't rapport any mistake and the code doesn't work.
Where am I wrong?
The drawer data key is looking for a boolean, if it's truthy the navigation drawer will show. So, you can add #click="drawer = false" to your menu links, and it will close the draw when any link is clicked.
Example in the docs: https://vuetifyjs.com/components/navigation-drawers#example-6
I handled this by making the drawer in my app a child of the route that uses it. The isOpen property is managed in the parent. I pass isOpen as a prop to the drawer and emit open and close events as appropriate.
Oh, I also found that timeouts are necessary to ensure the open / close animations work correctly. Someone please let me know if you found a better way to handle animations as this feels a little wonky.
I handle a few other things, like right/left justify and a return route, but ignore the noise if it isn't helpful.
Here's a parent loading the component
<my-drawer
:iconName="'my_icon'"
:isOpen="drawerIsOpen"
:justify="'right'"
:returnRoute="'home'"
#close="drawerIsOpen = false"
#open="drawerIsOpen = true"
>
// ...
</my-drawer>
Here are the methods from within the drawer component:
data() {
return {
closeDelay: 500,
width: 0,
};
},
methods: {
closeBtnClick() {
this.$emit('close');
setTimeout(() => { this.$router.push(this.returnRoute); }, this.closeDelay);
},
mounted() {
setTimeout(() => { this.$emit('open'); }, this.closeDelay);
},

React-Native: Adding a Button on press of a Button

A simple question I can't wrap my head around is this:
Let's say I have a button, if I press that button, I want another button to appear. Imagine that buttoncreation as something that loops, so I can't have a predefined list of buttons I just show or hide.
React can create with react.createElement(), but I don't seem to get the correct usage of createElement.
Can I steer around having to create Elements through react? (Kind of feels like against the nature of react to actually create new html into something)
Easier said: I'm currently developing an app that calculates a common route between multiple departures and destinations, I want to give the user the ability to enter as many departures and destinations as he'd like, since the backend algorithm can handle it.
You can try something like this:
state = {
buttons: []
}
createButton() {
let { buttons } = this.state
const button = (
<Button
onPress={this.createButton.bind(this)}
/>
)
buttons.push(button)
this.setState({
buttons
})
}
renderItem({ item }) {
return (
<View>
{item}
</View>
)
}
render() {
return() {
<FlatList
data={this.state.buttons}
renderItem={(item) => this.renderItem()}
/>
}
}

React-native: how to change row's content or style while swiping?

I want to have a functionality similar to Google Inbox (demo), when swiping an item causes a change of the background color and size of the icon.
My implementation has a ListView of Swipeable components as rows and I want to change the content or style of a row (in the right pane for example) based on the swipe position.
In order to have the ListView to re-render while swiping I added a pos state which I set using the onPanAnimatedValueRef prop of the Swipeable.
The problem is that the ListView doesn't re-render and nothing changes.
<ListView
...
renderRow={(data) => (
<Swipeable rightContent={
<View><Text>{this.state.pos}</Text></View>
}
...
onPanAnimatedValueRef={(pan) => {
pan.addListener(val => {
this.setState({
pos: val.x
});
}}>
{data}
</Swipeable>
)}
/>
Do you see any problem with this?
I would wrap the Swipeable in your own component, hold state there and your animation logic. It is not the ListView's responsibility to rerender the whole thing since the data hasn't changed, just your row itself.
Alternatively, you could change the data via setState to the ListView's data source (which presumably was based in state and hence force a rerender but this is a less efficient path)

React Native VirtualizedList get Scroll and scroll to position

So i am on RN 49.3,
I am looking for a way to get the scroll position or the index of the item visible on the virtualizedList!
PS: when i opened the source code of VirtualizedList.js which seems to have props.onScrollEndDrag & props.onScroll
We were using a different approach. It has changed a lot since, but I can help you with the initial approach. We added onScroll param to the list. We captured event.nativeEvent.contentOffset.y into the state or some variable inside spec. We used redux. When the user left the screen we saved this value inside a database. Second part was to load this value from the db in componentDidMount . You just put a ref into the list component and then call this.refs.myRef.scrollTo({ x: 0, y: loadedValue, animated: false });
Capture the scroll
render() {
return (
<VirtualizedList
ref='myRef'
onScroll={event => {
this.scroll = event.nativeEvent.contentOffset.y;
}}
...
/>
);}
Save on exit
componentWillUnmount() {
AsyncStorage.setItem(key, this.scroll);
}
Load after mounting
componentDidMount() {
AsyncStorage.getItem(key)
.then(y => {
this.refs.myRef.scrollTo({ x: 0, y, animated: false });
});
}
Best approach I think is to handle it inside redux and you connect this component with the list to the store. I mentioned saving into db because we do save the position for later use, this is only optional and depends on your requirements.
You may use the state as well, but then you need to handle the unnecessary updates

React Native: Update ListView Row when props changes

I do have a ListView component with a renderRow() function. My custom ListBountiesView component which renders a ListView Row also takes a prop called cr and displays some content in each row depending on the value of cr.
The problem is that when this.props.customerRelationship changes its value, the ListView rows do not get updated.
I am doing it with: this.props.customerRelationship.points += responseJson.points;
I guess that ListView only updates when the data attribute changes, but how can I move props to my renderRow component so that they also update the ListView?
SuperScreen.js
renderRow(bounty) {
const { customerRelationship } = this.props;
return (
<ListBountiesView
key={bounty.id}
bounty={bounty}
cr={customerRelationship}
onPress={this.openDetailsScreen}
/>
);
}
render() {
const { bounties } = this.props;
return (
<ListView
data={bounties}
renderRow={this.renderRow}
loading={loading}
/>
)
The ListView refreshes the data source when the data prop gets a new reference:
componentWillReceiveProps(nextProps) {
if (nextProps.data !== this.props.data) {
this.setState({ dataSource: this.listDataSource.clone(nextProps.data) });
}
if (nextProps.loading !== this.props.loading) {
this.setLoading(nextProps.loading);
}
}
Similarly, React Native's ListView refreshes when its data source changes. This was discussed on their GitHub and in many other threads. The generally accepted workaround is to create a new data array. In your case, do it in componentWillReceiveProps whenever customerRelationship changes.
Move your customerRelationship to bounty object. Each bounty should have this property, then check it's value changed in the rowHasChanged. Other way is to check customerRelationship in componentWillReceiveProps function, if it's value changed clone bounties so all of it's child have new object reference.