React Native: passing data between screens - react-native

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>
}
}

Related

React Native components seem to be sharing a state

I'm having an issue with React-native where I have a component TouchTimer which uses an AnimatedTimer component. This timer is supposed to start and stop when it is tapped, which it does, however all of the TouchTimer components I add to a page will start and stop whenever any of them are tapped, rather than only affecting the tapped component.
Below is a snippet of my component:
TouchTimer.tsx
export class TouchTimer extends React.Component<TouchTimerProps> {
state: {
...
paused: boolean,
}
constructor(props) {
super(props);
...
this.state = {
...
paused: true,
}
}
startStop() {
this.setState({paused: !this.state.paused});
}
render() {
const { time } = this.props;
return (
<TouchableHighlight onPress={() => this.startStop()}>
<View>
<AnimatedTimer
...
time={time}
pause={this.state.paused}
/>
<View style={styles.timeContainer}>
<Text style={styles.time}>{this.state.remaining}</Text>
</View>
</View>
</TouchableHighlight>
)
}
}
And here is a snippet of the screen containing these components:
Details.tsx
import { TouchTimer } from '../components/TouchTimer';
...
export class RecipeDetailsScreen extends React.Component<NavigationInjectedProps> {
...
{this.state.steps.map(step => (
<List.Item
key={step.id}
title={"Step " + step.index}
style={styles.step}
description={step.short_desc}
right={() => (step.time > 0 &&
<TouchTimer
time={step.time * 60000}
/>
)}
/>
)
}
I have tried wrapping the TouchTimer components in a View and changing the paused boolean to a prop, to no avail.
I have also tested to see if this issue appears when the components are not siblings, and when they are not produced as the result of a callback, and the issue still persists in both these cases.
If anybody has any advice or answers on how to make these timers independent I would very much appreciate it!
Curiously that component seems to be implemented with a global pauseFlag that applies to all component instances. See https://github.com/dalisalvador/react-native-animated-timer/blob/master/src/Components/AnimatedTimer.js#L34
So I don't think you're doing anything wrong here, this is a limitation of the library code that is coupling all instances of your timer to the same pauseFlag value.

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 - Interactive initial page

I'm interested in having a view which initially loads with my React Native app that essentially has nested components in it. These components will give visual queues to the user as to what state the app is in, eg: still loading data from the server, etc. Basically, it's not just a static splash screen. I might also add some kind of spinner/progress bar, eg: other animated components.
There are solutions out there for static splash screens that initially show while your app loads into memory, but I need to be able to load an initial component, and then remove it when the application's data is ready to go. Is there a convention/pattern to follow in order to achieve this? Is there a way to mount a component, then remove it when it's no longer necessary allowing the rest of the app to be displayed? What's a best practice for this using React Native?
This is what I used to do:
Use <Modal /> to provide your initial, interactive page. It will blocks the screen, with semi-transparent background; If you like it to be full width, just use flex: 1 within the <View /> inside <Modal />.
Use global object / queue for loading status information. My choice is rxjs, then your initial page can just listen to this one source of truth, suggest a BehaviorSubject. So you can subscribe on it for something like:
...
{ tag: 'FetchRemoteData', progress: 10 }
{ tag: 'LoadingComponent', progress: 5 }
{ tag: 'FetchRemoteData', progress: 20 }
...
Read it until match your "load complete" conditions, then close the model.
To make it clear with code.
app.js
render() {
return (
<View>
<InitialBlockingPage />
<YourMainApp />
</View>
);
}
initial-blocking-page.js
constructor(props) {
super(props);
this.state = {
visible: true
};
}
componentDidMount() {
globalQueue.subscribe( () => {
/// pseudo code: until fully loaded
if (fullloaded) this.setState({visible: false});
});
}
render() {
return (
<Modal visible={this.state.visible}>
<SplashScreenWithData />
</Modal>
);
}

react-native scrollView - scrollToEnd - on Android

I'm trying to call a function that will fire upon onFoucs on TextInput that will scroll the scrollView all the way down (using scrollToEnd())
so this is my class component
class MyCMP extends Component {
constructor(props) {
super(props);
this.onInputFocus = this.onInputFocus.bind(this);
}
onInputFocus() {
setTimeout(() => {
this.refs.scroll.scrollToEnd();
console.log('done scrolling');
}, 1);
}
render() {
return (
<View>
<ScrollView ref="scroll">
{ /* items */ }
</ScrollView>
<TextInput onFocus={this.onInputFocus} />
</View>
);
}
}
export default MyCMP;
the component above works and it does scroll but it takes a lot of time ... I'm using setTimeout because without it its just going down the screen without calculating the keybaord's height so it not scrolling down enough, even when I keep typing (and triggering that focus on the input) it still doesn't scroll all the way down.
I'm dealing with it some good hours now, I did set the windowSoftInputMode to adjustResize and I did went through some modules like react-native-keyboard-aware-scroll-view or react-native-auto-scroll but none of them really does the work as I need it.
any direction how to make it done the right way would be really appreciated. thanks!
Rather than using a setTimeout you use Keyboard API of react-native. You add an event listener for keyboard show and then scroll the view to end. You might need to create some logic on which input is focused if you have more than one input in your component but if you only have one you can just do it like the example below.
Another good thing to do is changing your refs to functional ones since string refs are considered as legacy and will be removed in future releases of react. More info here.
class MyCMP extends Component {
constructor(props) {
super(props);
this.scroll = null;
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow.bind(this));
}
componentWillUnmount () {
this.keyboardDidShowListener.remove();
}
_keyboardDidShow() {
this.scroll.scrollToEnd();
}
render() {
return (
<View>
<ScrollView ref={(scroll) => {this.scroll = scroll;}}>
{ /* items */ }
</ScrollView>
<TextInput />
</View>
);
}
}
export default MyCMP;
If you have a large dataset React Native docs is telling you to go with FlatList.
To get it to scroll to bottom this is what worked for me
<FlatList
ref={ref => (this.scrollView = ref)}
onContentSizeChange={() => {
this.scrollView.scrollToEnd({ animated: true, index: -1 }, 200);
}}
/>