unable to update parent from child - react-native

The included code is almost running. I'm trying to restart the count where it left off when I toggle the device. I'm getting the following error message:
Warning: Functions are not valid as a React child. This may happen if you
return a Component instead of <Component /> from render. Or maybe you meant
to call this function rather than return it.
here's what I was attempting:
1)pass as this.props.count class App this.state.count (this creates the starting point of the counter
2) pass a callback function from APP to COUNTER that will update APP.state.count (I do this inside of COUNTERS Inc method)
NOTE: it's a little tricky to get ignorewarnings working if you don't have the right dependencies installed. lines 3, 4 and 5 can be deleted
import React from 'react';
import {Button, StyleSheet, Text, View } from 'react-native';
import ignoreWarnings from 'react-native-ignore-warnings';
ignoreWarnings(['Warning: componentWillMount is deprecated',
'Warning: componentWillReceiveProps is deprecated'])
class Count extends React.Component {
shouldComponentUpdate(nextProps,nextState){
if(nextProps.count % 2 === 0)return true
else return true
}
render(){
return(
<Text style={styles.count}>{this.props.count}</Text>
)
}
}
class Counter extends React.Component {
constructor(props){
super(props)
this.state={
count: this.props.count,
}
console.log(this)
}
componentDidMount(){
this.interval=setInterval(this.inc,1000)
}
componentWillUnmount(){
clearInterval(this.interval)
}
inc=()=>{
//console.log(thisate)
this.setState(prevState =>({
count: prevState.count + 1,
}))
this.props.resetCount(this.state.count)
}
render() {
return (
<View>
<Count count={this.state.count}/>
</View>
);
}
}
export default class App extends React.Component {
constructor(){
super()
this.state={
show:true,
count:0,
}
}
toggle=()=> this.setState(prevState => ({
show: !prevState.show
}))
resetCounter(count){
this.setState({count: Count})
}
render(){
console.log(this.state)
if(this.state.show){
return(
<View style={styles.container}>
<Button title='toggle' onPress={this.toggle}/>
<Counter
count={this.state.count}
resetCount={(count)=>{this.resetCounter(count)}} />
</View>
)
}else{
return(
<View style={styles.container}>
<View style={styles.container}>
<Button style={styles.count} title='toggle' onPress={this.toggle}/>
<Text style={styles.count}> </Text>
</View>
</View>
)
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'lightgrey',
alignItems: 'center',
justifyContent: 'center',
},
count:{
fontSize:48
}
});

First problem i see, and i think the warning come from here, is you resetCounter function. You pass "Count" (a component) to the state. I think you would like to pass the argument : count. You could do it simply like this so there is not mistake :
resetCounter(count){
this.setState({count})
}
Second thing is your callback way. You should do it like this :
export default class App extends React.Component {
constructor(){
super()
this.resetCounter = this.resetCounter.bind(this);
this.state={
show:true,
count:0,
}
}
resetCounter(count){
this.setState({count: Count})
}
render(){
...
return(
<View style={styles.container}>
<Button title='toggle' onPress={this.toggle}/>
<Counter
count={this.state.count}
resetCount={this.resetCounter} />
</View>
...
)
}
}
It's a lot better for performance and for reading.
Hope it help !

just a typo! yureka.
resetCounter(count){
this.setState({count: Count})
}
needs to be:
resetCounter(count){
this.setState({count: count})
}

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>

setAccessibilityFocus using ref not working

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

How to change text in sibling component in react-native?

