Getting undefined from `this.refs[var-name].value` - react-native

I am using pusher for realtime updates. On client side there is multiple media is loaded from database. and adding ref to <Text> component and setting it's value to the current mediaID.
this.state.feed.map((media) => {
<View>
<Text ref={media._id}>{media.likes}</Text>
<Text ref={media._id}>{media.name}</Text>
</View>
})
I am accessing the value of ref in componentDidMount when binding the pusher trigger event.
async componentDidMount() {
channel.bind('updateLikes', (data) => {
alert(this.refs[data.mediaId].value)
})
}
But, it's giving the undefined instead of the value of the <Text> component. I tried the solution from other posts, but i cound't find any of them working for my code.

Related

React native with redux is laggy on dispatch

In my app, I have a function which calls every 2s a bluetooth command to ask the current temperature of a device with a setInterval function.
The bluetooth response is given by monitored function. I use react native-ble-plx library for that.
I have no problem with this process.
The temperature is returned via a property which is dispatched via redux in an action file.
But when I "dispatch" (via redux) the function to my screen, I have a short interrupt which causes a laggy/jerky behavior. In my case, I have a slide to unlock button, and on my device when the dispatch is call, the touch operation is interrupted, and become not intuitive and annoying. It's difficult to explain the problem, but my question is simple, how I have to set react-redux not to be laggy, or not interrupt current user interaction on redux dispatch ?
My app, is based on this project structure (for react-redux with Ble) : https://github.com/momolarson/BLEServiceDiscovery
Environement:
react-native: 0.63.3
react-native-ble-plx: 2.0.2
react-redux: 7.2.1
This is pseudo code of my app (the code is more longer, but I have excluded all other by remove them) :
HomeScreen.js
import stuff[...]
class HomeScreen extends Component {
componentDidMount() {
this.timer = setInterval(() => {
this.props.readTemp();
}, 2000);
}
render() {
const { value } = this.state
return (
<>
<ScrollView>
<Text>{this.props.temperatture}"></Text>
<Slide2Unlock/>
</ScrollView>
</>
);
}
}
function mapStateToProps(state) {
return {
temperature: state.temperature,
};
}
const mapDispatchToProps = dispatch => ({
readTemp: () => bluetooth.readTemp(),
})
export default connect(mapStateToProps, mapDispatchToProps())(HomeScreen);
redux's action file : actionBt.js (my file is based on this https://github.com/momolarson/BLEServiceDiscovery/blob/master/actions/index.js)
[...]
device.monitorCharacteristicForService(
characteristicData.serviceUUID,
characteristicData.uuid,
(error, characteristic) => {
if (characteristic != null && characteristic.value != null) {
dispatch(formatTemperature(characteristic.value));
}
},
);
thanks for your help
Update 1
I make a specific version of my app, without bluetooth, just the slide to unlock module and a watcher with setInterval, and still have a laggy behavior, when the state is dispatched. I have done tests with button only, when I tap then show the value via dispatch, it's still the same trouble.
this my test code, index.js (redux action file)
export const readTemp = () => {
return (dispatch, getState, DeviceManager) => {
const state = getState();
console.log("READ TEMP");
dispatch(temperatureSensor( Math.random(0,9) ))
}
}
function BLEservices(BLEServices) {
setInterval(() => {
BLEServices.readTemp();
}, 2500);
return (
<SafeAreaView style={styles.container}>
<Slider
childrenContainer={{ }}
onEndReached={() => {
console.log('REACHED')
}}
containerStyle={{
height:40,
margin: 8,
backgroundColor: "#EEEEEE",
overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
width: '50%',
}}
sliderElement={
<Text style={{color:"#FFF"}}>TEST</Text>
}
>
<Text style={{color: "#D5BD9E"}}>unlock</Text>
</Slider>
<Text>Temperature: {BLEServices.temperatureSensor}</Text>
</SafeAreaView>
);
}
thanks for your advice, and your help
Update 2
Solution found, see my answer below. The problem was type of var user in dispatch and some side effect due to previous test I have done on app and not clean them.
I solved my problem, by finding multiple var who are contains objects. I have a var which contain four attributes, I update and use one of them. And this object was update by my watcher. When I dispatch object to get a part of this object, I have to read the whole object, and this one is fully updated by my watchern which cause laggy render. So i have splitted that, to update only per var.
Another thing I've done, I split my interface elements in multi component, before, I has a lot of code in one screen, because I didn't need to reuse them elsewhere.

