Can not fetch from database in React Native with Firebase? - javascript

I'm a newbie in React Native and struggling to fetch the data to Firebase database.
Basically, here's the flow of my data:
1. User chooses locations and their trip information ( name, startDate, endDate) --> stored in Redux
2. In the Redux store, at the time user creates the trip, I POST that trip to the Firebase database
3. After that, in the TripsListScreen, I fetch(method: 'GET') the trips from that database to show to the user
Here's the behavior, the TripsListScreen just keeps refreshing over and over, even though I successfully post the trips to the database. But the error is in the function which load the trips from the server ( so I think I couldn't fetch successfully from the server)
Error video
Here's the TripsListScreen
const TripsListScreen = props => {
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState();
const trips = useSelector(state => state.trips.trips); // The root reducer in App.js is trips
const dispatch = useDispatch();
const loadTrips = useCallback(async() => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(tripActions.fetchTrip());
} catch (error) {
console.log(error)
setError(error);
}
setIsRefreshing(false);
},[dispatch, setIsLoading, setError]);
useEffect(() => {
const willFocus = props.navigation.addListener(
'willFocus',
loadTrips
);
return () => {willFocus.remove();}
},[loadTrips]);
useEffect(() => {
setIsLoading(true);
loadTrips().then(
setIsLoading(false),
);
}, [dispatch, loadTrips])
if(isLoading){
return(
<View>
<ActivityIndicator size='large' color={Colors.primary} />
</View>
)
}
if(!isLoading && trips.length === 0){
return(
<View style={styles.container}>
<Text>No trips created. Let make some!</Text>
</View>
)
}
if (error) {
return (
<View style={[styles.container, {justifyContent: 'center', alignItems: 'center'}]}>
<Text>An error occurred!</Text>
<Button
title="Try again"
onPress={loadTrips}
color={Colors.primary}
/>
</View>
);
}
return(
<Layout style={[styles.container, {justifyContent: 'center', alignItems: 'center'}]}>
<Layout style={styles.header}>
<Layout style={styles.titleContainer}>
<Text style={styles.title}>Let's pack for your trip</Text>
</Layout>
<Layout style={styles.subtitleContainer}>
<Text style={styles.subtitle}>And share it with your friends</Text>
</Layout>
</Layout>
<View style={styles.list}>
<FlatList
onRefresh={loadTrips}
refreshing={isRefreshing}
horizontal={true}
data={trips.reverse()}
keyExtractor={item => item.id}
renderItem={(itemData => {
return(
<TripItem
onSelect={() => props.navigation.navigate('PlanningProcess', {screen: 'MainMapScreen', params: {doNotAddPlace: true}})}
onEdit={() => props.navigation.navigate('PlanningProcess', {screen: 'TripDescription'})}
eventName={itemData.item.name}
startDate={itemData.item.startDate}
endDate={itemData.item.endDate}
/>
)
})}
/>
</View>
</Layout>
);
};
Here's the tripReducer
import { ADD_TRIP, SET_TRIP } from '../../actions/trip/trip';
import { Trip } from '../../../src/models/trip';
const initialState = {
trips: []
}
export default tripReducer = (state = initialState, action) => {
switch(action.type){
case ADD_TRIP:
const newTrip = new Trip(
action.tripData.id,
action.tripData.ownerId,
action.tripData.name,
action.tripData.startDate,
action.tripData.endDate,
action.locations
);
return {
...state,
trips: state.trips.concat(newTrip),
}
case SET_TRIP:
return{
trips: action.trips
}
default: return state;
}
}
Here's the tripActions
import { Trip } from "../../../src/models/trip";
export const ADD_TRIP = 'ADD_TRIP';
export const SET_TRIP = 'SET_TRIP';
export const addTrip = (name, startDate, endDate, locations) => {
return async (dispatch, getState) => {
const token = getState().auth.user.token;
const userId = getState().auth.user.uid;
const response = await fetch(
`https://...(keep secret for safety)/trips.json?auth=${token}`, {
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
body: JSON.stringify({
name,
startDate,
endDate,
locations,
ownerId: userId,
})
});
const resData = await response.json();
console.log(resData);
dispatch({
type: ADD_TRIP,
tripData:{
id: resData.name,
ownerId: userId,
name,
startDate,
endDate,
locations
}
})
}
};
export const fetchTrip = () => {
return async (dispatch, getState) => {
const userId = getState().auth.user.uid;
try {
const response = await fetch(
'https://...(keep secret for safety)/trips.json'
);
if(!response.ok){
throw new Error('Something went wrong, please try again!')
};
const resData = await response.json();
console.log(resData);
const loadedTrips = [];
for(let key in resData){
loadedTrips.push(new Trip(
key,
resData[key].ownerId,
resData[key].name,
resData[key].startDate,
resData[key].endDate,
resData[key].locations
))
};
dispatch({
type: SET_TRIP,
trips: loadedTrips.filter(trip => trip.ownerId === userId)
})
} catch (error) {
throw error;
}
}
}
Redux store flow:
1. ADD_TRIP: post trip information to the server (firebase database)
2. SET_TRIP: fetch trip information from the server, which is posted by the ADD_TRIP action ( to display on the screen for the user)
Here's the database after it receives the data from ADD_TRIP:
Here's the rules for Firebase database:
EDIT 1:
I tried to use axios and the request failed with error code 401, meaning the request hasn't be authorized.
PLEASE HELP

