I'd like to get user image into the tabBarIcon just like Instagram does it. I can't figure out the way to do it.
I tried getting the state but on the init of the app the state is empty.
I've tried like
const store = store.getState().user
but it's undefined on app init
I have MainTabNavigator.js
ProfileStack.navigationOptions = {
tabBarLabel: () => {
return null
},
tabBarIcon: ({focused}) => (
<Image source={{uri: ???}}/>
)
}
const TabNavigator = createBottomTabNavigator({
HomeStack,
SearchStack,
DashboardStack,
ProfileStack,
});
TabNavigator.path = '';
export default TabNavigator;
I can't get the props or state since this isn't a class
The solution was quite simple.... I don't know why I didn't think of it earlier.
The trick was to create a component that would get the state from redux and when the state changes after the init, the state would finally have the Object and there the image path
ProfileStack.navigationOptions = {
tabBarLabel: () => {
return null
},
tabBarIcon: ({focused}) => (
<BottomTabImage focused={focused} />
)
}
See i changed it to BottomTabImage which is just a component
And this is the component file
BottomTabImage.js
import {connect} from 'react-redux';
class BottomTabImage extends Component {
render() {
const uri = this.props.auth.image !== undefined ?`http://localhost/storage/creator_images/${this.props.auth.image)}`: '';
return <Image style={styles.profileImage} source={{uri}} />
}
}
const styles = StyleSheet.create({
profileImage: {
...
}
});
function mapStateToProps(state) {
return {
auth: state.user.auth
}
}
export default connect(mapStateToProps, {})(BottomTabImage)
Related
I have two screens:
Screen A
import React, { useState } from "react";
import { Text, View, Button } from "react-native";
const ViewA = ({ navigation }) => {
const [val, setVal] = useState(null);
const [val2, setVal2] = useState(null);
const callBack = (value1,value2) => {
setVal(value1);
setVal2(value2);
};
const onNextPress = () => {
navigation.navigate("Second Screen", { callBack: callBack });
};
return (
<View>
<Text>{val}{val2}</Text>
<Button title="Next" onPress={onNextPress} />
</View>
);
};
export default ViewA;
Screen B
import React from "react";
import { View, Button } from "react-native";
const ViewB = ({ route, navigation }) => {
const onBackPress = () => {
const { callBack } = route.params;
callBack(5,6); // Your new value to set
navigation.goBack();
};
return (
<View>
<Button title="back" onPress={onBackPress} />
</View>
);
};
export default ViewB;
when I enter screen B a warning appears: non-serializable values were found in the navigation state. how can I solve it?
According to the docs for I get the warning "Non-serializable values were found in the navigation state"
This can happen if you are passing non-serializable values such as class instances, functions etc. in params. React Navigation warns you in this case because this can break other functionality such state persistence, deep linking etc.
...
If you don't use state persistence or deep link to the screen which accepts functions in params, then the warning doesn't affect you and you can safely ignore it. To ignore the warning, you can use LogBox.ignoreWarnings.
import { LogBox } from 'react-native';
LogBox.ignoreLogs([
'Non-serializable values were found in the navigation state',
]);
An alternative would be to move the state into route params instead of local state of ViewA (which means you can set it when navigating):
import React, {useState, useEffect} from 'react';
import { Text, View, Button } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const ViewA = ({ route, navigation }) => {
const onNextPress = () => {
navigation.navigate("ViewB", {
previousScreen: route.name
});
};
return (
<View>
<Text>ViewA</Text>
<Text>Params: {JSON.stringify(route.params)}</Text>
<Button title="Next" onPress={onNextPress} />
</View>
);
};
const ViewB = ({ route, navigation }) => {
const onBackPress = () => {
navigation.navigate(route.params.previousScreen, {
val: 5,
val2: 6,
})
};
return (
<View>
<Text>ViewB</Text>
<Text>Params: {JSON.stringify(route.params)}</Text>
<Button title="back" onPress={onBackPress} />
</View>
);
};
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator mode="modal">
<Stack.Screen name="ViewA" component={ViewA} />
<Stack.Screen name="ViewB" component={ViewB} />
</Stack.Navigator>
</NavigationContainer>
);
}
Snack
anyway, if you still need to run that callback from another screen, you can make a custom class to subscribe to events and store the callback like so:
class SetNewPropsListener {
constructor(){
this.listeners = []
this.subscribe = ({ id, cb }) => {
this.listeners = [...this.listeners.filter((x)=> x.id !== id), { id, cb }]
}
this.unsubscribe = ({ id }) => {
this.listeners = this.listeners.filter((x)=> x.id !== id)
}
this.propogate = (id, newProps) => {
this.listeners.forEach((x)=> x.id === id && x.cb(newProps))
}
}
}
export const SetNewProps = new SetNewPropsListener()
and then, in the first screen you're navigating from, you can register callback function with unique id like so:
import { SetNewProps } from '../utils/EventListeners'
const callbackToInvokeFromOtherScreen = (newParamsFromNextScreen)=>{
// do whatever with new values
}
componentDidMount(){
SetNewProps.subscribe({ id: 'your_unique_id', cb: callbackToInvokeFromOtherScreen })
}
and then in the next screen you navigate to, you can access the stored callback from the SetNewProps class instance and execute it with your custom params like so:
import { SetNewProps } from '../utils/EventListeners'
const propsToPassToCallback = {}
SetNewProps.propogate('your_unique_id', propsToPassToCallback)
with right logic, this subscriber class method can solve many problems, like invoking peer components methods (like when you have swipe to delete interaction, you don't want any other ListView item to be left open when you swiped another one)
I'm actually working on a small react app, I actually want to connect my component to firebase, but this component contains multiple classes and multiple exports, so when i apply my method (which is based on one class component) it rendering me nothing, it supposed to returns data from firestore.
when i try to console log the state on mapStateToProps it returns undefined :
const mapStateToProps = (state) => {
console.log("state firebase",state);
return {
animationsfb: state.firestore.ordered.animations,
}
}
that's my component that contains multiple classes:
export class AnimationScreen extends Component {
render() {
return (
<View>
.........
</View>
);
}
}
const mapStateToProps = (state) => {
console.log("state firebase",state);
return {
animationsfb: state.firestore.ordered.animations,
}
}
class DetailsScreen extends React.Component {
render() {
return (
<View>
.........
</View>
);
}
}
const Navigator = FluidNavigator({
home: {screen: AnimationScreen},
homeDetails: {screen: DetailsScreen},
},
);
class HomeTransitions extends React.Component {
static router = Navigator.router;
render() {
const {navigation} = this.props;
return (
<Navigator navigation={navigation}/>
);
}
}
// it was like this before i change it: **export default HomeTransitions**
export default compose(
connect(mapStateToProps), firestoreConnect([{ collection: 'animations'}])
) (HomeTransitions);
I expect to return me data on state when i console log it, but it returns undefined.
Currently you are trying to connect everything to the store, including the navigator, which is probably not what you want to do.
If you are just using animationsfb in AnimationScreen, just connect this component to the store and use the output as a screen in your navigator:
class AnimationScreen extends Component {
render() {
return (
<View>
// [...]
</View>
);
}
}
const mapStateToProps = (state) => {
console.log("state firebase", state);
return {
animationsfb: state.firestore.ordered.animations,
}
}
const AnimationScreenConnected = connect(mapStateToProps)(AnimationScreen);
Then in your navigator:
const Navigator = FluidNavigator({
home: { screen: AnimationScreenConnected },
homeDetails: { screen: DetailsScreen },
});
i'm new in react native. i want to add if condition in initialRouteName like code below. when "notification" variable is null move to "MemberProfile" page. but if "notification" variable is not null move to "ReviewMember" page. i try it code but still move to "MemberProfile" page. any solution?.
this is my code
var notification = null;
class DrawerMember extends Component {
constructor(props) {
super(props);
this.state = {
notifData: null
};
this.callCheck();
}
async callCheck() {
await AsyncStorage.getItem("#tryCode:notification", (err, result) => {
if (result != null) {
this.setState({
notifData: "testing data"
});
}
});
}
render() {
notification = this.state.notifData;
return <Root />;
}
}
const Root = createDrawerNavigator(
{
MemberProfile: {
screen: MemberProfileScreen
},
ReviewMember: {
screen: ReviewScreen
}
},
{
drawerPosition: "right",
initialRouteName: notification == null ? "MemberProfile" : "ReviewMember",
contentComponent: props => <SideBar {...props} />,
}
);
export default DrawerMember;
I think Root is created before the async function returns so notification is always null.
A possible way to solve this problem would be to use a SwitchNavigator as the first screen in your drawer. This navigator would be responsible for loading the notification and redirecting to the right screen.
Something along the lines of:
import React from 'react';
import { View, AsyncStorage, ActivityIndicator, StatusBar } from 'react-native';
export default class DummySwitch extends React.Component {
async componentDidMount() {
this.listener = this.props.navigation.addListener('willFocus', async () => {
const notification = await AsyncStorage.getItem('#tryCode:notification');
if (notification === null) {
this.props.navigation.navigate('MemberProfile');
}
else {
this.props.navigation.navigate('ReviewMember');
}
});
}
render() {
return (
<View>
<ActivityIndicator />
<StatusBar barStyle='default' />
</View>
);
}
}
As you can see, the switch screen just displays a loading button while accessing the async storage and deciding which route to take.
Then you define the drawer as usual but you add the switch screen as the initial route. You can also hide the label if you want by defining your own drawerLabel:
export default createDrawerNavigator({
Switch: {
screen: Switch,
navigationOptions: () => ({
drawerLabel: () => null,
}),
},
MemberProfile: {
screen: MemberProfileScreen,
},
ReviewMember: {
screen: ReviewScreen,
},
}, { initialRouteName: 'Switch' });
This is it, the drawer now selects the route based on your async storage.
I'm working with React Native and React Navigation.
I have a component called App.js in which I declare the Drawer Navigation of React-Navigation.
In this I have an option to log out but I can not navigate to another component after removing the AsyncStorage
Does anyone know how to achieve it?
Thank you.
This is my code:
App.js
import { createDrawerNavigator, DrawerItems, NavigationActions } from 'react-navigation';
const customDrawerComponent = (props) => (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView>
<DrawerItems
{...props}
/>
<TouchableOpacity style={styles.button} onPress={this.logOut} >
<Text> Logout </Text>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
logOut = () => {
// NOT WORKS
// this.props.navigation.navigate('Login')
//NOT WORKS:
this.myAction();
}
myAction = () => {
const nav = NavigationActions.navigate({
routeName: 'App',
});
return nav;
};
const AppDrawNavigator = createDrawerNavigator(
{
MainComponent: { screen: MainComponent,
navigationOptions: ({navigation}) => ({
drawerLockMode: 'locked-closed'
}) },
Login: { screen: LoginComponent,
navigationOptions: ({navigation}) => ({
drawerLockMode: 'locked-closed'
}) },
User: { screen: UsersComponent }
},
{
contentComponent: customDrawerComponent,
}
);
make this as a class like
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
From your question I understand that either you want to :-
navigate from outside the components
navigate from components which do not have navigation prop.
For this I have tried 2 solutions and both work extremely fine though I based towards the second one.
First Solution
Use withNavigation from react-navigation package. If your components are deeply nested they wont have navigation prop unless u specify them manually or put them in context ;passing navigation prop becomes a real pain. So instead use withNavigation and your component would have navigation prop.
import {withNavigation} from "react-navigation";
const Component = ({navigation}) => {
const onPress = () => {
navigation.navigate(//ROUTE_NAME//)
}
return (
<TouchableOpacity onPress={onPress}>
<Text>Navigate</Text>
</TouchableOpacity>
)
}
export default withNavigation(Component);
Second Solution
Create a helper script and use that.
"use strict";
import React from "react";
import {NavigationActions} from "react-navigation";
let _container; // eslint-disable-line
export const navigation = {
mapProps: (SomeComponent) => {
return class extends React.Component {
static navigationOptions = SomeComponent.navigationOptions; // better use hoist-non-react-statics
render () {
const {navigation: {state: {params}}} = this.props;
return <SomeComponent {...params} {...this.props} />;
}
}
},
setContainer: (container) => {
_container = container;
},
reset: (routeName, params) => {
_container.dispatch(
NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName,
params
})
]
})
);
},
goBack: () => {
_container.dispatch(NavigationActions.back());
},
navigate: (routeName, params) => {
_container.dispatch(
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName,
params
})
);
},
navigateDeep: (actions) => {
_container.dispatch(
actions.reduceRight(
(prevAction, action) =>
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName: action.routeName,
params: action.params,
action: prevAction
}),
undefined
)
);
},
getCurrentRoute: () => {
if (!_container || !_container.state.nav) {
return null;
}
return _container.state.nav.routes[_container.state.nav.index] || null;
}
};
In your parent component when you mount the navigation call following:-
"use strict";
import React from "react";
import App from "./routes";
import {navigation} from "utils";
class Setup extends React.Component {
render () {
return (
<App
ref={navigatorRef => {
navigation.setContainer(navigatorRef);
}}
/>
);
}
}
export default App;
Now, in your components you can directly use helpers from this script itself and navigation would be accessibly globally now.
import {navigate} from "utils/navigation";
For more details you can this thread
Your logout function is declared outside of the Navigator. This means you don't have access to the navigation prop there. However, your customDrawerComponent is a screen of your Navigator and it should have access to it.
So you can try something like this (props here are the props passed to the customDrawerComponent):
onPress={()=> {props.navigation.navigate("Login")}}
Plus your App.js seems kind of strange since you're not exporting any component. Have you pasted the whole code of App.js or just parts of it?
I am using react-native with redux. I am trying to update current screen's params so that they can be accessed in a component used in top-bar but parameter is not getting set.
My code is following:
Screen Route:
AlertNameForm: {
screen: AlertNameForm,
navigationOptions: ({navigation}) => CancelAndDone(navigation)
}
Component Screen: In componentDidMount I am setting parameter.
class AlertNameForm {
..........
componentDidMount() {
this.props.navigation.setParams({onDonePress: this.onDonePress})
}
onDonePress: () => {
// want to access this function in top-bar buttons.
}
}
Following is further components:
export const CancelAndDone = (navigation) => ({
headerLeft: <ButtonCancel navigation={navigation} />,
headerRight: <ButtonDone navigation={navigation} />
})
const ButtonDone = withTheme(({navigation, theme: { tertiaryColor } }) => (
<Button color={tertiaryColor} title="Done" onPress={() => {
if (navigation.state.params && navigation.state.params.onDonePress) {
navigation.state.params.onDonePress()
}
else {
navigation.dispatch(NavigationActions.back())
}
}} />
))
But in ButtonDone component I am not able to access function onDonePress
Is there any other way to setParams for current screen in react-native.
You should reference navigation.state.paramsusing this.props since navigation should be passed as a prop to that component.
You can assign the function within the target component as follows:
componentDidMount = () => {
const { navigation } = this.props
navigation.setParams({
onDonePress: () => this.myFunction(),
})
}
myFunction = () => { /*body function*/ }
In your header or footer component call:
navigation.state.params.onDonePress or route.params.onDonePress if you using React Navigation v5.