React native navigate to ID of specific post - reactjs

I would like to navigate the same way you do with react-router.
I am fetching an API and display the results as cards, when you click a card, I need it to navigate to a specific ID based on the card selected.
<Route path='/item:id' component={item-details} />
and then on the card, you do this.<Link to='item/:id' > Card...</Link>
That's how you do it with react-router-dom...I need to archive the same with React Native.
Any solutions?

I suggest using react-navigation, it's the best solution for navigation for a RN app. It's not quite like React Router but RN requires a different method. In react-navigation, you declare your screens as 'navigators', e.g stack navigator.
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Screen1" component={Screen1Component} />
<Stack.Screen name="Screen2" component={Screen2Component} />
</Stack.Navigator>
);
}
On each screen, a navigation prop is passed down. This has a lot of properties and functions but one is a function called navigate, which takes you to another screen in your stack. For example:
const Screen1Component = ({ navigation }) => (
<View>
// content
<TouchableOpacity onPress={() => navigation.navigate('Screen2) })>Go to Screen2</TouchableOpacity>
</View>
)
navigate also has a second parameter it can take called screen params. These are parameters you can pass between screens when you navigate. It's used like navigation.navigate('Screen2, { id: '1234' }).
In your Screen2 component, there will be a route prop, where you can then pick that param up and use it, for example:
const Screen2Component = ({ route, navigation }) => {
const { id } = route.params
// do something with it, call api, pass to redux action,etc
return ...
}
Hope that helps.

Related

React-Native - Return to Sign in screen if unauthenticated user

I am new to react/native and this is my first app.
I want to protect some screens which only an authenticated user can access. I read some articles and Questions. But I am still not able to get them either because they are class based or some for react.
I made a few attempts at it. One of them that seemed to work is like this.
OrderList.js
import IsUserLoggedIn from '../utilities/authHelpers';
import UserSignin from './UserSignin';
const OrderList = ({ route, navigation }) => {
...
...
if(!IsUserLoggedIn){
return <UserSignin/>
}
return (
<SafeAreaView style={styles.container}>
{renderOrders()}
</SafeAreaView>
)
}
export default OrderList;
This shows Signin screen. But it also runs all other logic like REST API calls, that is made in useEffect before IsLoggedIn check returns the Singin screen. I tried putting the check before those calls but then react-native complaints about returning too early. I also tried, before the useEffect etc, the navigation to signin route. That again didn't work. React-native throws warning that it cannot redirect from one component to another while it is still rendering. I don't recall exact message but it along those lines.
Another issue with this is that I will need to add this check in each of the protected screens. I want something that sits before all screens like in Navigators.
navigators/drawer.js
const Drawer = createDrawerNavigator();
const DrawerNav = () => {
return (
<Drawer.Navigator initialRouteName="Home"
>
<Drawer.Screen name="Home" component={StackNav} />
<Drawer.Screen name="Sign In" component={UserSignin} />
<Drawer.Screen name="My Orders" component={OrderList} />
<Drawer.Screen name="Profile" component={UserProfile} />
<Drawer.Screen name="Change Password" component={ChangePassword} />
<Drawer.Screen name="Forgot Password" component={ForgotPassword}/>
<Drawer.Screen name="Sign Up" component={UserSignup}/>
</Drawer.Navigator>
)
}
export default DrawerNav;
I have read about HOC(High Order Components) that some have suggested in this case. But I don't understand how that is implemented in this case.
Any help will be appreciated.
UPDATE 2
I finally got it working with few changes.
The main thing is that you need to make sure component are re-rendered. This happens only when props or state is updated. But in my case, I save authentication tokens in local storage and that doesn't affect state of the app, thus no re-rendering. Local state in drawer.js had no effect except the first time app started. If user signed in they need to refresh the app to make drawer get new state.
So I put another state in redux store. It is global store and I update it whenever a user is successfully logged in.
drawer.js
import {useSelector} from "react-redux";
import { OrderList, RequireAuthentication } from "../screens"
const DrawerNav = () => {
const isLoggedIn = useSelector(state => state.userSigninState?.user_is_signed_in)
return (
<Drawer.Navigator
initialRouteName="Home"
>
<Drawer.Screen name="My Orders" component={RequireAuthentication(OrderList, isLoggedIn)} />
)
}
export default DrawerNav;
RequireAuthentication.js
import UserSignin from './UserSignin';
const RequireAuthentication = (protectedScreen, isLoggedIn) => {
// This is a HOC()Higher Order Component).
// It will be used to make sure protected screens are not accessible to anyone accept authenticated user.
return (
isLoggedIn!=true?UserSignin:protectedScreen
)
}
export default RequireAuthentication;
reducers.js
const initialUserSignin = {
user_is_signed_in:false
}
export function userSigninReducer(state=initialUserSignin, action){
if(action.type == USER_SIGNIN_STATUS_UPDATED){
return {
...state,
user_is_signed_in: action.payload.is_signed_in,
};
} else {
return state
}
}
actions.js
export function userSigninStatusUpdated(user_signed_in){
return {
type:USER_SIGNIN_STATUS_UPDATED,
payload:{
is_signed_in:user_signed_in
}
}
}
Signin.js
import store from "../redux/store";
import {userSigninStatusUpdated} from '../redux/actions';
// This is when user signs in. We save their tokens and also update their signin state to true
await AsyncStorage.setItem('access_token', response.data.access);
await AsyncStorage.setItem('refresh_token', response.data.refresh);
store.dispatch(userSigninStatusUpdated(true));
In signout logic you will set the state to false like this:
store.dispatch(userSigninStatusUpdated(false));
I haven't implemented signout logic yet but you would also want to remove the tokens on signout.
Hope this helps someone.
UPDATE 1
This doesn't work. The issue is the async nature of IsUserLoggedIn(). I will try to fix that and post an update.
Original Answer
Answering my own question.
Screens or components are just functions. An HOC(High Order Component) is also a function but it can take another function(screen or component) as parameter or return another function.
So we create an HOC(a function) and pass to it our protected screen(a function) as parameter.
Inside our HOC we check if the user is logged in. If they are then we return the screen(that we passed as parameter to HOC) else we return Signin screen(again a function).
That is the main logic. I want this check to happen in navigators. So instead of having our protected screen directly mention in the params for the navigator we mention our HOC with our screen passed to that as param.
Below is my code .
RequireAuthentication.js // This is inside my screens directory.
import React from "react";
import IsUserLoggedIn from '../utilities/authHelpers'
import UserSignin from './UserSignin';
const RequireAuthentication = (protectedScreen) => {
// This is an HOC(Higher Order Component).
// It will be used to make sure protected screens are not accessible to anyone accept authenticated user.
return (
!IsUserLoggedIn()? UserSignin: protectedScreen
)
}
export default RequireAuthentication;
The navigator, drawer.js
import {RequireAuthentication } from "../screens"
const DrawerNav = () => {
return (
<Drawer.Navigator initialRouteName="Home"
>
<Drawer.Screen name="My Orders" component={RequireAuthentication(OrderList)} />
</Drawer.Navigator>
)
}
Thank you

Equivalent of dynamic query params for React Native Navigation?

I want to dynamically show profiles based on userId in React Native. In react where I set up my routes, I'd add something like route="/users/:userId", and then use that userId to make various calls, display various data, etc. When I implement a link to the profile page, I route them to, say, /users/1.
How can I achieve something similar in React Native? The only thing seen around the web is to pass it in as params, but does that mean I've got to pass it in each time I call navigation.navigate? I'm not sure what the route-defining or the navigation.navigate syntax should look like.
I've checked a couple of SO threads, but none of them seem to answer this fundamental question. And all of the docs and articles about dynamic routing in React Navigation seem to mostly concern passing in dynamic title headers and stuff.
React Navigation primarily uses a params object which looks like { userId: 1 } rather than a string-based route definition like "/users/:userId".
Linking to a Profile
The navigation.navigate function takes two props: the name of the route and the params to pass. You don't need to include the second params argument at all if you are navigating to a route which doesn't take any parameters. navigation.navigate("Home") is fine. But when going to your "User" route you will always need to include a userId.
In order to go to a particular user profile, you would call:
onPress={() => navigation.navigate("User", { userId: 1 })}
Docs: Passing parameters to routes
Accessing Params
That userId param can then be accessed in the UserScreen component through the props which are injected by the navigator. Every screen receives props route and navigate. The params are a property of the route prop.
So you can define a UserRoute component like this, where we get the current userId from route.params.userId.
const UserScreen = ({route, navigation}) => (
<View>
<Text>Viewing profile for user #{route.params.userId}</Text>
<TouchableOpacity onPress={() => navigation.navigate("Home")}>
<Text>Back to Home</Text>
</TouchableOpacity>
</View>
);
(Typescript users: this component is React.FC<StackScreenProps<RootStackParamList, "User">> where RootStackParamList is your own app's param definitions)
Declaring Routes
You don't actually need to say anything about the params when you create your routing. This works just fine:
export const App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="User" component={UserScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
There are some additional optional configurations that you can use. You can map the params to options or map the params to a unique screen id, for example.
<Stack.Screen
name="User"
component={UserScreen}
options={({ route }) => ({
title: `User Profile #${route.params.userId}`
})}
getId={({ params }) => params.userId.toString()}
/>
(Typescript users will want to define a type, typically called RootStackParamList, which maps each route to its params types. This is then used as the generic on StackScreenProps, StackNavigationProp, etc.)
String-Based Navigation
React Navigation does support linking to paths like "/user/1", but it requires additional configuration. Behind the scenes it still uses a params object, so you need to define a mapping from the path to the params. It can be a custom mapping, or you can use the same syntax that React Router does with : to define params. "users/:userId" will create a params object with the userId as the key.
Your Stack.Screen component stay the same. The configuration options are passed as a prop linking to the NavigationContainer component. If you set this up then you are able to use the experimental Link component like you would in React Router to link to a path like "/users/1".
export const App = () => {
return (
<NavigationContainer
linking={{
prefixes: ["https://yourdomain.com"],
config: {
screens: {
Home: "",
User: "users/:userId"
}
}
}}
>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="User" component={UserScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
CodeSandbox Demo with typescript types.

Window.location.reload() in react-native

I often use window.location.reload on my React Web project.
window.location.reload();
Is there any similar way to reload page(component) in react-native?
The thing that you mention is a browser feature. React Native uses native capabilities to render your app, so there are no browser features like window.location.reload()
So I am not sure what is your particular use-case for this. I guess it is needed to reload the current screen, not the full app.
In this case, you should use a react-way to re-render your screens. In React the views are re-rendered when the props or state changes. So if you want to trigger a full reload, you need to have some internal state that will trigger a reload. For example, you can use key property and a container with an internal state that toggles this key.
But I would consider it a hack. You should really use the react data-driven views
You can read more about key prop either in official docs or check out this article from Google: https://kentcdodds.com/blog/understanding-reacts-key-prop/
import React from 'react';
import { View, Text } from 'react-native';
const Container = () => {
const [key, setKey] = React.useState(0);
const reload = React.useCallback(() => setKey((prevKey) => prevKey + 1), []);
return <Child reload={reload} key={key} />;
}
const Child = ({ reload }) => {
const getRandomId = () => parseInt(Math.random() * 100, 10);
// We use useRef to showcase that the view is fully re-rendered. Use ref is initialized once per lifecycle of the React component
const id = React.useRef(getRandomId());
return (
<View>
<Text>My random id is {id}</Text>
<Button onPress={reload} />
</View>
)
}
#Coding is Life
import { NavigationEvents } from 'react-navigation';
<NavigationEvents onWillFocus={() => this.goBackReload()}/>
This is the way to reload the page, ie. when you go back to page you got the call back method. Using this method clear all state value and refresh the page. Another way is refresh control using to reload the app.
<ScrollView refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh} />
}>
</ScrollView>
When you scroll down the screen onRefresh method trigger.

React Router <Link> to dynamic page and go back to previous state in useEffect

I'm trying to make a React app with dynamic pages and navigational fetch with next and previous buttons, when click on the Item it shows the page dynamic page but when I press the back button on the browser it forgets the state where count and input value and shows the initial state. What should I write to save the state so that when I go back it stays on the same count and value and not start from initial state?
const App = () => {
return (
<Router>
Route path='/item/:id' component={Item} />
Route path='/' exact component={Search} />
</Router>
)
}
const Items = (props) => {
return (
<div>
{props.data.map(image => (
/* Link to component Item */
<Link to={`/item/${image.id}`} key={image.id}>
<img src={image.urls.small} alt={image.description} />
</Link>
))}
</div>
);
}
const Search = () => {
useEffect(() => {
getData();
},[count]);
const nextPage = (event) => {
setCount(count + 1);
getData();
event.preventDefault();
}
const prevPage = event => {
if (count > 1) {
setCount(count - 1);
getData();
}
event.preventDefault();
}
return (
<div>
<Items data={images} />
<button onClick={prevPage}>PREV</button>
<button onClick={nextPage}>NEXT</button>
</div>
);
}
It looks like you may need to look into state management systems/patterns. What you are looking for is called persistence, where you can revisit a page and maintain the same values in the same session.
Your form state is being cleared because you're holding it in a "local" component state - a component that is being re-rendered every time you switch pages. The "easy" way to solve this is to manage the Search component's state inside of App and add all the handlers to App as well, this is also called "raising" the state. While this is the "easy" way to do things, it is also quick and dirty (you will probably need to refactor in the future) because it will eventually overcomplicate your App component if you add other page/form states, etc.
The other way (also quick and easy and what I would recommend) is to store your search values in localStorage. You can save them in a JSON format to be read and used to update the form as soon as the component mounts. I think if this is a small app, this is probably the best way.
The approach for larger applications (3+ pages) is to use a global state management system, Flux, Redux, Mobx etc. These systems make it much easier to maintain a global app state and persist information even if you navigate through different pages in the app (as long as you maintain the session). This approach, while I recommend you look into for practice is usually left fort larger applications and can add too much overhead for what its worth on smaller apps.
Another 'quick win' approach is to utilize ReactRouter's state. Basically you can set state properties to a particular point in the router's history. I've used this before to remember scroll position of a lazy-loading grid.
What this does is applies your state to the current browser history position, so when you navigate elsewhere and then hit back on your browser navigation bar, the previous state is also restored (not just the URL).
In essence, create your own Link component wrapper:
import React from 'react';
import { withRouter } from 'react-router';
import { Link } from 'react-router-dom';
class CustomLink extends React.Component {
handleClick(e) {
const { count, input, history } = this.props;
state = { count, input }
history.replace({ state });
}
render() {
const { to, children } = this.props;
return (
<Link
onClick={this.handleClick}
to={to}
>
{children}
</Link>
);
}
}
export default withRouter(CustomLink);

Is there a way to Add new Route into React native Stack navigation dynamically

We are building an app in which are using React native stack Navigation,
Depends in the web api call based on the output we want to add the redirect to the same type of screen with different data and that new screen can create another screen..
We are using stack navigation with a single route at the start of the app.
Based on the Web Api call i want to inject a new Route to the existing stack and it should be able to go back to the previous screens
Is there Any way to achieve this.?
Quoted from https://reactnavigation.org/docs/en/navigating.html
If we call this.props.navigation.navigate with a route name that we haven't defined on a stack navigator, nothing will happen. Said another way, we can only navigate to routes that have been defined on our stack navigator — we cannot navigate to an arbitrary component.
I would suggest you to create all possible routes for app and according to web api just navigate app the what screen do you want with any data. It's all up to you.
According to React Navigation Documents, you can change your stack whenever you want:
index is the index of the route you want to navigate on new actions array of routes. (in this case we want to navigate to route 'NewRoute' and its index is 1)
import {StackActions, NavigationActions} from "react-navigation"
class Example extends Component {
// ... Some other methods
navigate = (id) => {
const resetAction = StackActions.reset({
index: 1,
actions: [
NavigationActions.navigate({ routeName: 'Home' }),
NavigationActions.navigate({ routeName: 'NewRoute', params: { id, ...otherParams } }),
],
});
this.props.navigation.dispatch(resetAction);
}
render() {
return (
<View>
<TouchableOpacity onPress={() => this.navigate()}>
<Text>Go to New Route</Text>
</TouchableOpacity>
</View>
);
}
}

Resources