Programmatic navigation in Ionic React using ExploreContainer template - reactjs

I'm using the side menu template from Ionic React, so most of my logic is in ExploreContainer.tsx .
//imports
//some logic
const ExploreContainer: React.FC<ContainerProps> = ({ name }) => {
switch( name ) { //switch to show content on requested item from side menu
case 'Home':
return (
//content for home
);
case 'Info':
return (
//content for info
);
}
};
export default ExploreContainer;
Now, since I'm building this for mobile, I'm using Capacitor. This enables me to listen for hardware back button events. When the user presses their back button, I want them to back taken back to 'Home' in this example.
All answers I have found kind of always refer to buttons for navigation, where it's possible to declare a custom state and maybe use the <Redirect /> component.
Now, to extend the former example, lets say I want something like this:
//imports
import { Plugins } from '#capacitor/core';
const ExploreContainer: React.FC<ContainerProps> = ({ name }) => {
Plugins.App.addListener("backButton", () => {
name = "Home";
}
switch( name ) { //switch to show content on requested item from side menu
case 'Home':
return (
//content for home
);
case 'Info':
return (
//content for info
);
}
};
export default ExploreContainer;
Sadly, this does not work. Previously I've tried the following:
import { useHistory } from 'react-router-dom';
Plugins.App.addListener("backButton", () => {
history.push("/page/Home");
});
This however leads to some really ugly behavior. The app completely reloads, all variables are reset, etc.
If I understand correctly, the name property of the ExploreContainer is a state that gets changed when the user selects an item from the menu, so normally the app doesn't need to reload.
How would I go about this?

import { useHistory } from 'react-router-dom';
const history = useHistory();
Plugins.App.addListener("backButton", () => {
history.push("/page/Home");
});

Related

Link not opening in React admin's list - makes edit instead

I have a Resource of campaigns; id field of each campaign linked to a standalone page of URLs, belonging to that campaign. (I still don't know how I'll do it).
I use custom field
import { useRecordContext, useGetOne } from 'react-admin';
import { Link } from 'react-router-dom';
export default () => {
const campaign = useRecordContext();
const { data, isLoading } = useGetOne('campaign', { id: campaign.campaign_id });
const campaignUrlsPage = `/campaign/${campaign.id}/urls/`;
return /*isLoading ? null : */ <Link to={campaignUrlsPage}>{campaign.id}</Link>;
};
Why I commented out isLoading? Strange thing but it's always true, after all I don't notice any artefact that way.
Links are shown in the corresponding ceils, thay are actually ancor tags, but clicking them causes record edit. Yes, I have rowClick="edit" and I really need it.
Additionally, I have UrlField in the grid, and clicking it first shows record edit page but next it directs to the URL.
Can it be fixed?
Thanks in advance.
To support rowClick="edit", each datagrid row has a click handler.
If you put your button in a datagrid, when users click on the button, the click even propagates down to the datagrid row, and the row click event handler fires.
So you must stop event propagation in your link:
export default () => {
const campaign = useRecordContext();
const { data, isLoading } = useGetOne('campaign', { id: campaign.campaign_id });
const campaignUrlsPage = `/campaign/${campaign.id}/urls/`;
return isLoading ? null : (
<Link
to={campaignUrlsPage}
onClick={e => e.stopPropagation()}
>
{campaign.id}
</Link>
);
};

Need to re-render component after state is set to true

In my react app, I have a page that allows the user to "add" exercises to a personal list. The option to add an exercise is included in one "page" and the ability to see added exercises is view in a other. Side by side showing the two pages, notice 'Your Exercises' is empty. What I am trying to do is display these selected exercises. The exercises themselves are loaded via mapping function, from an array of objects. each object has a "selected" field and are set as "false" by default. My "add" button in the exercises page changes the state value of each component to "true", so now what I want to do is get these "true" values and render them on the page where it should show your exercises.
//Render your exercises
import React from "react";
import ExListItem from "./ExListItem";
// import selectedExercises from "../../../selectedExercises";
import exercises from "../../../exercises";
const selectedExerciseList = exercises
.filter(function (item) {
return item.selected === true;
})
.map(function ({name, reps}) {
return {name, reps};
});
// MAP HERE
function createSelectedExercises(exercises) {
return <ExListItem name={exercises.name} reps={exercises.reps} />;
}
function ExerciseList() {
return (
<ul data-tag="channelList" id="exercise-list" class="list-group mb-3">
{selectedExerciseList.map(createSelectedExercises)}
</ul>
);
}
export default ExerciseList;
Shouldnt this map the exercises?
You may need to use React state to accomplish this! You can do so with the following:
import React, { useState, useEffect } from 'react'
const Component = () => {
const [exercises, setExercises] = useState([])
useEffect(() => {
setExercises(exerciseFilterFunction(exercises)) // this will cause the re-render
}, [])
return (
<div>{exercises.map(e => <div>{e.name}</div>)}</div>
)
}

Fixed side menu on an specific Next.js route

I'm starting a new project and I would like to always have a top nav and only in a specific route have a sidebar where I can click and the content will change.
The top nav should always be visible if I am in site.com or site.com/*/**
If I go to site.com/posts I want to see a sidebar with all the posts title
If I click a post on the left it will redirect to site.com/posts/1 and only the right side should change
I'm having trouble with the second and third bullet. My pages path is pages/posts/index.js and pages/posts/[id].js but how can I declare only one file and avoid duplicating code? 🤔
I tried pages/posts/[[...slug]].js but I'm seeing this error: Error: Optional catch-all routes are currently experimental and cannot be used by default ("/posts/[[...slug]]")
I'm looking for examples but so far I couldn't do it.
Any ideas?
Update: Next.js now recommends to use function getLayout()
New Documentation ✨
Create a file: /layouts/RequiredLayout.js
const RequiredLayout = ({ children }) => {
return (
<>
<Navbar />
<main>{children}</main>
</>
);
};
export default RequiredLayout;
Then edit _app.js
function MyApp({ Component, pageProps }) {
const Layout = Component.Layout || EmptyLayout;
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
const EmptyLayout = ({ children }) => <>{children}</>;
export default MyApp;
Now whenever you require to use this layout, then add only a single line at the end
NameOfComponent.Layout = RequiredLayout;
For in your case: In **pages/posts/[id].js**
import React from 'react'
const ID = () => {
return(
<>
<p>page with id details</p>
</>
)
}
export deafult ID;
ID.Layout = RequiredLayout //responsible for layout
I was able to enable this experimental option by having my next.config.js look like this (I'm using Next v9.4.4):
module.exports = {
experimental: {
optionalCatchAll: true,
},
};

Next JS - Show modal based on query param just on first page load

I want to show modal on first page load based on query param existence and not to show it again when user refresh the page, how should I handle it?(query param is changeable)
I have tried it by set cookie but not work fine.
So as I understand you want the following:
Open a modal based on a query string.
Not open the modal if already opened.
Lets say that the url is www.mysite.com/payment?paymentStatus=success&paymentId=111
import { withRouter, Link } from 'react-router';
import queryString from 'query-string';
import Cookies from 'universal-cookie';// Haven't tried it but probably works from the repo
import Modal from 'some-modal-library';
const cookies = new Cookies();
const PaymentHandler = (props) => {
const {paymentStatus, paymentId} = queryString.parse(props.location.search) || {};
console.log(params); // {paymentStatus: 'success', paymentId: '111'}
const openModal = paymentStatus && paymentStatus === 'success' ? true : false;
if (openModal) {
// Check if stored in cookies based on the paymentId previously
const paymentStatusFromCookie = cookies.get(paymentId);
if (paymentStatusFromCookie && paymentStatusFromCookie === 'success') {
// This means we've already set it. Just return null or react fragment
// This will not render the modal on the DOM
return <></>;
}
// Else set the cookie and allow the modal render
cookies.set(paymentId, paymentStatus, { path: '/' });
}
return (
<Modal
open={openModal}
>
<h2>Your payment was successful</h2>
{/*Reroute to another page once close to even avoid refresh in the first place*/}
<Link to="/" />Close</Link>
</Modal>
)
}
export default withRouter(PaymentHandler);