After, adding authentication in the request and editing the resData. I get in done now.
The response.json() can't perform since it's not a function to be awaited ( I think so )
All I need is to assign resData to the response.data. No need to awaiting it
try {
const response = await axios.get(
`https://meetupapp-21180.firebaseio.com/trips.json?auth=${token}`,{
method: 'GET',
}
)
// if(!response.ok){
// throw new Error('Something went wrong, please try again!')
// };
const resData = response.data;

Related

How to make react component call an axios HTTP request

I am trying to make a section for my website with a news bar that contains snippets of update posts for my site. To do so, I have successfully created the data schema, routing on my backend for post and get requests. I am routing requests on the client server using axios for my XMLHTTPRequests, Redux for my global state store, and cors. Using my NewsBar component, I wrap my NewsPosts component, which is a collection of rendered NewsPost components.
NewsBar component:
function useQuery() {
return new URLSearchParams(useLocation().search);
}
const NewsBar = () => {
const classes = useStyles();
const query = useQuery();
const page = query.get('page') || 1;
const searchQuery = query.get('searchQuery');
const [currentId, setCurrentId] = useState(0);
console.log(`NewsBar: ${useLocation().search} | query=>${query}`);
console.log(`searchquery: ${searchQuery}`);
return (
<>
<Grow in>
<Grid container direction={'row'} className={classes.newsBar} justifyContent='center'>
<Typography variant='h3'>News Bar</Typography>
<Grid item>
<NewsPosts setCurrentId={setCurrentId} />
</Grid>
</Grid>
</Grow>
</>
)
}
export default NewsBar;
NewsPosts component:
const NewsPosts = ({ setCurrentId }) => {
const { newsPosts, isLoading } = useSelector((state) => state.newsPosts);
const classes = useStyles();
newsPosts.map((newsPost) => {
console.log(newsPost);
})
console.log(new Date().toISOString());
console.log(`newsPosts array: ${typeof newsPosts} ${newsPosts}`)
if (!newsPosts.length && !isLoading) return 'No news posts.';
return (
isLoading ? <LinearProgress /> : (
<Grid container alignItems='stretch' spacing={3}>
{newsPosts?.map((newsPost) => (
<Grid key={newsPost._id} item xs={12} sm={12} md={12} lg={12}>
<NewsPost newsPost={newsPost} setCurrentId={setCurrentId}/>
</Grid>
))}
</Grid>
)
);
};
export default NewsPosts;
I added console logging for each of my routing actions and methods, and unfortunately it seems as though I get an empty array of type Object instead of the page of documents I am supposed to get. Within my console, the only logs that output are from NewsPosts.js.
NewsBar: | query=>
NewsBar.js:27 searchquery: null
NewsPosts.js:17 2022-12-01T20:36:08.958Z
NewsPosts.js:18 newsPosts array: object
On top of that, I checked my network requests and none were made. Could someone attempt to tell me why this is?
Axios code as per request:
import { START_LOADING, END_LOADING, FETCH_POST, FETCH_ALL, DELETE, CREATE } from "../constants/actionTypes";
import * as api from '../api/index.js';
//CREATE ACTIONS -> should follow the standard XMLHTTPRequest operation
export const getNewsPost = (id) => async (dispatch) => {
try {
console.log('actions: action getNewsPost was called');
dispatch({ type: START_LOADING })
const { data } = await api.fetchNewsPost(id);
dispatch({type: FETCH_POST, payload: { newsPost: data }});
console.log(`got post ${data}`);
} catch (error) {
console.log(error);
}
};
export const getNewsPosts = (page) => async (dispatch) => {
try {
console.log('actions: action getNewsPosts was called');
dispatch({ type: START_LOADING });
const {data : {data, currentPage, numberOfPages }} = await api.fetchNewsPosts(page);
dispatch({ type: FETCH_ALL, payload: { data, currentPage, numberOfPages }});
dispatch({ type: END_LOADING });
} catch (error) {
console.log(error);
}
};
export const createNewsPost = (newsPost, history) => async (dispatch) => {
try {
console.log('actions: action createNewsPosts was called');
dispatch({ type: START_LOADING });
const { data } = await api.createNewsPost(newsPost);
dispatch({ type: CREATE, payload: data });
history.push(`/newsPosts/${data._id}`);
} catch (error) {
console.log(error);
}
};
export const deleteNewsPost = (id) => async (dispatch) => {
try {
console.log('actions: action deleteNewsPost was called');
await await api.deletePost(id);
dispatch({ type: DELETE, payload: id });
} catch (error) {
console.log(error);
}
};
index.js
import axios from 'axios';
const API = axios.create({ baseURL: 'http://localhost:5000' });
API.interceptors.request.use((req) => {
if (localStorage.getItem('profile')) {
req.headers.Authorization = `Bearer ${JSON.parse(localStorage.getItem('profile')).token}`;
}
return req;
});
export const fetchPost = (id) => API.get(`/posts/${id}`);
export const fetchPosts = (page) => API.get(`/posts?page=${page}`);
export const fetchPostsByCreator = (name) => API.get(`/posts/creator?name=${name}`);
export const fetchPostsBySearch = (searchQuery) => API.get(`/posts/search?searchQuery=${searchQuery.search || 'none'}&tags=${searchQuery.tags}`);
export const createPost = (newPost) => API.post('/posts', newPost);
export const likePost = (id) => API.patch(`/posts/${id}/likePost`);
export const comment = (value, id) => API.post(`/posts/${id}/commentPost`, { value });
export const updatePost = (id, updatedPost) => API.patch(`/posts/${id}`, updatedPost);
export const deletePost = (id) => API.delete(`/posts/${id}`);
export const signIn = (formData) => API.post('/user/signin', formData);
export const signUp = (formData) => API.post('/user/signup', formData);
export const fetchNewsPost = (id) => API.get(`/news/${id}`);
export const fetchNewsPosts = (page) => API.get(`/news?page=${page}`);
export const createNewsPost = (newNewsPost) => API.post('/news', newNewsPost);
export const deleteNewsPost = (id) => API.delete(`/news/${id}`);

How to Pass Id correctly to Rest API Endpoint from React

I'm trying to fetch data through endpoint from Django Rest Framework
endpoint is :
/api/v1/categories/nested/{id}/
Problem is when I'm requesting with id, Django server show this error :
ValueError: Field 'id' expected a number but got 'undefined'.
[07/Feb/2022 15:53:01] "GET /api/v1/categories/nested/undefined/ HTTP/1.1" 500 162581
As this suggest I'm unable to Pass id correctly,
So need littl help to fix that
I'm using actions > reducer > store > component approach using react redux
action.js
export const listCategoryDetails = (id) => async (dispatch) => {
try {
dispatch({ type: CATEGORY_DETAIL_REQUEST });
const { data } = await axios.get(`/api/v1/categories/nested/${id}`); // Purpose to show nested brands[]
dispatch({
type: CATEGORY_DETAIL_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: CATEGORY_DETAIL_FAIL,
payload:
error.response && error.response.data.detail
? error.response.data.detail
: error.message,
});
}
};
reducer.js
export const categoryDetailsReducer = (
state = { category: { } },
action
) => {
switch (action.type) {
case CATEGORY_DETAIL_REQUEST:
return { loading: true, ...state };
case CATEGORY_DETAIL_SUCCESS:
return { loading: false, category: action.payload };
case CATEGORY_DETAIL_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
store.js
const reducer = combineReducers({
categoryDetail: categoryDetailsReducer,
});
component
function CategoryDetail({ match, history }) {
// const { id } = useParams();
// console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails());
}, [dispatch, match]);
return <div>
{categories.map(category => (
<Col key={category.id} sm={12} md={8} lg={4} xl={3} >
<h1><strong>{category.title}</strong></h1>))}
</div>;
}
export default CategoryDetail;
const id = ...
and pass it to dispatch function dispatch(listCategoryDetails(id))
Before
// const { id } = useParams();
// console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails());
}, [dispatch, match]);
After
const { id } = useParams();
console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails(id));
}, [dispatch, match]);
Inside UseEffect You Are Not Passing id Variable So its Saying Id Is Undefined

