React Navigation - Custom Header Animation - reactjs

I am using React Navigation inside React Native App and i created a Custom Header Component for my routes
like this :
const Router = StackNavigator({
Main: {
screen: Main,
navigationOptions: ({navigation}) => ({
header:<Header title="Main"/>
})
},
})
when using a custom header component the native animation not working
i would like to know how can i achieve the animation in the header the same as here https://reactnavigation.org/

TL:DR; found solution to share the animated.Value / interpolation over screens code below.
Animated Custom Header React-native + React navigation
This post was taunting me for some time - I was faced with the same issue. Hopefully this will reach you even if it's couple of months later :D
So first my issue was this, I made a component for custom header like in your example, my target was having one of the StackNavigator pages, have a scrollView which would in turn manipulate the color of the header.
Similar issue, the information exchange between header and page should help you too, here it goes.
Header.js
export class HeaderBar extends Component {
componentWillMount(){
// might be a bit, ehm but worked so if you have tips how to make the code nice feel free to share
let valueToManipulate= new Animated.Value(0);
this.props.navigation.setParams({
valueToManipulate,
customkey: valueToManipulate.interpolate({
inputRange: [0, 150],
outputRange: ['rgba(0,0,0,0)', 'rgba(0,0,0,1)'],
extrapolate: 'clamp',
})
})
}
render () {
... bit of code ...
// important bit for data binding !
if( ! (this.props.navigation.state.params && this.props.navigation.state.params.customkey) ){
return null;
}
/* unless that variable on params exists we do not ! render */
... bit more of code ...
<View style={ floating }>
<Animated.View style={{backgroundColor: this.props.navigation.state.params.customkey }}> /// <<--- typical bind
<View style={{flexDirection: 'row', justifyContent: "space-between"}}>
... and rest of render ...
So this is the header bit, now for the other "fun" part:
HomePage.js
export default class HomePage extends Component<{}> {
... stufff..... :D
render() {
/* this here, again data binding !, do not let render before we have this var in state params ! */
if( !( this.props.navigation.state.params && this.props.navigation.state.params.valueToManipulate ) )
return null;
return (
<ScrollView
style={styles.container}
onScroll={ Animated.event(
[{ nativeEvent: { contentOffset: { y: this.props.navigation.state.params.valueToManipulate } } }], // <-- tadaaa
)}
bounces={false}
scrollEventThrottle={1}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
>
... moar stuff ...
}
}
And here ! Finally ! A Demo !
Animated Custom Header React-native + React navigation

I published react-navigation-collapsible.
I hope it would be helpful.
https://github.com/benevbright/react-navigation-collapsible

Related

Is this the best way to share Api Data across the APP?

So, I am new to react Native and I am taking over code that was written like 5 years ago for a react Native App. Since I know things moved a lot since then I want to make sure I follow best practices.
So basically, API responses are saved in a state.
Then for each screen, there is a StackNavigator using createStackNavigator
The render method in those stack navigator look like this
import { InfoContainer } from "../api/InfoContainer ";
....
....
....
render() {
return (
<Subscribe
to={[ InfoContainer ]}
>
{(InfoStore) => (
<SettingsStack
navigation={this.props.navigation}
screenProps={{
store: InfoStore,
}}
/>
)}
</Subscribe>
);
}
}
Then there is a tabNavigator.js that aggregates all the StackNavigators for each screen
and it looks like this
export default createBottomTabNavigator(
{
Overview: {
screen: HomeStack,
navigationOptions: {
title: "Home",
},
then on that screen, the API data is accessed like this
await InfoStore.fetchInfo(currentUser);
if someone could advise of what's the best way to do this and refer to some documentation or article as I could not find anything relevent.

react-native-background-timer, null is not an object

I've got an error using react-native-background-timer. I would be appreciate it if you could help me solve this problem.
I'm developing a mobile app on Expo Snack, and I now want to realize the auto-delete-account function: when an account is created and not being verified for 5 minutes, it will be deleted automatically.
So, I searched about background timer and I found the library below.
https://github.com/ocetnik/react-native-background-timer
However, I wasn't able to achieve it because of the error below
(3:2693) null is not an object (evaluating 'o.setTimeout')
and this is my code
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, Platform } from 'react-native';
import BackgroundTimer from 'react-native-background-timer';
let counter = 0;
let timer = null;
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
second: 0,
};
}
_interval: any;
onStart = () => {
if (Platform.OS == 'ios') {
BackgroundTimer.start();
}
this._interval = BackgroundTimer.setInterval(() => {
this.setState({
second: this.state.second + 1,
});
}, 1000);
};
onPause = () => {
BackgroundTimer.clearInterval(this._interval);
};
onReset = () => {
this.setState({
second: 0,
});
BackgroundTimer.clearInterval(this._interval);
};
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<TouchableOpacity onPress={this.onStart}>
<Text>start</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.onPause}>
<Text>pause</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.onReset}>
<Text>reset</Text>
</TouchableOpacity>
<Text>{this.state.second}</Text>
</View>
);
}
}
I followed a tutorial of this guy
https://medium.com/#loons.create/how-to-setup-react-native-background-timer-22263d655847
The equipped function, setInterval of javascript and etc. of course works fine as a timer, but actually they don't work behind in react native.
What am I missing, or is this an issue inside this library( I suppose so )? If so, please tell me an available version of this library; I use the latest version, 2.2.0, and React v35.0.0
Thank you
You cannot use "react-native-background-timer" with Expo on managed workflow. This library needs to compile some native code.
Instead, you should take a took to Expo BackgroundFetch which is doing almost the same thing.
https://docs.expo.io/versions/latest/sdk/background-fetch/
Using Expo components, you don't need to eject or compile additional native code.