React - native StackNavigator refresh previous component after hitting back button

I'm stuck for weeks on this problem that I can't solve. I have been through a lot of different solution and none of them is working..
Let-s say I have component A and component B
Component A : contains a flatlist, when click on one item leads me to component B.
Component B: contains details of component A items, when clicking on a button update data, data that component A is using in state current, which make the clicked items turn to orange. I would like when hitting back button to update those data in component A and actually have the clicked items in orange, right now nothing is happening..
Component A :
async interventionList(){
const planningList = await getInterventionList(this.state.userToken, '2019-02-27', '2019-02-27');
this.setState({list: planningList});
}
renderItem = ({ item }) => (
<View>
<TouchableHighlight onPress={
() => this.props.navigation.navigate('InterventionStart', { getIntervention: () => this.interventionsList() })}>
<View style={[styles.block, this.state.current !== null && this.state.current.remoteInterventionId === item.num ? styles.began : styles.notbegan]}>
<Text> {SomeData} <Text>
</View>
</View>
</TouchableHighlight>
</View>
)
Component B :
componentWillUnmount() {
this.props.navigation.state.params.getIntervention();
}
But nothing happens. I tried to console log, to see if those functions where called after clickin on back button and they are... So I don't understand what I am doing wrong or is it not the way to do this at all ?
You can use willFocus method of react-navigation
in your componentDidMount or in your constructor
this.willFocus = this.props.navigation.addListener('willFocus', () => {
this.interventionList();
});
It will call your interventionList method whenever your component is about to be focused. Make sure you remove the listener on your componentWillUnmount
this.willFocus.remove();
For reference
https://reactnavigation.org/docs/en/navigation-events.html

React Native ios Switch in FlatList not toggling after value changed

I am trying to toggle ios Switch in react native. But the switch comes back to initial position as soon as I change it.
What I have:
class ABC extends Component {
constructor(props) {
super(props)
this.state = {
obj: []
}
}
fetch(){
// fetch something from remote server, set it to state object array
}
setStatus(id, value){
var temp = [...this.state.obj]
temp.map((t) => {
if (t.id == id) {
t.flag = value
}
})
this.setState({ obj: temp })
}
render() {
return (
<View>
<FlatList
data={this.state.obj}
renderItem={({ item }) =>
<View>
<Text>{item.name}</Text>
<Switch
onValueChange={(val) => this.setStatus(item.id, val)}
value={item.flag}
/>
</View>
}
keyExtractor={({ id }, index) => id.toString()}
/>
</View>
);
}
}
I logged the before and after value of obj state and they seem to update. Should the FlatList be rendered again (like a web page refresh) ? Or is there something I am missing ? Searched SO for answers, couldn't find my mistake.
Flatlist has a prop called extraData.
This prop tells Flatlist whether to re-render or not.
If data in extraData changes then flatlist re-renders based on new data provided in data prop.
So whenever you need to re-render flatlist just change something in extraData.
Best way is to pass state toextraData which is passed to Data.
So, just pass extraData={this.state.obj}.
there also other way called forceUpdate.
you can call this.forceUpdate().
but this is not recommended because this will render not only flatlist but entire component in which you are calling this.

ComponentWillMount only trigger for first time?