How to use two contexts for authentication and subscription in Chrome extension?

I want to display the Stripe subscription form after user has signed up into the extension. I want to display home to a subscribed user. And whenever the user opens the extension the home should be displayed if he has already subscribed. If not, it should display subscription form.
But the problem is my app is displaying both the home and subscription form to a subscribed user.
Here is my private route code:
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
const { currentUser, subscriptionStatus } = useContext(AuthContext);
return (
<Route
{...rest}
render={(routeProps) =>
!!currentUser ? (
!!subscriptionStatus ? (
<RouteComponent {...routeProps} />
)
: (
<Redirect to={"/subscribe"} />
)
) : (
<Redirect to={"/login"} />
)
}
/>
);
};
This is my auth context provider:
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [pending, setPending] = useState(true);
const [emailVerified, setEmailVerified] = useState(true);
const [helper, setHelper] = useState(false);
const [subscriptionStatus, setSubscriptionStatus] = useState(null);
useEffect(() => {
app.auth().onAuthStateChanged(async(user) => {
setCurrentUser(user);
if(!user.emailVerified){
setEmailVerified(false);
}else{
setEmailVerified(true);
const fetchData = async () => {
const token = user && (await user.getIdToken());
const payloadHeader = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
};
const status = await fetch('http://localhost:3000/is-subscribed', payloadHeader).then(r => r.json());
if(status == 'active'){
setSubscriptionStatus(status);
setPending(false);
}else{
setPending(false);
}
}
fetchData();
}
});
}, []);
if (pending && helper) {
return <Loader />;
}
if(!emailVerified){
return <>Please verify your email</>
}
return (
<AuthContext.Provider
value={{
currentUser, subscriptionStatus
}}
>
{children}
</AuthContext.Provider>
);
};
Any idea on this?
The easiest option would be redirecting your customers to Checkout to pay, and handling the successful payments in your Firebase app via webhooks, but you can also use the custom flow if you prefer.