I have an 'OutputBox' component and want to change the text being displayed in the component when I click a button.
I've read about props and state and i can't seem to get them working the way i need them too. I just started react-native and have a heavy background c++. I thought i could just declare a variable, 'text' in the 'OutputBox' component and then call a 'setOutputBoxText' function and change the 'text' var. Getters and Setters paradigm. I just cant wrap my head around how to use props to 'pass args' to components and the such.
export default class HelloWorldApp extends Component {
render() {
return (
<View style={{ flex: 1, alignItems: "flex-end" }}>
<CustomButton
text="N"
onPress={() => {
OutputBox.setOutputBox('You head North');
alert("You head North");
}}
/>
<OutputBox></OutputBox>
</View>
);
}
}
class CustomButton extends Component {
render() {
const { text, onPress} = this.props;
return (
<TouchableOpacity style={styles.buttonStyle}
onPress={() => onPress()}
>
<Text style={styles.textStyle}>{text}</Text>
</TouchableOpacity>
);
}
}
class OutputBox extends Component {
constructor( props ) {
super( props );
var displayText = 'Hello Traveller';
}
setOutputBox( newText ){
displayText = newText;
}
render() {
return (
<View style={ styles.outputBox }>
<Text>{this.displayText}</Text>
</View>
);
}
}
I would expect to be able to do something similar to what i have, however i just keep getting a typeError: OutputBox.setOutputBox is not a function. I know this is the wrong paradigm for react-native. I can't seem to wrap my head around doing something like this with props and state.
UPDATE: I no longer get the error typeError: OutputBox.setOutputBox is not a function. Now, the OutputBox just doesn't display anything. How I do I get the <Text/> component of the OutputBox to change and display.
Here is the correct way to do it.
export default class HelloWorldApp extends Component {
constructor( props ) {
super( props );
this.state={
displayText : 'Hello Traveller',
}
}
render() {
return (
<View style={{ flex: 1, alignItems: "flex-end" }}>
<CustomButton
text="N"
onPress={() => {
this.setState({displayText:'You head North'})
}}
/>
<OutputBox text={this.state.displayText}/>
</View>
);
}
}
class CustomButton extends Component {
render() {
const { text, onPress} = this.props;
return (
<TouchableOpacity style={styles.buttonStyle}
onPress={() => onPress()}
>
<Text style={styles.textStyle}>{text}</Text>
</TouchableOpacity>
);
}
}
class OutputBox extends Component {
render() {
return (
<View style={ styles.outputBox }>
<Text>{this.props.displayText}</Text>
</View>
);
}
}
I just removed the OutputBox component and put a <Text> component in there. I realized i didn't need the OutputBox component, since it's only purpose was to display text. This is what i ended up with
export default class HelloWorldApp extends Component {
constructor( props ) {
super( props );
this.state={
displayText : 'Hello Traveller',
}
}
render() {
return (
<View style={{ flex: 1, alignItems: "flex-end" }}>
<CustomButton
text="N"
onPress={() => {
this.setState({displayText:'You head North'})
}}
/>
<Text>{this.state.displayText}</Text>
</View>
);
}
}
class CustomButton extends Component {
render() {
const { text, onPress} = this.props;
return (
<TouchableOpacity style={styles.buttonStyle}
onPress={() => onPress()}
>
<Text style={styles.textStyle}>{text}</Text>
</TouchableOpacity>
);
}
}

Cannot update during existing state

Index.js
I try do launch screen.
export default class LaunchScreen extends Component{
constructor(props){
super(props);
this.state= {
loaded:false
}
}
componentWillMount(){
Thread.load(v => this.setState({loaded:true}));
}
render(){
const { navigate } = this.props.navigation;
return(
<View style={styles.container}>
{this.state.loaded ? navigate("Form")
:
<View style={styles.imageContent}>
<Image style={styles.image}
source={require('../images/launch_icon.png')}/>
</View>
}
</View>
)}}
export default class thread{
static load(cb){
setTimeout(cb,3000);
}
}
when I use these codes I get the warning "can not update during an existing state transition". How to fix it?
You're trying to navigate inside a return, try to change your render code to this:
render(){
const { navigate } = this.props.navigation;
if(this.state.loaded) navigate("Form")
return(
<View style={styles.container}>
<View style={styles.imageContent}>
<Image style={styles.image}
source={require('../images/launch_icon.png')}/>
</View>
</View>
)
}
EDIT: But you should probably do the navigation part inside a shouldComponentUpdate() checking if the nextState.loaded is different from your this.state.loaded and is true, like:
shouldComponentUpdate(nextProps, nextState){
if((nextState.loaded!=this.state.loaded)&&nextState.loaded){
navigate("Form")
return true
}
return false
}
I see a couple problems, but I don't think these are your main problems.
You can only have one default export, so if both classes are actually in a single file, remove the "default" from the thread class.
Pretty sure all class names must begin with a capital letter in React, so change "thread" to "Thread"
But I think your actual problem is that you're calling navigate('Form') directly from within the render. Try adding a handleNavigation class method and calling this.handleNavigation there instead. So you'd be left with something like this...
export default class LaunchScreen extends Component {
state = {
loaded: false
}
componentWillMount() {
Thread.load( () => this.setState({loaded: true}));
}
handleNavigation = () => {
this.props.navigation('Form');
}
render() {
return (
<View style={styles.container}>
{
this.state.loaded
? this.handleNavigation
: <View style={styles.imageContent}>
<Image style={styles.image}
source={require('../images/launch_icon.png')}/>
</View>
}
</View>
);
}
}
export class Thread {
static load(cb){
setTimeout(cb,3000);
}
}

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);