MainComponent:
<Tabs
initialPage={this.props.day}
tabBarUnderlineStyle={{ backgroundColor: '#5AF158' }}
renderTabBar={() => <ScrollableTab />}>
{this.renderTabHeader()}
</Tabs>
renderTabHeader() {
return (
this.props.dateArray.map((date, i) =>
<Tab
key={i}
heading={date.format('DD/MM')}
tabStyle={styles.tabStyling}
activeTabStyle={styles.activeTabStyle}
textStyle={styles.tabTextStyle}
activeTextStyle={styles.activeTabTextStyle}
>
<View style={{ backgroundColor: '#EEEEEE', flex: 1 }}>
<Content contentDate={date.format('YYYY-MM-DD')} />
</View>
</Tab>
)
);
}
Content Component:
class Content extends Component {
componentWillMount() {
console.log('Component Will Mount() ?');
this.props.loadTransactionByDate({ date: this.props.contentDate });
}
render() {
return (
<View><Text>{this.props.contentDate}</Text></View>
);
}
Basically, in MainComponent there is a collection of tabs. I've noticed something rather weird which Content will be mounted on the first time their tab being click or active?
Meaning for the first time, we can click on Tab index 2 and seeing the console log in componentWillMount, then we switch to another tab and if coming back to Tab index 2 again, componentWillMount will not be triggered anymore?
First I would like to point out you should not use componentWillMount life cycle method since it has been deprecated on last minor update of React 16.3
Heres list of deprecated life cycle methods,
(componentWillMount, componentWillReceiveProps, and componentWillUpdate). You can read more about deprecated life cycle methods here.
Secondary in your example life cycle works as expected. componentWillMount triggers only once since your component will be initial rendered/mounted only one time and that's how React works.
I would work this out with following method.
Add getDerivedStateFromProps life cycle to Content component, which will trigger when component receives new props and as well on initial mount.
static getDerivedStateFromProps(nextProps, prevState) {
console.log('will log on props change');
if( nextProps.contentDate !== prevState.contentDate ) {
return { contentDate: nextProps.contentDate };
// Notice we return plain object here to update state
}
return null;
// return null when changes are not needed
}
This example checks that contentDate has changed and if so pushes it into component -state. And on render method you get it by this.state.contentDate.
render() {
return (
<View><Text>{this.state.contentDate}</Text></View>
);
}
You could achieve similar behaviour with implementing this in componentDidUpdate but then you have much bigger risk to end up with infinite loops and much worse performance. But it's possible just have strong checks that data you have expected has really changed as you would expect. Then you can do setState and component re-renders.

React Native: passing data between screens

I'm trying to create a small app, but have many questions)
I have 3 screen and use react-navigation to pass data. the scheme is following:
1) loading screen (fetch data and save it to AsyncStorage+handling data and save to obj1 for pickers)
(pass obj1)
2)main screen (get data,render pickers based on it, take selected values and pass them next)
(pass pickers selection+input)
3)some results(get data from Asyncstorage, some calculating and render results)
so I have two questions.
when I navigate back from 3) to 2) I have an error that screen2 need data, which was passed from screen1. yes - i've checked if this data pass to 3 and then to 2 when Back Button is pressed, and there is no error, but I'm sure this is bad solution
and second..trying to explain) on screen 3 some calculations made on pickers selection, so it hasn't problem. but rest of them needed get data from AsyncStorage and then convert it according to Picker values and render to ListView. Despite I'm putting getting from AS on componentWillMount it's still take much time so data for rendering is undefined. Of course I'm using states, but I think this is a bad logic of data handling..
UPD
so I'm trying pass data from child(screen) to parent(index.ios.js), where it define as first loading view( I'm using navigator's stack screens)
export default class App extends Component {
constructor(props) {
super(props);
};
}
myCallback(dataFromChild) {
console.log("yeah", dataFromChild);
}
render() {
return (
<LoadingScreen callbackFromParent={this.myCallback}/>
);
}
}
and LoadingScreen.js:
render() {
return(
<View style={{flex: 1, backgroundColor: '#dc143c', marginTop:20}}>
<Image
source={require('./WhiteLogo.png')}
style={{flex:3, height: undefined, width: undefined, marginLeft:20, marginRight:20}}
resizeMode="contain"
qwer={this.props.callbackFromParent('listInfo').bind(this)}
/>
<Spinner color='white' style={{flex:1}}/>
</View>
);
}
}
and I've got an error "Unhandled JS Exception: this.props.callbackFromParent is not a function"
AsyncStorage might not be the best solution for what you are trying. Using react-navigation for data delivery is not the best neither. I would suggest checking redux for storing states globally and delivering it to the pages when needed. There is also another way which is passing functions to the props of child components for pulling up any data from child to parent components. You can check this and this or the sample code is below.
Parent
class Parent extends Component {
updateState (data) {
this.setState(data);
}
render() {
<View>
<Child updateParentState={this.updateState.bind(this)} />
</View>
}
}
Child
class Child extends Component {
render() {
<View>
<Button title="Change" onPress={() => {this.props.updateParentState({name: 'test})}} />
</View>
}
}