I'm working on react-native project (main target is iPhone 6) and got some problems with including new elements in accessibility chain. For some reasons Voice Over does not update when new element appears after re-rendering. Hidden button does not appear in accessibility chain after running showButton() method. It becomes visible, but iOS Voice Over does not see it. The problem occurs only when app does something asynchronously. Here is my code:
export default class SmartView extends Component {
constructor(props) {
super(props)
this.state = {
showButton: false,
}
}
showButton = () => {
setTimeout(() => {
this.setState({ showButton: true })
}, 500)
}
render() {
const { showButton } = this.state
return (
<View style={style.root}>
<Button
onPress={this.showButton}
accessibilityRole="button"
accessibilityTraits="button"
accessibilityLabel="appeared"
accessible
simple
>
<Text>Appeared</Text>
</Button>
{showButton && (
<Button
accessibilityRole="button"
accessibilityTraits="button"
accessibilityLabel="appeared"
accessible
simple
>
<Text>Hidden</Text>
</Button>
)}
</View>
)
}
}
So, if I remove setTimeout and do state updating in current js stream, everything work fine. Is there any possibility to make something like VoiceOverReload()?
I use: react-native v0.59.9 and iPhone 6, software version 12.4
Thanks.
Below demo works fine, probably your custom Button component has issues
import React, { useState } from 'react'
import { View, Text, TouchableOpacity } from 'react-native'
export default function Screen () {
const [showButton, setShowButton] = useState(false)
function handleShow () {
setTimeout(() => {
setShowButton(true)
}, 1000)
}
return (
<View style={{ padding: 40 }}>
<TouchableOpacity
onPress={handleShow}
accessibilityRole='button'
accessibilityTraits='button'
accessibilityLabel='This button label is long for demo'
accessible
>
<Text>Appeared</Text>
</TouchableOpacity>
{showButton && (
<TouchableOpacity
accessibilityRole='button'
accessibilityTraits='button'
accessibilityLabel='hidden'
accessible
>
<Text>Hidden</Text>
</TouchableOpacity>
)}
</View>
)
}
If your view is going to update and you need voice over to detect the change faster, the you can add the following trait to the parent view: frequentUpdates. This will be the equivalent of setting "Updates Frequently" on the accessibility properties in XCode, as explained in the following answer: Making dynamically updating content in a UITableView accessible for VoiceOver
This works for ReactNative 0.59, though its deprecated and I don't know how to do it in newer versions of RN.
Related
I have created a custom component which takes a color name from the parent component and updates that color in the state in the parent component. Currently, after I have done all the code, it does not save the new color, and therefore, does not update the the state.
This is for a react-native android app that I am building. I have looked at the ReactNative documentation for flatlist and textinput. I have looked at Stack overflow for solutions too
Set up a react native project. this is my parent component
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
backgroundColor: "blue",
availableColors: [
{name: 'red'}
]
}
this.changeColor = this.changeColor.bind(this)
this.newColor = this.newColor.bind(this)
}
changeColor(backgroundColor){
this.setState({
backgroundColor,
})
}
newColor(color){
const availableColors = [
...this.state.availableColors,
color
]
this.setState({
availableColors
})
}
renderHeader = ()=>{
return(
<ColorForm onNewColor={this.newColor} />
)
}
render() {
const { container, row, sample, text, button } = style
const { backgroundColor, availableColors } = this.state
return (
<View style={[container,{backgroundColor}, {flex: 1}]} >
<FlatList
data={availableColors}
renderItem={
({item}) =>
<ColorButton
backgroundColor={item.name}
onSelect={(color)=>{this.changeColor(color)}}>
{item.name}
</ColorButton>}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={this.renderHeader}
>
</FlatList>
</View>
);
}
}
this is the code for ColorForm component
class ColorForm extends Component {
constructor(props) {
super(props);
this.state = {
txtColor:'',
}
this.submit = this.submit.bind(this)
}
submit() {
this.props.onNewColor(this.state.txtColor.toLowerCase())
this.setState({
txtColor: 'yellow',
})
}
render() {
const {container, txtInput, button} = style
return (
<View style={container}>
<TextInput style={txtInput}
placeholder="Enter a color"
onChangeText={(txtColor)=>this.setState({txtColor})}
value={this.state.txtColor}></TextInput>
<Text
style={button}
onPress={this.submit}>Add</Text>
</View> );
}
}
and below is the code for ColorButton component
export default ({backgroundColor, onSelect=f=>f}) => {
const {button, row, sample, text} = style
return (
<TouchableHighlight onPress={()=>{onSelect(backgroundColor)}} underlayColor="orange" style={button}>
<View style={row}>
<View style={[sample,{backgroundColor}]}></View>
<Text style={text}>{backgroundColor}</Text>
</View>
</TouchableHighlight>
)
}
The imports and stylesheets are setup as standard and do not effect the code so I have chosen to not show them.
EDIT: Adding the expo snack here.
Expected Behavior:
When I press "ADD" on the ColorForm component, it should take that color and add that to the this.state.availableColor array and therefore visible in the ColorButton component. And when I touch the button, it should make that change
Current behaviour:
When I enter a color and press on add, it makes an empty button in the ColorButton component - NOT the color i entered in the color I entered in the ColorForm component.
EDIT: Adding the expo snack here.
Your state is updating but the FlatList is not updating. Because your data={availableColors} in flatlist is not changing but your state is changing .
Try to add extraData
A marker property for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.
Try this
<FlatList
extraData={this.state.backgroundColor}
Updated Answer
the problem is in this function newColor(color)
const availableColors = [
...this.state.availableColors,
color
]
you just receive a string of color but you have defined object like this {name: 'red'}
please use this code
newColor(color){
const availableColors = [
...this.state.availableColors,
{name: color}
]
this.setState({
availableColors
})
}
Snack link with example : https://snack.expo.io/#mehran.khan/healthy-cake-state-sample
Also add export default to main component to remove error of launch
export default class HomePage extends Component {
App Preview
I had many problems using setState() in the way you are using now. I recommend you to use in this way setState(), with a callback:
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});
This is one of the article that talks about it.
setState() does not always immediately update the component. It may
batch or defer the update until later.
From react website setState().
I am currently running into an issue where I can open my react-native modal just fine but once it's open I can't seem to close it. I just started using react-native about three weeks ago so I am extremely new to this.
I have tried implementing solutions that I've found online but nothing seemed to work for me. The opening functionality is great and seems to be working perfectly but when it comes to closing the modal none of the things I've tried have seemed to give the modal that ability. I have not been able to find a solid solution for my exact problem anywhere!
This is how I am opening the modal.
constructor(props) {
super(props);
this.state = {
refreshing: false,
display: false
};
}
triggerModal() {
this.setState(prevState => {
return {
display: true
}
});
}
<View>
<Button onPress = { () => this.triggerModal() } title = "Open Modal"></Button>
<DisplayModal display = { this.state.display } />
</View>
This is the modal itself, I am trying to use a button to close it.
import React from 'react'
import { Modal, View, Image, Text, StyleSheet, Button } from 'react-native';
const DisplayModal = (props) => (
<Modal visible={ props.display } animationType = "slide"
onRequestClose={ this.display }>
<View>
<Button title="close" onPress = { () => !props.display }></Button>
</View>
</Modal>
)
export default DisplayModal;
As my familiarity with react-native is limited, it has been difficult wrapping my head around how some aspects of the framework function... I'm probably just making a dumb mistake somewhere in the code.
I appreciate any help with this problem!
You've almost got it, however we can make a few tweaks to get it working as you want.
As your DisplayModal has no state of its own, the state must be controlled by its parent component. So with that in mind we can do the following. Firstly pass an additional prop called closeDisplay to the DisplayModal. We're going to pass a function that sets the display property in state to false.
<DisplayModal
display={this.state.display}
closeDisplay={() => this.setState({display: false})} // <- we are passing this function
/>
Then in our DisplayModal component we are going to call that function to close the modal. So your DisplayModal component should look like this:
const DisplayModal = (props) => (
<Modal
visible={ props.display }
animationType = "slide"
onRequestClose={ this.display }>
<View>
<Button
title="close"
onPress = { () => props.closeDisplay() }> // <- here we call the function that we passed
</Button>
</View>
</Modal>
)
Notice that the onPress function of the Button in the DisplayModal component, we are calling the function closeDisplay(). This function then sets the state in the parent component, which in turn gets passed back down to the DisplayModal component causing it to hide.
I have a button and when I click on it, button changes its state. But I have to double tap to change the state. I console logged it and on first click, it shows blank and when I click on it again, it will change its state. Below is my code:
class CustomButtonOne extends React.Component {
constructor(props) {
super(props);
this.state = {
buttonOne:'',
};
this.selectionOnPressOne = this.selectionOnPressOne.bind(this),
}
selectionOnPressOne = () => {
this.setState({
buttonOne:'ONE'
})
console.log(this.state.buttonOne, 'this.state.buttonOne')
}
render() {
return (
<View>
<TouchableOpacity
onPress={()=> this.selectionOnPressOne()}>
<Text>Button</Text>
</TouchableOpacity>
</View>
)
}
}
export default CustomButtonOne
Why is this happening? and How can I change the state with one tap?
Any advice or comments would be appreciated thanks!
The setState action is async, therefore the changes that you log may not be available immediately.
Try this
this.setState({
buttonOne:'ONE'
}, () => console.log(this.state.buttonOne, 'this.state.buttonOne'))
I'm new to react native.
My screen contains 5 buttons, each one opens the same <Modal>, but the <View> inside it will change depending on the button clicked.
If I click the first button, a text input will be shown into the modal.
If I click the second button, a switch will be shown into the modal.
I've made a modal component (Modal.tsx) :
export default class Modal extends Component {
constructor(props) {
super(props)
}
public render() {
return (
<View style={style.modal} >
{this.props.children}
<View>
)
};
}
// Specific modal implementation with TextInput
const ModalWithTextInput = props => (
<Modal>
<TextInput
value={props.someValue}
/>
<Modal>
)
// Specific modal implementation with Switch
const ModalWithSwitch = props => (
<Modal>
<Switch
value={props.someValue}
/>
<Modal>
)
And now in my 5-button-screen (ButtonsScreen.tsx), I open the right modal depending on the button clicked :
openTextModal = () => {
this.setState({ modalType: 'text' });
}
openSwitchModal = () => {
this.setState({ modalType: 'switch' });
}
These functions are called with, for example, onPress={this.openTextModal}
Finally, I render the modal, to be able to do something like :
<View>
{this.renderModal(modalType)}
</View>
As this :
renderModal = (type) => {
if (type === 'text') {
return <ModalWithTextInput someValue="default text" />
}
if (type === 'switch') {
return <ModalWithSwitch someValue={false}/>
}
}
When I try to open a modal with onPress={this.openTextModal}, nothing happens (no error, no warning).
Anyone can please help ? Thanks.
You need to extract modalType from state, in the render method of your component that displays the Modal.
Clicking the button, only set's state, you need to handle state in the component in order to trigger a refresh. A refresh of the render method will render your Modal changes; React 101.
render() {
const { modalType } = this.state;
return (
<View>
{this.renderModal(modalType)}
</View>
);
}
Based on the fact that this is pretty much my code from your other question. I strongly suggest you take a step back, learn the basic's of React rather than just asking people to piece together solutions which you do not understand. Otherwise the result is you learn very little and have code that you do not understand.
I am trying to use from React-virtualized.
In the following component I am trying to call public methods. The problem is, these methods are called (I see them called while debugging, but they have no visible effect.
import React, {Component} from "react";
import {List, AutoSizer, CellMeasurer, CellMeasurerCache} from "react-virtualized";
class InfiniteScroller extends Component {
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 50
});
this.state = {
currentLineSt: 50,
currentLineEnd: 100,
}
}
renderRow = ({index, parent, key, style}) => {
let className = "code-line";
if (this.state.currentLineSt <= index && this.state.currentLineEnd >= index) {
className += " code-line-focus";
}
return (
<CellMeasurer
key={key}
cache={this.cache}
parent={parent}
columnIndex={0}
rowIndex={index}
>
<div style={style} className={className}>
<span className={"code-index"}><b>{index}: </b></span>
<span className={"code"} style={{whiteSpace: "pre-wrap"}}>{this.props.data[index]}</span>
</div>
</CellMeasurer>
)
};
componentDidUpdate() {
// these methods are called but do nothing visible
this.myInfiniteList.forceUpdateGrid();
this.myInfiniteList.scrollToRow(100);
}
componentDidMount() {
// these methods are called but do nothing visible
this.myInfiniteList.forceUpdateGrid();
this.myInfiniteList.scrollToRow(100);
}
render() {
return (
<AutoSizer>
{
({width, height}) => {
return <List
ref={(ref) => this.myInfiniteList = ref}
forceUpdateGrid
rowCount={this.props.data.length}
width={width}
height={height}
deferredMeasurementCache={this.cache}
rowHeight={this.cache.rowHeight}
rowRenderer={this.renderRow}
/>
}
}
</AutoSizer>
);
}
}
export default InfiniteScroller;
I need to call them since:
1) After data change, line size does not change
2) Need a way to scroll to line on click.
Any ideas why it doesn't work or how I could do this differently would be greatly appreciated.
You'll have to talk more Brian to get a real understanding, but it appears your list isn't fully initialized on componentDidMount.
Note in this sandbox (took yours and tweaked): https://codesandbox.io/s/lro6358jr9
The log of the element in componentDidMount has an array of 0 for children, whereas the log when I click to do the same thing later has 26 children (and works fine).
I've noticed a lot of weird first load issues in react-virtualized usages (like your list not loading initially). Keep in mind that if you're giving react-virtualized data you expect it to update on, make sure that data is changing a prop somewhere. Otherwise nothing inside will re-render.