setAccessibilityFocus using ref not working - react-native

I'm using the ref prop along with findNodeHandle on a bunch of components in order to be able to trigger AccessibilityInfo.setAccessibilityFocus. However, it's not always working as expected. Sometimes the reference is null even though componentDidMount has executed.
I'm often using setAccessibilityFocus in order to focus the header of a new element which appears on the screen, for example when opening a modal.
IMPORTANT: This is Voiceover/Talkback functionality so you'll need to have that activated on your device.
See my snack: https://snack.expo.io/#insats/example-accessibilityinfo-setaccessibilityfocus-not-working
This is the code sample:
import React, { Component } from 'react';
import {
View,
Text,
findNodeHandle,
TouchableOpacity,
AccessibilityInfo,
StatusBar,
} from 'react-native';
class Sample extends React.Component {
constructor(props) {
super(props);
this.accessibilityRef = null;
}
componentDidMount() {
console.log('componentDidMount');
this.setAccessibilityFocus();
}
setAccessibilityRef(el) {
console.log('setAccessibilityRef', el);
this.accessibilityRef = el;
}
setAccessibilityFocus() {
console.log('setAccessibilityFocus', this.accessibilityRef);
if (this.accessibilityRef) {
const reactTag = findNodeHandle(this.accessibilityRef);
AccessibilityInfo.setAccessibilityFocus(reactTag);
}
}
render() {
console.log('Rendering Sample');
return (
<Text ref={this.setAccessibilityRef}>
This text ought to be read out loud by the screenreader if enabled
</Text>
);
}
}
export default class App extends React.Component {
state = {
open: false,
};
toggle = () => this.setState({ open: !this.state.open });
render() {
return (
<View style={{ margin: 50 }}>
<StatusBar hidden />
<TouchableOpacity
style={{ backgroundColor: 'blue', padding: 20, marginBottom: 20 }}
onPress={this.toggle}>
<Text style={{ color: 'white' }}>
{this.state.open ? 'Hide text' : 'Show text'}
</Text>
</TouchableOpacity>
{this.state.open && <Sample />}
</View>
);
}
}

I don't really understand what is causing these issues. I've found that calling the setAccessibilityFocus twice solves the problem. You can simplify the logic of focusing by just handling everything in the callback ref as well.
Example:
export default () => {
const setInitFocus = (element: React.Component | null) => {
if (element == null) return;
const elementId = findNodeHandle(element);
if (elementId) {
AccessibilityInfo.setAccessibilityFocus(elementId);
AccessibilityInfo.setAccessibilityFocus(elementId);
}
};
return (
<TouchableOpacity
onPress={() => {}}
ref={setInitFocus}
>
<Text>Blah blah</Text>
</TouchableOpacity>
);
};
Here's your snack with those changes applied:
https://snack.expo.io/#loganlim/example-accessibilityinfo-setaccessibilityfocus-not-working

Related

How to call a component onPress TouchableOpacity in react-native

I am using TouchableOpacity from react-native. The code is:
<TouchableOpacity onPress={onPress}>
<Text style={styles.supplementItem}>{item.item}</Text>
</TouchableOpacity>
where the OnPress function is as:
const onPress = () => (
// eslint-disable-next-line no-sequences
<Text style={styles.supplementItem}>Hello</Text>
this.setState({tempKey: tempKey + 1})
);
I looked at: this question and tried to do like it. But this is not working. I am setting my states as follows:
constructor(props) {
super(props);
this.state = {
tempKey: 0,
};
}
Kindly help me what I am doing wrong.
The question which you mentioned is using function based components and you are using class based components as you showed the costructor part which proves that.
So onPress must be a method in that class in your case, so you don't need the const keyword before it and you need to call it in this way ; this.onPress
In nutshell, your whole component should be like this;
import React from 'react';
import {Text, TouchableOpacity, View} from 'react-native';
class YourComp extends React.Component {
constructor(props) {
super(props);
this.state = {
tempKey: 0,
show: false
};
}
onPress = () => {
// eslint-disable-next-line no-sequences
this.setState(prevState => ({tempKey: prevState.tempKey + 1}))
};
render() {
return (
<View>
<TouchableOpacity style={{height: 100, justifyContent: 'center', alignItems: 'center'}} onPress={() => this.onPress()}>
<Text>Hello (item.item in your case)</Text>
</TouchableOpacity>
<Text key={this.state.tempKey.toString()}>Hello {this.state.tempKey}</Text>
</View>
)
}
}
export default YourComp;
If you want to show some component conditionally;
this.state = {
...,
show: false
}
Then in the onPress method;
this.setState(prevState => ({show: !prevState.show})) // this will make <Text /> to toggle the modal when clicked.
Then in the render method;
<Text>
{this.state.show && (
<YourNewComponent />
) || null}
</Text>