React Native - How can i display data from an API?

I'm a beginner in React Native and I am developing a movie app using The Movie Database API.
I managed to extract the info from each movie. However, I find it difficult to extract information on the casting of films. I only managed to extract data of type "String" using the function "map". I would like to know how we can extract photos from actors and improve the display of data.
Here is an example of the API data:
_displayFilm() {
const { acteur } = this.state
if (acteur != undefined) {
return (
<ScrollView style={styles.scrollview_container}>
<Text style={styles.default_text}>Acteurs(s) :{"\n"}{acteur.cast.map(function(casts){
return ( casts.name + ' ' + casts.character)
}).join("\n")}
</Text>
</ScrollView>
)
}
}
render() {
return (
<View style={styles.main_container}>
{this._displayLoading()}
{this._displayFilm()}
</View>
)
}
}
Thank you for your help
You would probably want to display this data using a FlatList rather than in a ScrollView.
FlatLists are very easy to set up and manage. You can see more about them in the docs here.
Below is some sample code to get you started. You can update your component to incorporate the following. You would need to add two functions to the body of your component; one is a key extractor and the other is a render item (I've named them appropriately). Then in your component's render method add the code for the FlatList.
Make sure you also import FlatList from react-native
...
_keyExtractor (item, index) {
return index.toString();
}
_renderItem ({ actor, index }) {
return (<Text>{actor.name} - {actor.character}</Text>);
}
render() {
return(
...
<FlatList style={{ flex: 1 }}
data={this.state.acteur.cast}
keyExtractor={this._keyExtractor.bind(this)}
renderItem={this._renderItem.bind(this)}
/>
...
);
}
...
Adding Images
To add images to your list of characters you could update the _renderItem
method to return a better styled item, that could include images etc. For networked images look to the docs again, all the information is there.
Example:
_renderItem({actor, index}) {
return (
<View style={{flexDirection: 'row'}}>
<Image
source={`https://webaddress.com${actor.profile_path}`}
style={{width:40, height:40}}
/>
<Text>{actor.name} - {actor.character}</Text>
</View>
);
}
Remember to import Image from react-native and to use the correct base url for the web address so that you can get the image.
The above should get your started and you should only have to make some minor changes to the code so that it works for you.

How to properly update/re-render a component that is not a child React Native?

I'm using a react-navigation. More specifically, I have a materialTabNavigator nested inside of a drawerNavigator. Each tab is in itself a stackNavigator. I have a button in homeScreen, that navigates to makePost.js. There I take in information and store it to Async storage using a simple wrapper.
In Posts.js there's a FlatList displaying each post as a component. The data for the FlatList is initially set correctly after making a request from Async Storage. The problem is that this only happens when the app is first opened. I have tried many different approaches to solve this. The only way so far I've found is to continuously setState in ComponentDidUpdate() in Posts.js. Obviously this is problematic, because it re-renders constantly. I can set a flag to stop is from rendering, but then it will not re-render again.
Ultimately, what I'd like to happen is that when I hit the user is done entering their information and is ready to make a post, they hit the button in makePost.js, and the data in the FlatList of Posts.js is update.
I've tried to pass parameters using navigation, does not work, parameters get lost somewhere, probably because of the nested navigators.
I could really used some guidance on the proper way to accomplish this.
( Navigators; not sure why this is forcing to one line )
---drawer
--tabNav
-home
homeScreen.js
makePost.js
-posts
posts.js
-messages
--drawer1
--drawer2
//Posts.js
export default class Posts extends React.Component {
state = {
rows: [
{id: 0, text: "dog"},
],
}
componentDidMount() {
this.loadState();
}
loadState = () => {
var value = store.get('posts').then((res => {
if (res === null) {
res = [{id: 0, text: "default"}]
} else {
res = res
}
this.setState({rows: res})
}))
}
componentDidUpdate() {
this.loadState();
}
renderItem = ({item}) => {
return (
<BoardTab style={styles.row} />
)}
render() {
return (
<View style={styles.view}>
<FlatList
ListFooterComponent={this.renderFooter}
style={styles.container}
data={this.state.rows}
renderItem={this.renderItem}
keyExtractor={extractKey}
>
</FlatList>
<BoardScreenFooter />
</View>
);
}
And Posts.js button looks like this:
<TouchableOpacity
onPress={ () => {
this._onPressButton
this.storeFunc(this.state.newPost)
const retval = this.state.rows
this.props.navigation.navigate('Board',
{rowsID: retval});
}
}>
<Icon
reverse
name='md-camera'
type='ionicon'
color='green'
size={12}
/>
</TouchableOpacity>
storeFunc(newObj) {
newObj.id = newObj.id + 1
store.push('posts', newObj)
store.get('posts').then((res) => {
this.setState({rows: res})
})
}
Rapidly, i would say: use Redux. It alloq you to have global state in your app, which mean you can access the state anywhere (And also set them anywhere)
When opening the app, you get the data from the AsyncStore into the Redux store. You listen to the redux state (Which will be a props in your component) and display your list. When modifying your list in the other tab, you need to do 2 things:
Store the new data in the AsyncStorage
Update the state in the redux store. Since Posts.js will be listening at the redux store (as a props), it will re-render each time your data will change
A simple way to re-render a React-Navigation screen view on navigating to it:
All credit goes to Andrei Pfeiffer, Jul 2018, in his article: "Handle Tab changes in React Navigation v2" https://itnext.io/handle-tab-changes-in-react-navigation-v2-faeadc2f2ffe
I will reiterate it here in case the above link goes dead.
Simply add a NavigationEvents component to your render function with the desired listener prop:
render() {
return (
<View style={styles.view}>
<NavigationEvents
onWillFocus={payload => {
console.log("will focus", payload);
this.loadState();
}}
/>
<FlatList
ListFooterComponent={this.renderFooter}
style={styles.container}
data={this.state.rows}
renderItem={this.renderItem}
keyExtractor={extractKey}
>
</FlatList>
<PostScreenFooter />
</View>
);
}

Get height of tab bar on any device in React-Navigation

I'd like to position a component just above the createBottomTabNavigator TabBar in React-Navigation V2.
The height of the tab bar seems to differ on various devices (iOS devices especially). Is there a way to calculate the height of the tab bar as it is displayed on a device?
As you check the source code for react-navigation-tabs which react-navigation uses for createBottomTabNavigator, you can see that there is only 2 different bottom tab bar heights. Compact and default, which changes between some conditions. You can also set your component's position according to these conditions manually.
React Navigation 5 +
You now have two options to get the height of the bottomTabBar.
To get the height of the bottom tab bar, you can use BottomTabBarHeightContext with React's Context API or useBottomTabBarHeight, which is a custom Hook:
import { BottomTabBarHeightContext } from '#react-navigation/bottom-tabs';
// ...
<BottomTabBarHeightContext.Consumer>
{tabBarHeight => (
/* render something */
)}
</BottomTabBarHeightContext.Consumer>
or
import { useBottomTabBarHeight } from '#react-navigation/bottom-tabs';
// ...
const tabBarHeight = useBottomTabBarHeight();
Make sure you use version 5.11.9 or greater
To avoid Ipnone X issues they use react-native-safe-area-view inside.
You just need to know padding at bottom:
import { getInset } from 'react-native-safe-area-view'
const bottomOffset = getInset('bottom')
It solved problem for us.
We also use specific component position.
Updated according to library update:
import { SafeAreaConsumer } from 'react-native-safe-area-context'
<SafeAreaConsumer>
{insets => (
<TouchableOpacity
style={{
paddingBottom: 11 + insets.bottom,
}}
>
...
</TouchableOpacity>
)}
</SafeAreaConsumer>
or hook:
const insets = useSafeArea();
For your issue of how to position something above the tab bar, you can also achieve this without absolute positioning. This way you aren't relying on how the logic of determining the height of the bar is implemented (which may also change in the future).
import { createBottomTabNavigator, BottomTabBar } from "react-navigation"
createBottomTabNavigator({
// Your tabs
}, {
tabBarComponent: (props) => <BottomTabBar {...props} />
})
For example, if you wanted a little red bar above your tabs, you could do the following
tabBarComponent: (props) => (
<View>
<View style={{ backgroundColor: "red", height: 10 }} />
<BottomTabBar {...props} />
</View>
)
The other answer by benny points to where you need to go, but doesn't give you an easy way to check if . To complete the answer, I'll elaborate on the exact checks required to know which height to use. First we need to know if the tab bar is in adaptive mode or not. If you haven't passed "adaptive" as a parameter, adaptive is set to true for all iOS devices with iOS 11+. If it's not iOS11+, then adaptive is false. So, if you HAVE NOT passed "adaptive" as a parameter to tabBarOptions, the function is:
import {Platform, Dimensions} from 'react-native';
const isLandscape = () => {
const dim = Dimensions.get('screen');
return dim.width >= dim.height;
};
function tabBarHeight() {
const majorVersion = parseInt(Platform.Version, 10);
const isIos = Platform.OS === 'ios';
const isIOS11 = majorVersion >= 11 && isIos;
if(Platform.isPad) return 49;
if(isIOS11 && !isLandscape()) return 49;
return 29;
}

Resources