Why is the array in my redux reducer not available from another component after a redirect to another page of my app?

I have two separate components. I want to have a button that when clicked on will add an element to an array in my reducer and redirect to another component, this component that gets redirected to needs to render the data that was just added to the array. The page redirects to the component I want but the data does not load and the console.logs don't show anything.
This is the component that has the redirect button. On this component the console.log(socialNetworkContract.members[0]) shows the string I expect.
const Posts = () => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
window.location.href='/member'
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default Posts;
This is my reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
and this is the component that is redirected to. this just says undefined in console.log(socialNetworkContract.members[0])
const Member = () => {
const [user, setUser] = useState({});
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
useEffect(async()=>{
try {
const pro = socialNetworkContract.members[0]
console.log(socialNetworkContract.members[0])
await setUser(pro)
console.log(socialNetworkContract.members[0])
} catch (e) {
console.error(e)
}
}, [])
I have the route set in Routes.js as
<Route path="/member" exact component={Member} />
Use history.push('/') instead of window.location.href which will reload your whole page and you will lost your local state data.
const {withRouter} from "react-router-dom";
const Posts = (props) => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push('/member');
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter( Posts );

Changing app navigation structure from version 4 to 5 in react native

I was working on an old app using react navigation version 4 the app contains a register and login in page obviously and then the content of the app.
recently I started remaking the content of the app using react navigation version 5 in order to use the shared element animation and the bottom tab navigator and it was fairly simple.
but I struggled with converting the login part to version 5 since the app structure is somewhat complicated and I am somewhat new to react navigation version 5.
i will leave a figure of the app structure bellow a long with samples of the code used.
App.js :
import { setNavigator } from "./app/navigationRef";
const articleListFlow = createStackNavigator({
Main: MainScreen, // screen with diffrent articles categories
ResultsShow: ResultShowScreen, // article details screen
});
const loginFlow = createStackNavigator({
Signup: SignupScreen,
Signin: SigninScreen,
});
loginFlow.navigationOptions = () => {
return {
headerShown: false,
};
};
articleListFlow.navigationOptions = {
title: "News Feed",
tabBarIcon: ({ tintColor }) => (
<View>
<Icon style={[{ color: tintColor }]} size={25} name={"ios-cart"} />
</View>
),
activeColor: "#ffffff",
inactiveColor: "#ebaabd",
barStyle: { backgroundColor: "#d13560" },
};
const switchNavigator = createSwitchNavigator({
ResolveAuth: ResolveAuthScreen,
MainloginFlow: createSwitchNavigator({
//WelcomeScreen: WeclomeScreen,
loginFlow: loginFlow,
}),
mainFlow: createMaterialBottomTabNavigator(
{
articleListFlow: articleListFlow,
ArticleSave: ArticleSaveScreen, // we dont need this one
Account: AccountScreen,
},
{
activeColor: "#ffffff",
inactiveColor: "#bda1f7",
barStyle: { backgroundColor: "#6948f4" },
}
),
});
const App = createAppContainer(switchNavigator);
export default () => {
return (
<AuthProvider>
<App
ref={(navigator) => {
setNavigator(navigator);
}}
/>
</AuthProvider>
);
};
NavigationRef.js :
import { NavigationActions } from "react-navigation";
let navigator;
export const setNavigator = (nav) => {
navigator = nav;
};
export const navigate = (routeName, params) => {
navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
};
// routename is the name of the routes singin singup accountscreen
// params information we want to pass to the screen we want to show
AuthContext.js
import { AsyncStorage } from "react-native";
import createDataContext from "./createDataContext";
import userAPI from "../api/user";
// using navigate to access the navigator and redirect the user
import { navigate } from "../navigationRef";
// AUTHENTICATION REDUCERS
const authReducer = (state, action) => {
switch (action.type) {
case "add_error": {
return {
...state,
errorMessage: action.payload,
};
}
case "clear_error_message": {
return {
...state,
errorMessage: "",
};
}
case "signin": {
return {
errorMessage: "",
token: action.payload,
};
}
default:
return state;
}
};
// CLEARING ERROR MESSAGES WHEN SWITCHING SIGNIN-SIGNUP
const clearErrorMessage = (dispatch) => () => {
dispatch({ type: "clear_error_message" });
};
// AUTOMATIC SIGNIN ONLY USING TOKENS ON USER DEVICE
const tryLocalSignin = (dispatch) => async () => {
const token = await AsyncStorage.getItem("token");
if (token) {
// if token exists
dispatch({ type: "signin", payload: token });
navigate("Main");
} else {
// if token doesnt exist
navigate("WelcomeScreen");
}
};
// SIGNUP
const signup = (dispatch) => async ({ email, password }) => {
try {
const response = await userAPI.post("/signup", { email, password });
await AsyncStorage.setItem("token", response.data.token);
dispatch({ type: "signin", payload: response.data.token });
// making use of the navigate component to access navigation
// and redirect the user
navigate("Main");
} catch (err) {
dispatch({
type: "add_error",
payload: "Something went wrong with sign up",
});
}
};
// SIGNIN
const signin = (dispatch) => async ({ email, password }) => {
try {
const response = await userAPI.post("/signin", { email, password });
await AsyncStorage.setItem("token", response.data.token);
// using signin since the logic is the same
dispatch({ type: "signin", payload: response.data.token });
// making use of the navigate component to access navigation
// and redirect the user
navigate("Main");
} catch (err) {
console.log(err);
dispatch({
type: "add_error",
payload: "Something went wrong with sign in",
});
}
};
// SIGNOUT
const signout = (dispatch) => async () => {
// removing the token makes identification not work again
await AsyncStorage.removeItem("token");
dispatch({ type: "signout" });
navigate("loginFlow");
};
// CREATING CONTEXT AND PROVIDER OBJECTS FOR AUTHENTICATION
export const { Provider, Context } = createDataContext(
authReducer,
{
signin,
signup,
signout,
clearErrorMessage,
tryLocalSignin,
},
{
token: null,
errorMessage: "",
}
);
createDataContext.js
import React, { useReducer } from "react";
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let action in actions) {
// for every action in the actions, call it with dispatch
boundActions[action] = actions[action](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
My appologies for the long code and thank you in advance for anyone who can help.
There are several things that you need to consider when moving from V4 to V5 it involves some changes and also you can consider using features like the hooks.
The first change will be removing the Switch Navigator and conditionally render the navigator in its place. This will be done in your App.js. As you already have a reducer based implementation you can use the state values to take this decision.
The next change will be the creation of stacks, in V4 you create the navigation by passing the screen, now everything is a component and you pass the screens as children.
The option are also sent as props to either the navigator or the screen itself.
The usage of navigation ref is still possible but you can also use hooks like usenavigation inside components and for your authentication flow you wont be using this as you conditionally render the navigators.
I have made a simplified version based on your code.
App.js
const AuthStack = createStackNavigator();
const AppTabs = createMaterialBottomTabNavigator();
const ArticleStack = createStackNavigator();
const Articles = () => {
return (
<ArticleStack.Navigator>
<AppTabs.Screen name="ArticlesList" component={ArticleList} />
<AppTabs.Screen name="ArticlesDetails" component={ArticleDetail} />
</ArticleStack.Navigator>
);
};
export default function App() {
const [state, dispatch] = React.useReducer(authReducer, {
isLoading: true,
token: null,
errorMessage: '',
});
React.useEffect(() => {
const bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async (data) => {
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async (data) => {
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
{state.token === null ? (
<AuthStack.Navigator headerMode="none">
{state.isLoading ? (
<AuthStack.Screen name="Welcome" component={WelcomeScreen} />
) : (
<>
<AuthStack.Screen name="SignIn" component={SignInScreen} />
<AuthStack.Screen name="SignUp" component={SingUpScreen} />
</>
)}
</AuthStack.Navigator>
) : (
<AppTabs.Navigator
activeColor="#f0edf6"
inactiveColor="#3e2465"
barStyle={{ backgroundColor: '#694fad' }}>
<AppTabs.Screen
name="Articles"
component={Articles}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name="home"
color={color}
size={size}
/>
),
}}
/>
<AppTabs.Screen name="Search" component={SearchScreen} />
<AppTabs.Screen name="Save" component={SaveScreen} />
<AppTabs.Screen name="Account" component={AccountScreen} />
</AppTabs.Navigator>
)}
</NavigationContainer>
</AuthContext.Provider>
);
}
Auth Context
const AuthContext = React.createContext();
export default AuthContext;
Auth Reducer
export const authReducer = (state, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...state,
token: action.token,
isLoading: false,
};
case 'SIGN_IN': {
return {
errorMessage: '',
token: action.payload,
};
}
case 'SIGN_OUT': {
return {
errorMessage: '',
token: null,
};
}
default:
return state;
}
};
As you can see the flow will be showing the welcome screen till the token is loaded from async storage and then based on that show the tabs or the login screen. Also the parameters are passed as props. I've moved the actions to app.js but it can be separated as well.
You can see a fully running sample here
https://snack.expo.io/#guruparan/navigation-sample-3
Hope this helps, Feel free to ask if there are any questions.
As per your diagram, I have tried to create Navigation
const WelcomeStack = createStackNavigator();
const Tab = createBottomTabNavigator();
const ArticleStack = createStackNavigator();
const MainStack = createStackNavigator();
function Welcome(){
return(
<WelcomeStack.Navigator>
<WelcomeStack.screen name='SignIn' component={SignIn}/>
<WelcomeStack.screen name='SignUp' component={SignUp}/>
</WelcomeStack.Navigator>
)
}
function Article(){
return(
<ArticleStack.Navigator>
<ArticleStack.Screen name='ArtcileList' name={ArticleList}/>
<ArticleStack.Screen name='ArticleDetail' name={ArtcileDetail}/>
</ArticleStack.Navigator>
)
}
function TabNav(){
<Tab.Navigator>
<Tab.Screen name='Article' component={Article}/>
<Tab.Screen name='Search' component={Search}/>
<Tab.Screen name='Save' component={Save}/>
<Tab.Screen name='Account' component={Account}/>
</Tab.Navigator>
}
function App(){
return(
<NavigationContainer>
<MainStack.Navigator>
{this.state.isLogin ?
<MainStack.Screen name='Tab' component={TabNav}/>
:
<MainStack.Screen name = 'WelcomeStack' component={Welcome}/>
}
</MainStack.Navigator>
</NavigationContainer>
)
}
In react navigation 5, their is no switch navigator so you have to go with stack navigation + ternary operator.
This is just an idea as per your diagram. You can make it better after some R&D.

Categories

Resources