When use the react-navigation and add the headerRight button
...
class Screen extends React.Component {
static navigationOptions = {
headerRight: (
<TouchableOpacity>
<Icon .../>
</TouchableOpacity>
)
}
render() {
return (
<View />
)
}
}
Then I want to animate this button, but I encounter a problem when passing value
...
class Screen extends React.Component {
static navigationOptions = {
headerRight: (
<Animated.View style={animatedStyle}> <---- how to pass it
<TouchableOpacity>
<Icon .../>
</TouchableOpacity>
</Animated.View>
)
}
componentWillMount() {
this.animatedValue = new Animated.Value(0)
}
componentDidMount() {
Animated.timing(...}).start()
}
render() {
const interpolateRotation = ...
const animatedStyle = ... <---- the animatedStyle value
return (
<View />
)
}
}
I have tried the this.animatedStyle and animatedStyle for Animated.View, but it also can not effectiveness. It seems static navigationOptions is not inline within the class Screen
You can use this.props.navigation.setParams({ headerRightButtonStyle: this.animatedValue }) in constructor or componentWillMount to pass this value. Then read it like this:
static navigationOptions = ({ navigation }) => {
const params = navigation.state.params
return {
headerRight: (
<Animated.View style={params.headerRightButtonStyle}>
<TouchableOpacity>
<Icon .../>
</TouchableOpacity>
</Animated.View>
)
}
}
setParams will also trigger header rerender. You can use this method to pass header title/style/etc.
Related documentation: https://reactnavigation.org/docs/headers.html#using-params-in-the-title
Related
PLEASE NOTE that 'this' is not accessible from a static function: React Native : Access Component state inside a static function
I am trying to define a button in the screen's header that, when clicked, will affect rendering, and will be replaced with another icon.
It is an old app, still using react navigation 3.
I didn't know how to do the following things:
modify the component's state from a function that is activated when the button is pressed
modify the screen parameter from this function
What I managed to implement is the following lame and embarrassing way to do it:
When the button is clicked, a static function is executed, which modfies a static variable
periodic code is fired in componentDidMount that checks whether the static detailedDisplay variable has been modified. If it has been modified, this code sets a state variable that affects rendering. This periodic code also modifies the screen parameter which changes the icon in the header (because, as I wrote above, I also failed to set the parameter from the static function).
How can this be done in not-so-lame way?
Here is my code:
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/Octicons';
...
export default class Messages extends Component {
...
var detailedDisplay = false;
...
static navigationOptions = ({ navigation }) => {
return {
headerRight: //navigation.getParam('detailedDisplay', false) ?
detailedDisplay ?
<TouchableOpacity onPress={() => this.toggleDisplay(navigation)}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => this.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
static toggleDisplay(navigation) {
detailedDisplay = !detailedDisplay;
// the following statement gave the error "cannot read property
// 'setParams' of undefined", so I am setting it below.
// navigation.setParams({ detailedDisplay });
}
constructor(props) {
super(props);
this.state = {
detailedDisplay: false,
};
}
componentDidMount() {
setInterval(() => {
if (detailedDisplay !== this.state.detailedDisplay) {
this.props.navigation.setParams({ detailedDisplay });
this.setState({ detailedDisplay });
}
}, 500);
}
...
return (
<View>
{ this.state.detailedDisplay ?
{this.renderConcise()} :
{this.renderDetailed()}
}
</View>
);
}
renderDetailed() {
...
}
renderConcise() {
...
}
}
I think you can try to use your state inside your navigationOption method
static navigationOptions = ({ navigation }) => {
return {
headerRight: //navigation.getParam('detailedDisplay', false) ?
this.state.detailedDisplay ?
<TouchableOpacity onPress={() => this.toggleDisplay(navigation)}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => this.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
And then change your state in the toggleDisplay method
static toggleDisplay(navigation) {
this.setState(state=> detailedDisplay:!state.detailedDisplay)
// the following statement gave the error "cannot read property
// 'setParams' of undefined", so I am setting it below.
// navigation.setParams({ this.state.detailedDisplay });
}
You can try and keep the param and a state variable in sync.
An example POC is here
Here, I have tried to keep the state toggleVariable and the param value in sync but updating them through a common setter, and on Component Mount it will just be the same as the parameter.
You cannot read properties of this because this in JS is dynamic, and when you pass function as callback this is lost. There is 2 solutions that i know:
One of them bind this using bind method:
constructor(props) {
super(props);
this.state = {
detailedDisplay: false,
};
this.toggleDisplay = this.toggleDisplay.bind(this);
this.navigationOptions = this.navigationOptions.bind(this)
}
Now you can use non-static methods and you can access react state and methods in them:
navigationOptions({ navigation }) {
// somewhere in code <button onPress={this.toggleDisplay}><button>
// this.props this.state this.setState is available here
};
toggleDisplay(navigation) {
// this.props this.state this.setState is available here
// this.props.navigation.setParams() is available too
}
Finally, you can change the detailedDisplay state and screen params in toggleDisplay function. So when you'll press button state'll be changed and your commponent'll be re-rendered.
Additional
Second way to do it is to call method in a function:
onPress={() => this.toggleDiaplay()}
Here you must also use non-static methods, and this will work the same way as the first solution with bind.
Try doing something like this:
UPDATED: removed static from the toggleDisplay and added Class reference in the static function.
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/Octicons';
...
export default class Messages extends Component {
...
state = {
detailedDisplay: false,
};
...
static navigationOptions = ({ navigation }) => {
return {
headerRight: navigation.getParam('detailedDisplay', false) ?
<TouchableOpacity onPress={() => Messages.toggleDisplay()}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => Messages.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
toggleDisplay() {
this.setState({detailedDisplay: !this.state.detailedDisplay})
this.props.navigation.setParams({detailedDisplay: true});
}
constructor(props) {
super(props);
}
componentDidMount() {
// setInterval(() => {
// if (detailedDisplay !== this.state.detailedDisplay) {
// this.props.navigation.setParams({ detailedDisplay });
// this.setState({ detailedDisplay });
// }
// }, 500);
}
...
return (
<View>
{ this.state.detailedDisplay ?
{this.renderConcise()} :
{this.renderDetailed()}
}
</View>
);
}
renderDetailed() {
...
}
renderConcise() {
...
}
}
I've tried do render the image from URL but I have no success. If write the URI ok, but if I write item.show.image.original.replace ('http:', 'https:') to get the image no success.
The problem is thereĀ“s no error but not rendering.
The others values I've had success with.
import React, { Component } from "react";
import { Text, View, Image } from "react-native";
let termo = 'batman';
const API = 'http://api.tvmaze.com/search/shows?q='+termo;
export default class mapFunction extends Component {
constructor(props) {
super(props);
this.state = {
array: [], };
}
componentDidMount() {
fetch(API)
.then(response => response.json())
.then(data => this.setState({ array: data}));
}
list = () => {
return this.state.array.map(item => {
return (
<View style={{marginLeft: 10}}>
<Text>{item.score}</Text>
<Text>{item.show.name}</Text>
<Text>{item.show.type}</Text>
<Text>{item.show.language}</Text>
<Text>{item.show.summary} </Text>
<Image source={{uri:'https://static.tvmaze.com/uploads/images/original_untouched/6/16463.jpg'}}
style={{width:90, height:150}} />
</View>
);
});
};
render() {
return <View>{this.list()}
</View>;
}
}
Just change render function as below:
render() {
return (
<View>{this.list()}</View>
);
}
In my React Native application, I use React Navigation.
It's an app that enables the user to search an underlying database, i.e. for names. The GIF below illustrates the navigation.
From the landing screen, Go to search button is pressed (Main Stack Navigator) --> The Header appears, which is alright.
On the second screen, there is a bottomTabNavigator, where names is chosen (in names, there is a second StackNavigator nested).
This leads to the third screen. Here, three cards are shown. With the help of the second StackNavigator, clicking on Mehr opens a details screen.
What I want to achieve is that the Header of the first StackNavigator (that one at the top) disappears as soon as the user opens the details screen.
You see a button there because in the first step, I wanted to let the Header disappear on button click.
The below code works if it is implemented in a screen that is derived from the first StackNavigator directly. But because I am inside a nested navigator, it does not work anymore.
Here is the code:
App.tsx:
imports ...
class RootComponent extends React.Component {
render() {
const image = require('./assets/images/corrieBackground3.png');
console.log('calling the store', this.props.resultValue); // undefined
return (
<View style={styles.container}>
<LandingPage />
</View>
);
}
}
const RootStack = createStackNavigator(
{
LandingPage: {
screen: RootComponent,
navigationOptions: {
header: null,
},
},
SearchScreen: {
screen: SearchScreen,
navigationOptions: {
title: 'I SHOULD DISAPPEAR',
},
},
},
{
initialRouteName: 'LandingPage',
},
);
const AppContainer = createAppContainer(RootStack);
export default class App extends React.Component {
render() {
return <AppContainer />;
}
}
TwoTabs.tsx (for the 2nd screen):
imports ...
const SearchBarStack = createStackNavigator(
{
SearchBar: {
screen: SearchBar,
navigationOptions: {
header: null,
},
},
Details: {
screen: Details,
navigationOptions: {
title: 'I am here, above header disapear',
},
},
},
{
initialRouteName: 'SearchBar',
},
);
const TabNavigator = createBottomTabNavigator(
{
One: {
screen: SearchCriteria,
navigationOptions: {
tabBarLabel: 'criteria',
},
},
Two: {
screen: SearchBarStack,
navigationOptions: {
tabBarLabel: 'names',
},
},
},
);
const TabLayout = createAppContainer(TabNavigator);
type Props = {};
const TwoTabsHorizontal: React.FC<Props> = ({}) => {
return (
<View>
<TabLayout />
</View>
);
};
export default TwoTabs;
SearchBar.tsx (3rd screens skeleton):
import ...
type Props = {};
const SearchBar: React.FC<Props> = () => {
// logic to perform database query
return (
<View>
<ScrollView>
... logic
<SearchResult></SearchResult> // component that renders 3 cards
</ScrollView>
</View>
);
};
export default SearchBar;
Card.tsx (card rendered by SearchResult):
imports ...
type Props = {
title: string;
navigation: any;
};
const Card: React.FC<Props> = ({title, navigation}) => {
return (
<Content>
<Card>
<CardItem>
<Right>
<Button
transparent
onPress={() => navigation.navigate('Details')}>
<Text>Mehr</Text>
</Button>
</Right>
</CardItem>
</Card>
</Content>
);
};
export default withNavigation(Card);
And finally, the Details screen together with its Content. Here, the Header from the first StackNavigator should be hidden.
imports ...
type Props = {};
const Details: React.FC<Props> = ({}) => {
return (
<View>
<Content></Content>
</View>
);
};
export default Details;
imports ...
type Props = {
navigation: any;
};
class Content extends React.Component {
state = {
showHeader: false,
};
static navigationOptions = ({navigation}) => {
const {params} = navigation.state;
return params;
};
hideHeader = (hide: boolean) => {
this.props.navigation.setParams({
headerShown: !hide,
});
console.log('props ', this.props.navigation);
};
render() {
return (
<View>
<View>
</View>
<Button
title={'Press me and the header will disappear!'}
onPress={() => {
this.setState({showHeader: !this.state.showHeader}, () =>
this.hideHeader(this.state.showHeader),
);
}}
/>
</View>
);
}
}
export default withNavigation(CardExtended);
Maybe someone has an idea?
I got this code, and im supposed to add 3 different images to each row.
class SubRatingRowGroup extends Component {
onRatingPressed(elementName, rating) {
const cat = _.find(this.props.categories_tree.categories, {
name: elementName
});
cat.rating = rating;
this.props.setSubRatings(cat, rating);
}
handleClick = () => {
this.props.setCategories( this.props.categories_tree.categories);
this.props.openSubCategory(this.props.categories_tree, this.props.editable)
}
render() {
if (this.props.categories_tree && this.props.categories_tree.categories) {
let subRatingRows = [];
subRatingRows = this.props.categories_tree.categories.map((category, key) => (
<View style={{ flex: 1}} key={key}>
<View style={styles.ratingButtonRow}>
<View style={styles.buttonNameText} >
<Button title={category.name} onPress={this.handleClick} />
</View>
</View>
</View>
));
return (
<View >
{subRatingRows }
</View>);
}
return renderLoadingSpinner();
}
}
This will end with me rendering 3 rows with :
Enviromental
Social
Cultural
and i need 3 different backgrounds on these rows, i'm kinda new so i'm not sure what to do here. Would appreciate any help!
I have a list of images that I want to display and I can't do that dynamically. So, I did a massive switch case to know which image to display each time: no in the render of the page, I made renderImage: a function that contains a switch case and then in the render of the component itself I use renderImage but I get every time Null (the default value that I set). Here is the code I used:
class CountryDetails extends Component {
constructor(props) {
super(props);
fl = this.props.flag
};
static propTypes = {
popRoute: React.PropTypes.func,
navigation: React.PropTypes.shape({
key: React.PropTypes.string,
}),
}
popRoute() {
this.props.popRoute(this.props.navigation.key);
}
render() {
console.log('image ' , fl)
return (
<Container>
<Header style={{ backgroundColor: '#C0C0C0' }} hasTabs>
<Body>
<Text style={{ color: '#FFF', fontSize:17, fontWeight:'bold'}}> {this.props.name} </Text>
</Body>
<Right>
<View> {this.renderImage()} </View>
</Right>
</Header>
</Container>
);
}
renderImage() {
console.log('image ' , fl)
switch (fl)
{
case 'image1':
return (
<Image source={require('path/image1.png')}/>
);
case 'image2':
return (
<Image
source={require('path/image2.png')}/> );
case 'image3':
return (
<Image
source={require('path/image3.png')}/> );
default:
return (
<View >
<Text>{'Null'}</Text>
</View>
);
}}}
I keep getting Null, the default value that I set, every time in the screen.
After trying <View> {this.renderImage()} <View/> I get this error:
RawText "" must be wrapped in an explicit <Text> component.
PS I have other components to return in my render method like title and footer.
I don't think that is the correct way to use Image. Try the following code:
// .. rest of the code
render() {
return (
<View>
{this.renderImage()}
</View>
)
}
renderImage() {
switch (img) {
case 'image1':
return (<Image source={require('path/image1.png')}/> );
// .. rest of the case
default:
return (
<Text>{'Null'}</Text>
);
}
}