I want to change background of a more than one child on press of one of the special child

Hello I have just started to learn react native, and i am stuck at this. I am not sure this the right way to do or need to change. Please help.
I have a parent component and a child component. Parent contain flatlist and rendering child. If a child is special then want to change background of multiple child, if not then only his background will change.
class Parent extends Component {
constructor(props){
a = ['a','h','n','1','2','3'];
this.state={
list=a,
};
}
render(){
return (
<View>
<FlatList
data={this.state.list}
renderItem={(item) => (
<Child name={item['item']} />)} />
</View>
)
}
}
class Child extends Component {
constructor(props){
this.state={
itemState:"off"
}
pressed(name){
//if name is alpha it's special change background of multiple.
//else change background of this only.
}
getImage(){
if(this.state.itemState === "on){
return require('onImgPath')
else
return require('offImgPath')
}
render(){
var imgp=this.getImage();
return (
<TouchableOpacity onPress={this.pressed.bind(this,this.props.name)>
<ImageBackground source={imgp}>
<Text>{this.props.name}</Text>
</ImageBackground>
</TouchableOpacity>
)
}
}
on the press of flatlist item i.e. child i want to change image. which will be based on name of child if its 'a' then all non special child will change background to 'on'. if 'h' then only first half will change to 'on' and other to 'off' and if it's 'n' then all non special will change to off image.
Please advise how to make it work and which way should be proper to handle such kind of situation.
You should precise :
if the name is alpha it's special change background of multiple.
Correction
This is a possible correction to your code :
import React, { Component } from 'react';
import { StyleSheet, View, FlatList, Text, ImageBackground,TouchableOpacity } from 'react-native';
export default class Parent extends Component {
constructor(props){
super(props);
let a = ['a','h','n','1','2','3'];
this.state={
list:a,
};
}
_renderItem = ({ item, index }) => (<Child name={item} /> );
render(){
return (
<View style={styles.container}>
<FlatList
data={this.state.list}
renderItem= {this._renderItem}/>
</View>
)
}
}
class Child extends Component {
constructor(props){
super(props);
this.state={
itemState:true,
}
}
pressed = () => {
if(this.props.name === 'alpha'){
// Do stuffs
}
this.setState({itemState:!this.state.itemState});
console.log(this.props.name);
}
render(){
return (
<TouchableOpacity style={styles.container} onPress={()=>this.pressed()} style={{ width: '100%', height: 80 }}>
<ImageBackground source={this.state.itemState === true ? require('assets/on.png') : require('assets/off.png')} style={styles.image}>
<Text style={styles.text}>{this.props.name}</Text>
</ImageBackground>
</TouchableOpacity>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
text : {
fontSize:16,
marginLeft:20,
color:'white'
},
image : { width: '100%', height: '100%' }
})
Start screen
Clicked on 'n' and '2'
Conclusion
you must call 'super' in the constructor
be aware to close all bracket opened: {}
look at the difference with your code, learn and enjoy!
A more precise answer
If I look to the question in the title of this thread, one solution is to control the state of your children in the parent component. You must think of the behaviour of the children in this case.
This is a solution :
import React, { Component } from 'react';
import { StyleSheet, View, FlatList, Text, ImageBackground,TouchableOpacity } from 'react-native';
let a = [
{name :'a',itemState: true,linked:['n','1']},
{name :'h',itemState: true},
{name :'n',itemState: true},
{name :'1',itemState: true},
{name :'2',itemState: true,linked:['h']},
{name :'3',itemState: true}
];
export default class Parent extends Component {
constructor(props){
//Never forget to call super()
super(props);
this.state={
list:a,
listLinked : []
};
}
//Don't forget to pass the callBack function
//This callBack process the state of items in list
_renderItem = ({ item, index }) => (<Child item={item} callBack={this._callBack}/> );
_callBack = (entrie) => {
//Look if item has linked elements and do the job
if (undefined != entrie.item.linked){
for(var i=0; i< entrie.item.linked.length; i++){
var indexLinkedItem = a.findIndex(element => element.name===entrie.item.linked[i]);
//Here you must think to the behaviour of your function
//By default, I toggle the actuel state of the linked item
//If you want to force them to be true or false
//You have to write :
// a[indexLinkedItem].itemState = true
//or
// a[indexLinkedItem].itemState = false
a[indexLinkedItem].itemState = !a[indexLinkedItem].itemState;
}
}
//In all case, the state of the pressed item change
var indexItem = a.findIndex(element => element.name===entrie.item.name);
a[indexItem].itemState = !a[indexItem].itemState;
//Assign the new list a to the state
this.setState({list : a});
}
render(){
console.log('main');
return (
<View style={styles.container}>
<FlatList
data={this.state.list}
renderItem= {this._renderItem}
//Use extraData to be sure flatlist rerender after _callBack
extraData={this.state}/>
</View>
)
}
}
class Child extends Component {
//This is a stateless component
pressed = () => {
this.props.callBack(this.props);
}
render(){
return (
<TouchableOpacity style={styles.container} onPress={()=>this.pressed()} style={{ width: '100%', height: 80 }}>
<ImageBackground source={this.props.item.itemState === true ? require('assets/on.png') : require('assets/off.png')} style={styles.image}>
<Text style={styles.text}>{this.props.item.name}</Text>
</ImageBackground>
</TouchableOpacity>
)
}
}
//This is the style you can change
//ImageBackground must have size
const styles = StyleSheet.create({
container: {
flex: 1,
},
text : {
fontSize:16,
marginLeft:20,
color:'white'
},
image : { width: '100%', height: '100%' }
})
From the start screen, when I click on the first item 'a', the state of the linked items 'n' and '1' change too :
Conclusion
You must think about what happens if I click on 'n' now?
Does it change only the state of 'n' or the state of 'a', 'n' and '1'?
And what happens if you have another item linked with 'a', 'n' or '1'?
Watch the code in action here :
https://www.youtube.com/watch?v=tmsfhZ53zNE&feature=youtu.be

Update an input field in the webview from react-native component

I have a webview component like this:
export default class Home extends React.Component {
onMessage(m) {
//Update an input field in the webview with a custom value
}
render(){
let jsCode = '';
return (
<WebView
ref={(webView) => { this.webView.ref = webView; }}
injectedJavaScript={jsCode}
url={this.state.url}
onMessage={m => this.onMessage(m)}
/>
)
}
}
The webpage has an input field with an id='inpOne'. onMessage prop is triggered by a button click inside the webpage. How to update the input field with the above id when the onMessage prop is executed?
Stripped most of the code for brevity.
Probably like this.
export default class Home extends React.Component {
onMessage(m) {
const js = `document.getElementById('inpOne').value = ${m};`
this.webView.injectJavaScript(js);
}
}
Also, check your WebView's ref prop definition. It looks incorrect. Should be ref={ref => (this.webView = ref)}
Here is the full code of how to change HTML inside of WebWiew from React Native
import React, { Component } from 'react';
import { Text, View, TouchableHighlight } from 'react-native';
import { WebView } from 'react-native-webview';
export default class Sample extends Component {
constructor(props) {
super(props);
}
sendDataFromReactNativeToWebView() {
let injectedData = `document.getElementById("login_field").value = 'xyz#github.com';`;
this.webView.injectJavaScript(injectedData);
}
render() {
return (
<View style={{ flex: 1, marginTop: 30 }}>
<TouchableHighlight style={{ padding: 10, backgroundColor: 'gray', marginTop: 20 }} onPress={() => this.sendDataFromReactNativeToWebView()}>
<Text style={{ color: 'white' }}>Send Data To WebView from React Native</Text>
</TouchableHighlight>
<WebView
style={{ flex: 1 }}
source={{ uri: 'https://github.com/login' }}
ref={(webView) => this.webView = webView}
/>
</View>
);
}
}

State does not change until the second button press? Adding a call back throws an error "invariant violation: invalid argument passed"

I'm trying to render a Flatlist that contains a store name. Upon clicking the store name, more information about the store should be displayed.
I attempted to change state and then use
{this.renderMoreDetails(item) && this.state.moreDetailsShown}
to get the additional information to appear. However, it was clear via console.log that state was only changed after a second button press.
From what I read in this article 1 and this article 2 it seemed as though I needed a callback function as an additional parameter to my setState().
I tried adding a callback function(probably incorrect, but I'm extremely lost at this point) and only got more errors.
I know that I have access to all of the data from renderItem inside FlatList. How do I make it appear upon click?
import React, { Component } from 'react';
import {
View,
FlatList,
Text,
TouchableWithoutFeedback,
ListView,
ScrollView
} from 'react-native'
import { NavigationActions } from 'react-navigation';
import { Header, Card, CardSection } from '../common';
import Config from '../Config';
export default class Costco extends Component<Props> {
constructor(props) {
super(props);
this.state = {
stores: [],
selectedItem: {id: null},
};
}
componentWillMount() {
const obj = Config.costcoThree;
const costcoArr = Object.values(obj);
this.setState({
stores: costcoArr,
})
}
renderListOfStores() {
return <FlatList
data={this.state.stores}
renderItem={ ({item}) => this.singleStore(item)}
keyExtractor={(item) => item.id}
extraData={this.state.selectedItem} />
}
singleStore(item) {
console.log(this.state.selectedItem.id)
return item.id === this.state.selectedItem.id ?
<TouchableWithoutFeedback
onPress={() => this.selectStore(item)}
>
<View>
<Text>{item.branchName}</Text>
<Text>Opening Time {item.openingTime}</Text>
<Text>Closing Time {item.closingTime}</Text>
<Text>Address {item.dongAddKor}</Text>
</View>
</TouchableWithoutFeedback>
:
<TouchableWithoutFeedback>
<View>
<Text>{item.branchName}</Text>
<Text>Only showing second part</Text>
</View>
</TouchableWithoutFeedback>
}
selectStore(item) {
this.setState({selectedItem: item});
console.log('repssed');
}
render() {
return(
<ScrollView>
<Header headerText="Costco"/>
<Text>hello</Text>
{this.renderListOfStores()}
</ScrollView>
)
}
}
as a workaround you can have a state that maintain your selected item to expand(or something like another view) and then you can use some conditions to work for render your flat list data.
Consider the below code and let me know if you need some more clarification.
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,FlatList,TouchableNativeFeedback,
View
} from 'react-native';
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = {
response: [],
selectedItem: {id: null},
};
}
userListLayout() {
const {response} = this.state;
return <FlatList data={response} renderItem={ ({item}) => this.singleUserLayout(item)} keyExtractor={(item) => item.email} extraData={this.state.selectedItem} />
}
singleUserLayout(item) {
return item.id == this.state.selectedItem.id ?
<TouchableNativeFeedback onPress={() => this.userSelected(item)}>
<View style={{flex:1, borderColor: 'black', borderWidth: 1}}>
<Text style={{padding: 10, fontSize: 25}}>{item.email}</Text>
<Text style={{padding: 10,fontSize: 20}}>{item.name}</Text>
</View>
</TouchableNativeFeedback>
: <TouchableNativeFeedback onPress={() => this.userSelected(item)}><Text style={{padding: 10,fontSize: 20}}>{item.email}</Text></TouchableNativeFeedback>
}
userSelected(item) {
console.log(item)
this.setState({selectedItem: item})
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(data => data.json())
.then(response => this.setState({response}))
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>HI THERE</Text>
{this.userListLayout()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

Flux (alt), TabBarIOS, and Listeners and tabs that have not yet been touched / loaded

I've got a problem that I'm sure has a simple solution, but I'm new to React and React Native so I'm not sure what I'm missing.
My app has a TabBarIOS component at its root, with two tabs: TabA and TabB. TabB is subscribed to events from a Flux store (I'm using alt) that TabA creates. TabA basically enqueues items that TabB plays. This part of the code is fine and works as expected.
The problem is that TabA is the default tab so the user can use TabA an enqueue items, but because TabB hasn't been touched/clicked the TabB component hasn't been created so it's listener hasn't been registered. Only when TabB is pressed does it get created and correctly receive events.
So how can I ensure the TabB component gets created when the TabBarIOS component is rendered? Do I need to something hacky like set the active tab to TabB on initial load and flip it back to TabA before the user does anything?
Yes, you'll need to do something hacky if you're not using a Navigator component. If you're using Navigatoryou can specify a set of routes to initially mount with the initialRouteStackprop. This is however going to need you to modify a bit the way your app works I think.
If not using Navigator, you'll indeed have to do something hacky as you suggested. I've set up a working example here based on RN's TabBar example.
Below you'll find the code of this example, check the console.log (they don't seem to work on rnplay) to see that that components are mounted on opening the app.
Example Code
var React = require('react-native');
var {
AppRegistry,
Component,
Image,
StyleSheet,
TabBarIOS,
Text,
View
} = React;
import _ from 'lodash';
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
class StackOverflowApp extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'blueTab',
notifCount: 0,
presses: 0
};
}
_renderContent = (color, pageText, num) => {
return (
<View style={[styles.tabContent, {backgroundColor: color}]}>
<Text style={styles.tabText}>{pageText}</Text>
<Text style={styles.tabText}>{num} re-renders of the {pageText}</Text>
</View>
);
};
componentWillMount() {
this.setState({selectedTab: 'redTab'});
}
componentDidMount() {
this.setState({selectedTab: 'blueTab'});
}
render () {
return (
<View style={{flex: 1}}>
<TabBarIOS
tintColor="white"
barTintColor="darkslateblue">
<TabBarIOS.Item
title="Blue Tab"
icon={{uri: base64Icon, scale: 3}}
selected={this.state.selectedTab === 'blueTab'}
onPress={() => {
this.setState({
selectedTab: 'blueTab',
});
}}>
<Page1 />
</TabBarIOS.Item>
<TabBarIOS.Item
systemIcon="history"
badge={this.state.notifCount > 0 ? this.state.notifCount : undefined}
selected={this.state.selectedTab === 'redTab'}
onPress={() => {
this.setState({
selectedTab: 'redTab'
});
}}>
<Page2 />
</TabBarIOS.Item>
</TabBarIOS>
</View>
);
};
}
class Page1 extends Component {
static route() {
return {
component: Page1
}
};
constructor(props) {
super(props);
}
componentWillMount() {
console.log('page 1 mount');
}
componentWillUnmount() {
console.log('page 1 unmount');
}
render() {
return (
<View style={styles.tabContent}>
<Text style={styles.tabText}>Page 1</Text>
</View>
);
}
}
class Page2 extends Component {
static route() {
return {
component: Page2
}
};
constructor(props) {
super(props);
}
componentWillMount() {
console.log('page 2 mount');
}
componentWillUnmount() {
console.log('page 2 unmount');
}
render() {
return (
<View style={styles.tabContent}>
<Text style={styles.tabText}>Page 2</Text>
</View>
);
}
}
const styles = StyleSheet.create({
tabContent: {
flex: 1,
backgroundColor: 'green',
alignItems: 'center',
},
tabText: {
color: 'white',
margin: 50,
},
});
AppRegistry.registerComponent('StackOverflowApp', () => StackOverflowApp);