I'm in the process of creating a social media app and I've the following diagram of screen transition:
Main -> Profile -> Followers -> John's Profile -> John's Followers ->
Emily's Profile -> ....
How can I implement a flow like this? Currently my router implementation is buggy, I can not go nested, it returns the previous screen.
Here is the part of the router to express my problem:
const appStack = createStackNavigator(
{
[PROFILE_STACK]: { screen: profileStack },
[PROFILE_FOLLOWERS_STACK]: { screen: profileFollowersStack },
[PROFILE_FOLLOWINGS_STACK]: { screen: profileFollowingsStack }
},
{
initialRouteName: PROFILE_STACK,
headerMode: "none"
}
);
const profileStack = createStackNavigator(
{
[PROFILE]: {
screen: UserProfileScreen,
navigationOptions: () => ({
header: null
})
}
},
{
initialRouteName: PROFILE
}
);
const profileFollowersStack = createStackNavigator(
{
[PROFILE_FOLLOWERS]: {
screen: UserFollowersScreen,
navigationOptions: () => ({
header: null
})
}
},
{
initialRouteName: PROFILE_FOLLOWERS
}
);
const profileFollowingsStack = createStackNavigator(
{
[PROFILE_FOLLOWINGS]: {
screen: UserFollowingsScreen,
navigationOptions: () => ({
header: null
})
}
},
{
initialRouteName: PROFILE_FOLLOWINGS
}
);
export const goUserProfile = function(navigation, userId) {
const { navigate } = navigation;
navigate(PROFILE_STACK, {
userId: userId
});
};
export const goUserFollowers = function(navigation, userId) {
const { push } = navigation;
push(PROFILE_FOLLOWERS_STACK, {
userId: userId
});
};
export const goUserFollowings = function(navigation, userId) {
const { push } = navigation;
push(PROFILE_FOLLOWINGS_STACK, {
userId: userId
});
};
The problem was I was using navigate() method in my goUserProfile(), not push(). After using push(), my problem is solved.
Reference:
React Navigation V2: Difference between navigation.push and navigation.navigate
Related
Im using React navigation version 5 with Firebase integration. Using that I'm trying to make authentication flow. I almost done a Signing flow and it works Perfect, but after Firebase Signed in it will not render and it is in still same SignIn Page in mobile. PFB the Code of My AppRouter Page.
import React, { Component, useEffect } from 'react'
import { View, Platform, TouchableOpacity, Text, Image, Dimensions, Slider, Button, ActivityIndicator, Alert } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
import RootStackScreen from '../Router/RootStackScreen'
import HomeStackScreen from './TabScreens'
import AsyncStorage from '#react-native-community/async-storage';
import { Authcontext } from '../Components/context'
import auth from '#react-native-firebase/auth';
import Home from '../Pages/OnboardingScreen/Home';
var { height, width } = Dimensions.get('window')
const Drawer = createDrawerNavigator();
const AppRouter = ({navigation}) => {
const initialoginState = {
isLoading: true,
email: null,
userToken: null
};
const loginReducer = (prevState, action) => {
switch (action.type) {
case 'RETRIVE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'LOGIN':
return {
...prevState,
email: action.id,
userToken: action.token,
isLoading: false,
};
case 'LOGOUT':
return {
...prevState,
email: null,
userToken: null,
isLoading: false,
};
case 'REGISTER':
return {
...prevState,
email: action.id,
userToken: action.token,
isLoading: false,
};
}
}
const [loginState, dispatch] = React.useReducer(loginReducer, initialoginState)
const authContext = React.useMemo(() => ({
signIn: async (email, password) => {
let userToken;
userToken = null;
if (email !== '' && password !== '') {
auth().signInWithEmailAndPassword(email, password)
.then(async (success) => {
try {
await AsyncStorage.setItem('userToken', success.user.uid)
} catch (e) {
console.log(e)
Alert.alert('Shopping', e)
}
})
.catch(error => {
if (error.code === 'auth/email-already-in-use') {
Alert.alert('Shopping', 'That email address is already in use!')
}
if (error.code === 'auth/invalid-email') {
Alert.alert('Shopping', 'That email address is invalid!')
}
Alert.alert('Shopping', error.code)
});
} else {
Alert.alert('Shopping', 'Invalid Email / Password')
}
dispatch({ type: 'LOGIN', id: email, token: userToken , isLoading: false})
},
signUp: () => {
//Pending
},
signOut: async () => {
try {
await AsyncStorage.removeItem('userToken')
} catch (e) {
console.log(e)
}
dispatch({ type: 'LOGOUT' })
},
}), [])
useEffect(() => {
setTimeout(async () => {
let userToken;
userToken = null;
try {
userToken = await AsyncStorage.getItem('userToken')
console.log('token', userToken)
} catch (e) {
console.log(e)
}
dispatch({ type: 'RETRIVE_TOKEN', token: userToken })
}, 1000)
}, []);
if (loginState.isLoading) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator size="large" color="black" />
</View>
)
}
return (
<Authcontext.Provider value={authContext}>
<NavigationContainer>
{loginState.userToken !== null ?
(
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeStackScreen} /> //Dashboard Screens
</Drawer.Navigator>
) :
<RootStackScreen /> //Authentication Screens
}
</NavigationContainer>
</Authcontext.Provider>
)
}
export default AppRouter
Thanks in Advance
You need to dispatch login inner signInWithEmailAndPassword then or userToken always will be null because of the async.
const authContext = React.useMemo(() => ({
signIn: async (email, password) => {
let userToken;
userToken = null;
if (email !== '' && password !== '') {
auth().signInWithEmailAndPassword(email, password)
.then(async (success) => {
dispatch({ type: 'LOGIN', id: email, token: userToken , isLoading: false})
try {
await AsyncStorage.setItem('userToken', success.user.uid)
} catch (e) {
console.log(e)
Alert.alert('Shopping', e)
}
})
.catch(error => {
if (error.code === 'auth/email-already-in-use') {
Alert.alert('Shopping', 'That email address is already in use!')
}
if (error.code === 'auth/invalid-email') {
Alert.alert('Shopping', 'That email address is invalid!')
}
Alert.alert('Shopping', error.code)
});
} else {
Alert.alert('Shopping', 'Invalid Email / Password')
}
},
signUp: () => {
//Pending
},
signOut: async () => {
try {
await AsyncStorage.removeItem('userToken')
} catch (e) {
console.log(e)
}
dispatch({ type: 'LOGOUT' })
},
}), [])
I am using react-navigation 3 versions with react native 0.59 version.
I am using switch navigation for the login code. Once I got login it's redirecting to the home screen and other navigation from home screen working fine but drawer icon not displaying.
My navigator.js
export const AppStack = createStackNavigator({
Home: {
screen: HomeScreen,
navigationOptions: {
headerRight:soundicon()
}
},
withdraw: {
screen: WithdrawScreen,
navigationOptions: {
headerRight:soundicon()
}
},
deposite: {
screen: DepositScreen,
navigationOptions: {
headerRight:soundicon()
}
},
money: {
screen: MoneyScreen,
navigationOptions: {
headerRight:soundicon()
}
}
});
export const drawermenu = createDrawerNavigator({
Home: AppStack,
})
export const AuthStack = createStackNavigator({
SignIn: {
screen: SignInScreen,
navigationOptions: {
header: null,
}
},
Signup: {
screen: SignupScreen,
navigationOptions: {
}
},
ForgotPassword: {
screen: ForgotPasswordScreen,
navigationOptions: {
}
}
});
export const AppNavigator = createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: drawermenu,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
});
export const AppNavigatorObj = createAppContainer(AppNavigator)
As it is you can open the drawer by sliding right,if you want to open it with a icon you have to make a header component and use it on screens you want. Heres a nice example of headers: https://react-native-training.github.io/react-native-elements/docs/header.html
When I specify drawerLockMode direactly with createStackNavigator it is not working.
const drawerStack = createStackNavigator({
HomeScreen: { screen: HomeScreen },
}, {
headerMode: 'screen',
navigationOptions: {
drawerLockMode:'locked-closed'
}
})
But when I use drawerStack variable to define navigationOptions, it is working.
drawerStack.navigationOptions = ({ navigation }) => {
drawerLockMode = 'locked-closed';
return {
drawerLockMode,
};
};
Am I doing any mistake when I am directly using it inside createStackNavigator?
Update
As #bennygenel suggested, we need to user drawerLockMode in drawerNavigator instead of stackNavigator. Here is what i have done.
const drawerNavigator = createDrawerNavigator({
drawerStack: drawerStack
}, {
contentComponent: DrawerComponent,
navigationOpions:{
drawerLockMode:'locked-closed'
}
})
But it is not working in this way also. The only way it is working is by using the const variable created using createStackNavigator or createDrawerNavigator
Try the following code, it's working for me:
const UserHome_StackNavigator = StackNavigator({
userHm: {
screen: UserHome,
navigationOptions: ({ navigation }) => ({
title: 'User screen title',
headerStyle: {
backgroundColor: 'white',
},
headerTintColor: 'black'
}),
},
});
UserHome_StackNavigator.navigationOptions = ({ navigation }) => {
let drawerLockMode = 'locked-closed';
//logic here to change conditionaly, if needed
return {
drawerLockMode,
};
};
in case someone need this:
const drawerNavigator = createDrawerNavigator({
drawerStack: drawerStack
}, {
contentComponent: DrawerComponent,
navigationOpions: ({navigation}) => {
let routeName = navigation.state.routes[navigation.state.routes.length-1].routeName;
if(['Animation', 'Splash', 'Intro', 'Login', 'Signup'].indexOf(routeName)>=0){
return {
drawerLockMode: 'locked-closed'
}
}
return {}
}
})
Can't solve this problem, using react-native and redux-thunk.
I am trying to post data to firebase.io and repeatedly get the same error: Actions must be plain objects. Use custom middleware for async actions.
reducers/index.js
import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import PlacesReducer from './placesReducer'
const rootReducer = combineReducers({
places : PlacesReducer,
});
let composeEnhancers = compose;
if (__DEV__) {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}
const configureStore = () => {
return createStore(rootReducer, applyMiddleware(thunk));
};
export default configureStore;
actions/index.js
export const addPlace = (placeName, location, image) => {
return dispatch => {
const placeData = {
name: placeName,
location: location
};
fetch("https://awesome-places-b592c.firebaseio.com/placesReducer.json", {
method: "POST",
body: JSON.stringify(placeData)
})
.catch(err => console.log(err))
.then(res => res.json())
.then(parsedRes => {
console.log(parsedRes);
});
};
};
reducers/placesReducer.js
const initialState = {
places: []
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_PLACE":
return {
...state,
places: state.places.concat({
key: Math.random(),
name: action.placeName,
image: {
uri: action.image.uri,
location: action.location
}
})
};
default:
return state;
}
};
export default reducer;
All help is appreciated, thank you.
UPDATE
Added initialState argument to the createStore() function in reducers/index.js
const configureStore = () => {
return createStore(rootReducer, {places: []}, applyMiddleware(thunk));
};
Still receiving the same error
You need to dispatch the actions after the async request is completed
.then(parsedRes => {
console.log(parsedRes);
dispatch({
type: YOUR_REDUCER_TYPE,
parsedRes
})
});
Also as mentioned in the createStore docs, you need to add the initialState
(reducer, preloadedState, enhancer)
Try updating the code as follows:
export const addPlace = (placeName, location, image) => {
//return dispatch => {
const placeData = {
name: placeName,
location: location
};
fetch("https://awesome-places-b592c.firebaseio.com/placesReducer.json", {
method: "POST",
body: JSON.stringify(placeData)
})
.catch(err => console.log(err))
.then(res => res.json())
.then(parsedRes => {
console.log(parsedRes);
// you may tweak this
// use ...parsedRes following the reducer code you paste,
// I assume location, placeName and image are inside parsedRes
dispatch({ type:"ADD_PLACE", ...parsedRes })
});
//};
};
Then in the reducer:
const reducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_PLACE":
// check the action content
console.log(action)
return {
...state,
places: state.places.concat({
key: Math.random(),
name: action.placeName,
image: {
uri: action.image.uri,
location: action.location
}
})
};
default:
return state;
}
};
The view is not rendering in iOS build but its running fine in Android,
the view is not navigating to the next view.
Here is the navigation structure, the screen not navigating from Address Result to AddressDetail
const StackNav = StackNavigator({
Main: {
screen: Addresssearch,
navigationOptions: ({ navigation }) => ({
title: "Address Search",
header: null
})
},
AddressDetail: {
screen: AddressDetails,
navigationOptions: (props) => ({
title: "Profile View",
})
},
AddressResult: {
screen: AddressResult,
navigationOptions: (props) => ({
title: "",
})
},{
headerMode: 'float'
})
The render Function of Address Detail class
render(){
console.log("=========== View Details Props ============");
const {bgImageTop, boxWithName, phoneIcon, showMapBtn, locationIcon, profile_icon} = images;
const { container, topCover, coverView, subContainer, info1, titleLabel, profileImage,subHeading } = styles;
const {data, differentSearchData} = this.state.dataArray;
const personData = data
const { firstname, lastname, middlename, age, streetAddress, city, zipcode, landline } = personData["0"];
const routeName = this.props.navigation.state.routeName
console.log("routeName: "+routeName)
const { onScroll = () => {} } = this.props;
return(
<View>
<Text>Read</Text>
</View>
);
}}
Here the console statement is executed but I can't see the screen navigating from Address Result to Address Details in iOS build
Details:
"react": "^16.0.0-alpha.12",
"react-native": "^0.48.3",
"react-navigation": "^1.0.0-beta.12",