Status of React Navigation drawer? (open or closed)

I'm building a drawer with React Navigation and want to perform some logic if the user closes the drawer. I don't see anything obvious in the documentation that will allow me to do this. Is anyone aware of a way to do this?
You need to custom navigation actions to capture the DrawerClose event:
const MyDrawerNavigator = DrawerNavigator({
//...
});
const defaultGetStateForAction = MyDrawerNavigator.router.getStateForAction;
MyDrawerNavigator.router.getStateForAction = (action, state) => {
//use 'DrawerOpen' to capture drawer open event
if (state && action.type === 'Navigation/NAVIGATE' && action.routeName === 'DrawerClose') {
console.log('DrawerClose');
//write the code you want to deal with 'DrawerClose' event
}
return defaultGetStateForAction(action, state);
};
According to #ufxmeng
import {
StatusBar,
} from "react-native";
const MyDrawerNavigator = DrawerNavigator({
//...
});
const defaultGetStateForAction = MyDrawerNavigator.router.getStateForAction;
MyDrawerNavigator.router.getStateForAction = (action, state) => {
if(state && action.type === 'Navigation/NAVIGATE' && action.routeName === 'DrawerClose') {
StatusBar.setHidden(false);
}
if(state && action.type === 'Navigation/NAVIGATE' && action.routeName === 'DrawerOpen') {
StatusBar.setHidden(true);
}
return defaultGetStateForAction(action, state);
};
See here https://github.com/react-community/react-navigation/blob/673b9d2877d7e84fbfbe2928305ead7e51b04835/docs/api/routers/Routers.md
and here https://github.com/aksonov/react-native-router-flux/issues/699
This is for anyone using the v2.0+ version of react-navigation. There are now drawer actions you can track.
{
OPEN_DRAWER: 'Navigation/OPEN_DRAWER',
CLOSE_DRAWER: 'Navigation/CLOSE_DRAWER',
TOGGLE_DRAWER: 'Navigation/TOGGLE_DRAWER',
DRAWER_OPENED: 'Navigation/DRAWER_OPENED',
DRAWER_CLOSED: 'Navigation/DRAWER_CLOSED'
}
react-navigation.js line 825
However, it seems that implied drawer navigations from say swiping don't fire the OPEN_/CLOSE_ actions, since you didn't manually toggle it. The _OPENED/_CLOSED actions do fire afterwards, though.
const MyDrawerNavigator = createDrawerNavigator(...);
const defaultGetStateForAction = MyDrawerNavigator.router.getStateForAction;
MyDrawerNavigator.router.getStateForAction = (action, state) => {
switch (action.type) {
case 'Navigation/OPEN_DRAWER':
case 'Navigation/DRAWER_OPENED':
StatusBar.setHidden(true, 'slide');
break;
case 'Navigation/CLOSE_DRAWER':
case 'Navigation/DRAWER_CLOSED':
StatusBar.setHidden(false, 'slide');
break;
}
return defaultGetStateForAction(action, state);
};
Working with "react-navigation": "^3.5.1"
const defaultGetStateForAction = DrawerNav.router.getStateForAction;
DrawerNav.router.getStateForAction = (action, state) => {
if(action){
if(action.type == 'Navigation/MARK_DRAWER_SETTLING' && action.willShow){
StatusBar.setHidden(true);
} else if(action.type == 'Navigation/MARK_DRAWER_SETTLING' && !action.willShow) {
StatusBar.setHidden(false);
}
}
return defaultGetStateForAction(action, state);
};
For anyone looking to wire it up such that the drawer events are available in one of your Screens or Components instead of the top level app, I was able to wire that up by using screenProps as described in this post. You first set up the screenProps on your app and pass in the router and whatever else you need. You can pull screenProps off the props and use it your screen or component (I wired it up in the constructor in this example), use getStateForAction to setState in your component driven off the router events.
Here is an example (some code removed for clarity)
App.js
import React from 'react';
import { AppLoading } from 'expo';
import {
createDrawerNavigator,
createAppContainer,
createStackNavigator,
} from 'react-navigation';
import { HomeScreen } from './src/screens/HomeScreen.android';
import { LanguageSelectScreen } from './src/screens/LanguageSelectScreen';
export default class App extends React.Component {
state = {
isLoadingComplete: false,
isTilted: false,
};
constructor() {
super();
}
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading />
);
} else {
return (
<MyApp screenProps={MyAppNavigator.router} />
);
}
}
}
const MyAppNavigator = createDrawerNavigator(
{
Home: {
screen: HomeScreen,
},
PlayerNameScreen: {
screen: PlayerNameScreen,
},
},
{
unmountInactiveRoutes: true,
initialRouteName: 'PlayerNameScreen',
},
);
const RootStack = createStackNavigator(
{
Main: {
screen: MyAppNavigator,
},
MyModal: {
screen: LanguageSelectScreen,
},
},
{
mode: 'modal',
headerMode: 'none',
},
);
export const MyApp = createAppContainer(RootStack);
HomeScreen.android.js
import React from 'react';
import {Icon} from 'react-native-elements';
export class HomeScreen extends React.Component {
static navigationOptions = {
drawerLabel: () => 'Home',
drawerIcon: ({ tintColor }) => (
<Icon
name="checkerboard"
type="material-community"
size={25}
color={tintColor}
/>
),
};
constructor(props) {
super(props);
const router = props.screenProps;
const defaultGetStateForAction = router.getStateForAction;
router.getStateForAction = (action, state) => {
switch (action.type) {
case 'Navigation/MARK_DRAWER_SETTLING':
if (action.willShow == false) {
console.log('CLOSED');
this.setState({ isTilted: false });
} else if (action.willShow == true) {
this.setState({ isTilted: true });
console.log('OPEN');
}
break;
}
return defaultGetStateForAction(action, state);
};
this.state = {
isTilted: false,
};
}
render() {
const { isTilted } = this.state;
// ... render using isTilted
}
}
Now in react navigation 5 you can directly access the status of your drawer using this approach :
useIsDrawerOpen() is a Hook to detect if the drawer is open in a parent navigator.
For exemple in your view you can test if your drawer is open or not directly using this approach :
import { useIsDrawerOpen } from '#react-navigation/drawer';
const MainContainer = () => {
return (
<View style={(useIsDrawerOpen()) ? styles.conatinerOpenStyle : styles.containerClosedStyle}>
<Text>{console.log("Test drawer "+useIsDrawerOpen())}</Text>
</View>
);}
I found something similar to asdfghjklm.
Here's my code which is working perfectly, even when you use a swipe to open - and then use a button to close it:
openCloseDrawer = (props) => {
if (props.navigation.state.index == 1) {
props.navigation.navigate('DrawerClose');
} else {
props.navigation.navigate('DrawerOpen');
}
}
I execute this function when the user taps on the "Open/Close Drawer" button.
Based on Brad Bumbalough, fredrivett (thank you mans) solution I find a more fast response solution (the other solution delays secons in some uses).
const defaultGetStateForAction = StackLogadoDrawer.router.getStateForAction;
StackLogadoDrawer.router.getStateForAction = (action, state) => {
switch (action.type) {
case 'Navigation/MARK_DRAWER_SETTLING':
if (action.willShow == false) {
console.log('CERRADO');
} else if (action.willShow == true) {
console.log('ABIERTO');
}
break;
}
return defaultGetStateForAction(action, state);
};
This is fired just immediately actions occurs (or very near).
It works on gestures or calling openDrawer()
Anyway I think this is a "must have" easy and direct way in the API
Hmm not really seeing much. One way would be to control the opening/closing of the drawer manually using:
this.props.navigation.navigate('DrawerOpen'); // open drawer
this.props.navigation.navigate('DrawerClose'); // close drawer
That way you can wrap your close/open events in a function where you can do whatever you want before opening/closing.
The only other possible solution I saw in their docs was using a custom contentComponent. You can pass a function to the onItemPress(route) event, so maybe you can try hooking into that.
This is working fine in React-Native, not sure about React.
For my case I had to change the StatusBar so I did not need to know wether the Drawer is closed fully or not, So below code worked for me...
props.navigation.state.drawerMovementDirection === 'closing'
? //it is closing not fully closed
: (props.navigation.state.drawerMovementDirection === 'opening' ||
props.navigation.state.isDrawerOpen) && (
//fully opened or opening
)
If you don't need to know immediate then you can use below code, and this will work fine and will give you accurate answer whether the Drawer is Open or Not!
Note: This will be delayed answer, in my case it was taking 1 sec.
props.navigation.state.isDrawerOpen?
//open
:
//close;
If the solution does't work I am very sorry, but above all answer did not worked! So this is the version which is working for me:))
useEffect(() => {
const _unsubscribe = props.navigation.addListener('state', (data) => {
var array = data.data.state.routes[0].state.history;
const mydata = array.some(item => item.hasOwnProperty('status'))
console.log('data--->', mydata)
if (mydata) {
console.log('daawer close ')
} else {
console.log('daawer open ')
}
});
return _unsubscribe;
}, [])
Without Redux integration can be used onNavigationStateChange on router component. Just intercept drawer actions: DrawerOpen and DrawerClose.
Example:
handleNavigationState = (previous, next, action) => {
if (action.routeName === 'DrawerOpen') {
this.props.setDrawerState(true);
} else if (action.routeName === 'DrawerClose') {
this.props.setDrawerState(false);
}
}
render() {
return (
<Router onNavigationStateChange={this.handleNavigationState} />
);
}
I know this is late, but for anyone who is looking for an answer:
The logic for the drawer being open/closed is in:
this.props.navigation.state.routes[0].index
It's 0 for closed, 1 for open.
You can also toggle the Drawers with
this.props.navigation.navigate('DrawerToggle')
instead of
this.props.navigation.navigate('DrawerOpen');
or
this.props.navigation.navigate('DrawerClose');
It seems to be more convenient for me and I have not happened upon any problems yet. Although it's nice to know whether they are toggled or not in order to invoke other actions.
I truly believe React-Navigation has one of the worst documentation that I have ever seen. There are commands that nobody knows about. I could not find the DrawerToggle action in the documents, and I only happened upon it through using console.log(this.props.navigation);

Resources