I am new to React Native so excuse the question if this is simple. I am trying to toggle icons in a ListView. How should I go about this? Your help is much appreciated.
Here's a piece of code i'm working with.
this.state = {
logo: 'star-o',
check: false
};
saveFavourite = (data) => {
this.state.check === false ? this.setState({logo:'star', check:true}) : this.setState({logo:'star-o', check:false})
}
<TouchableOpacity onPress={() => this.saveFavourite(data)}>
<Icon name={this.state.logo} size={30} />
</TouchableOpacity>
it din't change the icon.
First I can guess by your code that you are addressing this in the component holding the list view. This is wrong because you cant define a state to every "loveable" component, mainly because you cannot know how many are they beforehand. Instead, you should try to make this component dummy and receive if it is loved or not by props.
Then you should put a callback inside your component to execute code from your parent Component. Something like this:
class MyListComponent extends React.Component {
state = {
items: [
{ text: 'Some text', loved: false },
{ text: 'Some text2', loved: true },
],
}
toggleLoved() => {
// your logic here
}
render() {
return(
<FlatList
data={this.state.items}
renderItem={(item) => {
<MyLoveableComponent
loved={item.item.loved}
onLoved={ () => this.toggleLoved() }
}/>
);
}
}
const MyLoveableComponent = ({ loved, onLoved, logo }) => {
return(
<TouchableOpacity
onPress={() => {
setFavourite();
onLoved();
}>
<Icon name={logo} size={30} />
</TouchableOpacity>);
}
}
Check the component-container design pattern and remind that components should be as dummy as possible.
Related
Hi I want simple a button select and deselect when I have lots of buttons in one page like toggle select but no other button will effect.
My code like below:
constructor(props) {
super(props);
this.state = {
activeState: [false, false, false]
};
this.buttonPressed = this.buttonPressed.bind(this);
}
buttonPressed(index) {
// I want to update array value true and false.
}
<TouchableOpacity
style={this.state.activeClasses[0] ? styles.rateButton :
styles.rateButtonActive}
onPress={() => this.addActiveClass(0)}>
</TouchableOpacity>
<TouchableOpacity
style={this.state.activeClasses[1] ? styles.rateButton :
styles.rateButtonActive}
onPress={() => this.addActiveClass(1)}>
</TouchableOpacity>
You can suggest me a different process or method by which I can make this.
I don't know if this is exactly what you want, but i'll give it a try:
buttonPressed(index) {
const tmpState = this.state.activeState.map((val, tmpIndex) => {
if (tmpIndex === index) {
return !val;
}
return val;
});
this.setState({ activeState: tmpState });
}
I have my side menu via DrawerNavigator. I know that to customize the drawer, it's in "contentComponents" props.
I want for example, put a button who open a modal like : Share (to share the app on other social media)
But for now, all my button are route. So if I click on it, it's redirect to the page (normal). I just want to add a button who react and not redirect.
I don't know how to custom that in the Component dynamically. I think about hardcoded each button (some for redirect, some for display simple modal).
Here is my code :
index.android.js
const DrawerContent = (props) => (
<ScrollView>
<View style={styles.container}>
<Text style={styles.logo}>TechDico</Text>
<Text style={{ paddingLeft: 10, paddingRight: 10, fontSize: 13, textAlign: 'center', color: '#f4f4f4' }}>Des millions de traductions classées par domaine d'activité</Text>
</View>
<DrawerItems style={{ marginTop: 30 }} {...props} />
</ScrollView>
)
const appNavigator = DrawerNavigator({
Redirection1: {
screen: Index,
navigationOptions: {
drawerLabel: 'Redirection1',
drawerIcon: ({ tintColor }) => (<Icon name="home" size={20} color={tintColor} />),
}
},
DisplayModal: {
screen: Index,
navigationOptions: {
drawerLabel: 'DisplayModal',
drawerIcon: ({ tintColor }) => (<Icon name="home" size={20} color={tintColor} />),
}
},
Redirection2: {
screen: Index,
navigationOptions: {
drawerLabel: 'Redirection2',
drawerIcon: ({ tintColor }) => (<Icon name="home" size={20} color={tintColor} />),
}
}, }, {
// define customComponent here
contentComponent: DrawerContent,
contentOptions: {
inactiveTintColor: '#000000',
activeTintColor: '#1eacff',
showIcon: true,
}
});
Index class
export default class Index extends Component {
renderRoot = () => {
const { navigation } = this.props;
console.log("My Navigation ", navigation);
switch (navigation.state.key) {
case 'Redirection1':
return (
<App navigation={navigation} />
);
case 'DisplayModal':
// TODO I don't want to return so I can remove to cancel the redirection, but now, how can I display a modal without redirect.
return (
<DisplayModal navigation={navigation} />
);
case 'Redirection2':
return (
<Redirection2 navigation={navigation} />
);
default:
return (
<Test navigation={navigation} />
);
}
}
I'm using 'react-navigation'.
I'm looking at the same task as well. I think having multiple routes pointing to the same screen type may cause eventually a mess with state management, as each screen instance is different.
Looking at the source code in DrawerSidebar/DrawerNavigatorItems it seems all items in the sidebar list are those found in drawer's route config (unless we rewrite completely DrawerNavigatorItems). So maybe we may have a fake screen for some route and in componentWillMount implement required action and then navigate to the default route.
Here is a sample code:
let drawer = DrawerNavigator({
Main: {
screen: MainScreen,
},
About: {
screen: AboutScreen,
},
ContactUs: {
screen: ContactUsFakeScreen,
},
});
const mailUrl = "mailto:test#test.com";
class ContactUsFakeScreen extends React.Component {
componentWillMount() {
let self = this;
Linking.canOpenURL(mailUrl)
.then(self.openEmail)
.catch(err => self.openEmail(false));
}
openEmail(supported) {
if (supported) {
Linking.openURL(mailUrl).catch(err => {});
}
let { navigation } = this.props;
navigation.navigate('Main');
}
render() {
return null;
}
}
Here Main/MainScreen and About/AboutScreen are regular routes and screens, while ContactUs/ContactUsFakeScreen only pretend to be a route and a screen. Clicking on ContactUs will trigger componentWillMount which deals with email screen and then eventually navigates to the MainScreen (Main route).
Another approach could be to hijack getStateForAction from drawer router and put some extra routing logic there replacing destination route on the fly. Something along these lines:
const defaultDrawerGetStateForAction = drawer.router.getStateForAction;
drawer.router.getStateForAction = (action, state) => {
let newState = defaultDrawerGetStateForAction(action, state);
if (action.type === 'Navigation/NAVIGATE' && action.routeName === 'ContactUs') {
// extra logic here ...
newState.routes.forEach(r => {
if (r.key === 'DrawerClose') {
// switching route from ContactUs to Main.
r.index = 0;
}
});
}
return newState;
}
And if an item in the drawer list is not even actionable (like copyright), then fake screen will look even simpler (note styling via navigationOptions):
let drawer = DrawerNavigator({
...
Copyright: {
screen: Copyright,
},
});
class Copyright extends React.Component {
static navigationOptions = {
drawerLabel: ({ tintColor, focused }) =>
(<Text style={{color: '#999'}}>Copyright 2017</Text>)
)
};
componentWillMount() {
let { navigation } = this.props;
navigation.navigate('Main');
}
render() {
return null;
}
}
I am trying to use react-native-sidemenu https://github.com/react-native-community/react-native-side-menu
My code looks like this.
There is no error and even output is overlapping to each other
var list = [{name: "komaldeep", subtitle: "dssdfds", avatar_url:"sadasdsa" }];
export default class First extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
};
this.toggleSideMenu = this.toggleSideMenu.bind(this);
}
toggleSideMenu () {
this.setState({
isOpen: !this.state.isOpen
})
}
render() {
//menu list `enter code here`
const MenuComponent = (
<View style={{flex: 1, backgroundColor: '#ededed', paddingTop: 200}}>
<List containerStyle={{marginBottom: 20}}>
{
list.map((l, i) => (
<ListItem
roundAvatar
onPress={() => console.log('Pressed')}
avatar={l.avatar_url}
key={i}
title={l.name}
subtitle={l.subtitle}
/>
))
}
</List>
</View>
)
return (
<SideMenu
isOpen={this.state.isOpen}
menu={MenuComponent} >
//Menu Component just contain some random text
<Menu toggleSideMenu={this.toggleSideMenu.bind(this)}/>
</SideMenu>
);
}
}
Can you just guide me.. what i am doing wrong..
OutPut looks like this
enter image description here
The reason that the items in your menu shows up on the right of the screen, seemingly outside of the menu, is that your MenuComponent takes up the entire screen. Set the prop openMenuOffset={number} to SideMenu and use the same number to set width: number in the style of your MenuComponent.
I am tring to make right button on navigation bar using react-native-router-flux.
My code is
import React from 'React';
import { TouchableHighlight } from 'react-native';
import { Scene, Router, Actions } from 'react-native-router-flux';
...
const filterIcon = () => (
<TouchableHighlight onPress={()=>} style={{...}}>
<Icon name="filter" size={30}/>
</TouchableHighlight>
);
const MainRounter=()=>(
<Router>
<Scene
key="main"
component={mainPage}
initial={true}
renderRightButton={()=>filterIcon}
/>
</Router>
);
But I can't show right button on navigation bar.
How can I make it?
You can use the onRight, rightTitle or rightButtonImage to add the right button to the navBar.
const MainRounter = return (
<Router>
<Scene
key="main"
component={mainPage}
initial={true}
onRight={ ()=> whatever you want to do }
rightButtonImage={require('path/to/your/icon')}
/>
</Router>
);
Let me know if it worked :)
UPDATE - 18 Oct 2017
With the last releases the methods described below have stopped working, again. So, my last solution for this is:
class MyAwesomeClass extends React.Component {
componentWillMount() {
Actions.refresh({ right: this._renderRightButton });
}
_renderRightButton = () => {
return(
<TouchableOpacity onPress={() => this._handleIconTouch() } >
<Icon name="check" size={26} color='grey' />
</TouchableOpacity>
);
};
_handleIconTouch = () => {
console.log('Touched!');
}
}
UPDATE - 07 Aug 2017
As RNRF4 uses now ReactNavigation as navigation solution, the above solution stopped working. To make it work again I using the ReactNavigation API directly. This is my solution:
class MyAwesomeClass extends React.Component {
static navigationOptions = ({ navigation }) => {
headerRight: <TouchableIcon size={22} name="search" onPress={() =>
navigation.state.params.handleIconTouch()} />,
}
componentWillMount() {
this.props.navigation.setParams({ handleIconTouch:
this.handleIconTouch });
}
handleIconTouch = () => {
console.log('Touched!');
}
}
In navigationOptions the general navigation info is accesed in a class-level. As the context is static its impossible to access our Component methods and their params. To make it work a little workaround is needed.
So, first you need to define the function which will handle your touch, in this case handleIconTouch. In here you should define the action your button will perform when it's pressed. As this function is not static you can access props and state from here which gives you more functionality and options.
Then, in your componentWillMount() method, add the method to the navigation params with setParams() to be available in the static context of navigationOptions. As this method is not static either, you can pass any required params to your button here.
And finally, using the params, define your button inside navigationOptions/headerRight key using the component that suits you the most.
ORIGINAL
I recommend you this approach:
Create a method with your desired logic and images, for example:
renderRightButton = () => {
return(
<TouchableOpacity onPress={() => null } >
<Icon name="check" size={26} color='grey' />
</TouchableOpacity>
);
};
In your componentWillMount() method add at the beginning:
Actions.refresh({ renderRightButton: this.renderRightButton });
Ready to go!
Tell me if you have any doubts.
For RNRF4. Thank you Jacse.
import React from 'react';
import { View, Button, Alert } from 'react-native';
class MyScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
headerRight: <Button title="Save" onPress={() => params.handleSave()} />
};
};
_saveDetails() {
Alert.alert('clicked save');
}
componentDidMount() {
this.props.navigation.setParams({ handleSave: this._saveDetails });
}
render() {
return (
<View />
);
}
}
export default MyScreen;
RNRF4 I tried this code working fine
<Scene
key="SignUp"
component={SignUp}
title="SignUp"
onRight={()=>{}}
rightTitle={' Save'} />
//SignUp Component
export default class SignUp extends Component {
componentWillMount() {
this.props.navigation.setParams({
'onRight': this.handleIconTouch
})
}
handleIconTouch() {
debugger;
}
render() {
return ();
}
}
Update for 2021:
This will remove the deprecation warning.
HomePage.navigationOptions = () => {
return {
headerRight: () => (
<TouchableOpacity onPress={() => navigation.navigate('Create')}>
<Feather name="plus" size={30} />
</TouchableOpacity>
) };
};
Here Feather is a library to show icons in the application, it is available in Expo.
You can use this library by importing it like below.
import { Feather } from '#expo/vector-icons'
you should create a custom navBar take a look at this issue in github custom navBar
Working with a ListView in React-Native, I have seen that is not the same, moving props to the list item,
Pass functions as props only with the reference, and invoke the parameters in the child component, or
Pass functions as props with parameters defined, and invoke the function with no parameters in the child
None of the solutions works.
The function invoked are Actions creators of Redux, and dispatched. Is this a issue of Redux or React-Native (maybe ReactJS)
This is a snippet, market as //ERROR the code lines that does'nt work followed by the good ones
class App extends Component {
// On props
// data: an Array
// doThis: an action creator of Redux
// doThat: idem
constructor(){
super();
this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
}
render () {
const dataSource = this.ds.cloneWithRows(this.props.data);
return (
<View>
<ListView style={{flex:1}}
dataSource={dataSource}
renderRow={(rowData, sectionID, rowID) =>
<Item rowData={rowData}
//ERROR
//onPress={this.props.doThis}
//onLongPress={this..props.doThat}
//RIGHT NO ERROR TOO
onPress={() => this.props.doThis(rowData)}
onLongPress={() => this.props.doThat(rowData)}
/>
}
/>
</View>
)
}
}
class Item extends Component {
render() {
return (
<View>
<TouchableHighlight
//ERROR
//onPress={() => { this.props.onPress( this.props.rowData ) }}
//onLongPress={() => { this.props.onLongPress( this.props.rowData ) }}
//WRONG TOO
onPress={this.props.onPress}
onLongPress={this.props.onLongPress}
>
<Text>
{rowData}
</Text>
</TouchableHighlight>
</View>
);
}
}
There is a repo with this problem here https://github.com/srlopez/test
Thanks in advance
If your high-level callbacks accept a parameter, you need to make sure your anonymous functions accept a parameter as well (Note: creating anonymous functions using the arrow syntax automatically binds our function to the value of this in the current context). I think you witnessed a combination of issues where either your callbacks were bound to the incorrect context (the window) or you weren't accepting the passed arguments:
class App extends Component {
// On props
// data: an Array
// doThis: an action creator of Redux
// doThat: idem
constructor(){
super();
this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
}
render () {
const dataSource = this.ds.cloneWithRows(this.props.data);
return (
<View>
<ListView style={{flex:1}}
dataSource={dataSource}
renderRow={(rowData, sectionID, rowID) =>
<Item rowData={rowData}
onPress={(data) => this.props.doThis(data)}
onLongPress={(data) => this.props.doThat(data)} />
}/>
</View>
)
}
}
class Item extends Component {
render() {
return (
<View>
<TouchableHighlight
onPress={() => this.props.onPress(this.rowData)}
onLongPress={() => this.props.onLongPress(this.rowData)}>
<Text>
{rowData}
</Text>
</TouchableHighlight>
</View>
);
}
}
It's probably be a problem with your this not being set right in your closure.
Try binding it this way:
class Item extends Component {
render() {
return (
<View>
<TouchableHighlight
onPress={this.props.onPress.bind(this, this.props.rowData)}
onLongPress={this.props.onLongPress.bind(this, this.props.rowData)}
>
<Text>
{rowData}
</Text>
</TouchableHighlight>
</View>
);
}
}