I have to show a loading white overlay page when route changes in Next.js. I used n-progess, it's working. But I have to show a white overlay page instead of the n-progress. My _app.js is written in class based views. So I don't know where I can put those code. I tried lot of ways, but none is working.
_app.jsx
function Loading() {
const router = useRouter();
const [loading, setLoading] = useState(false);
useEffect(() => {
const handleStart = (url) => url !== router.asPath && setLoading(true);
const handleComplete = (url) =>
url === router.asPath &&setLoading(false)
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
});
return (
loading && (
<div
className="spinner-wrapper"
style={{
height: '100%',
width: '100%',
backgroundColor: 'blue',
}}>
<div
className="spinner"
style={{ width: '50px', height: '50px' }}></div>
</div>
)
);
}
class MyApp extends App {
constructor(props) {
super(props);
this.persistor = persistStore(props.store);
}
componentDidMount() {
setTimeout(function () {
document.getElementById('__next').classList.add('loaded');
}, 100);
this.setState({ open: true });
}
render() {
const { Component, pageProps, store } = this.props;
const getLayout =
Component.getLayout ||
((page) => <DefaultLayout children={page} />);
return getLayout(
<Provider store={store}>
<PersistGate
loading={<Component {...pageProps} />}
persistor={this.persistor}>
<Loading />
<Component {...pageProps} />
</PersistGate>
</Provider>
);
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
What changes needed in this case? How can do those things?
Related
I have a problem with displaying a list for user selection. When I open the page for the first time, it works correctly. But after I reload the page, the users "disappear" or are unloaded.
Before reload
After reload.
Here is the code I have.
My-page:
const Page = observer(() => {
const { project } = useContext(Context);
const [modalVisible, setModalVisible] = useState(false);
useEffect(() => {
fetchUsers().then((data) => project.setUsers(data));
}, []);
return (
<Container>
<Button onClick={() => setModalVisible(true)}>
ChooseUser
</Button>
<ChooseUser show={modalVisible} onHide={() => setModalVisible(false)} />
</Container>
);
});
export default Page;
Modal:
const ChooseUser = observer(({ show, onHide }) => {
const { project } = useContext(Context);
return (
<Modal show={show} onHide={onHide}>
<Form>
<Form.Select>
{/* The problem with this list */}
{project.users.map((user) =><option>{user.username}</option>)}
</Form.Select>
</Form>
</Modal>
);
});
Context creating in index.js:
export const Context = createContext(null);
ReactDOM.render(
<Context.Provider value={{
project: new ProjectStore(),
}}
>
<App />
</Context.Provider>,
document.getElementById('root'),
);
ProjectStore
export default class ProjectStore {
constructor() {
this._users = [];
makeAutoObservable(this);
}
setUsers(value) {
this._users = value;
}
get users() {
return this._users;
}
}
You might try Array.from(project.users).map((user) ... instead of project.users.map((user) ... and see if that helps.
this is the scenario:
In Home Route, there is a dropdown which contains SubSystems and based on each subSystem menu items are different. So, when I change subSystem, Menu Items of drawer should update.
I have implemented my drawer to get dynamic items like below code. But, the problem is that when then SubSystems are loaded I go to get the menu items after that I update the menuData state the whole component recompiles and it falls into a loop.
How Can I Overcome This Problem ?
MY CODE
StackNavigationManager.js
import {AuthContext} from './context';
const StackNavigationManager = () => {
const [menuData, setMenuData] = React.useState([]);
const authContext = React.useMemo(() => ({
validateMenuData: menuData => {
setMenuData(menuData);
},
}));
const DrawerScreen = ({route, navigation}) => (
<Drawer.Navigator
drawerContent={props => <CustomDrawerContent {...props} />}>
<Drawer.Screen
name="HomeDrawer"
component={Home} />
</Drawer.Navigator>
);
function CustomDrawerContent(props) {
return (
<SafeAreaView style={{flex: 1}}>
<DrawerItemList {...props} />
<FlatList
data={menuData}
keyExtractor={data => data.id.toString()}
renderItem={({item}) => {
return (
<DrawerItem
label={item.name}
onPress={() => alert(item.id)}></DrawerItem>
);
}}
/>
</SafeAreaView>
);
}
return (<AuthContext.Provider value={authContext}>
<NavigationContainer>
<Stack.Navigator
initialRouteName={initialRoute}>
<Stack.Screen
name="Home"
component={DrawerScreen}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>);
Context.js
import React from 'react';
export const AuthContext = React.createContext();
Home.js
import {AuthContext} from './context';
const home = ({navigation}) => {
const {validateMenuData} = React.useContext(AuthContext);
React.useEffect(() => {
console.log('MOUNTED EFFECT');
getSubSystems();
return () => {
isMounted = false;
console.log('UNMOUNTED EFFECT');
};
}, []);
const getSubSystems = async () => {
const subSystemModel = {
isActive: true,
isVisible: true,
};
fetchSubSystemData(
subSystemModel,
response => {
if (response.list !== null) {
let listData = [...response.list];
fetchMenuAction(
listData[0].id,
response2 => {
validateMenuData([...response2.list]);
},
);
}
},
() => {},
);
};
I am implementing infinite scrolling with react-native, when I do a search the result is returned and if the result has many pages on the API, when I scroll the API returns more data .
my implementation works fine on the class component but when I try to convert it to a working component, when I do a search, the data is returned and if I did another search, the previous data from the previous search is still displayed
class component
class Exemple extends React.Component {
constructor(props) {
super(props);
this.searchedText = "";
this.page = 0;
this.totalPages = 0;
this.state = {
films: [],
isLoading: false,
};
}
_loadFilms() {
if (this.searchedText.length > 0) {
this.setState({ isLoading: true });
getFilmsWithSearch(this.searchedText, this.page + 1).then((data) => {
this.page = data.page;
this.totalPages = data.total_pages;
this.setState({
films: [...this.state.films, ...data.results],
isLoading: false,
});
});
}
}
_searchTextInputChanged(text) {
this.searchedText = text;
}
_searchFilms() {
this.page = 0;
this.totalPages = 0;
this.setState(
{
films: [],
},
() => {
this._loadFilms();
}
);
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
}
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder="Titre du film"
onChangeText={(text) => this._searchTextInputChanged(text)}
onSubmitEditing={() => this._searchFilms()}
/>
<Button title="Rechercher" onPress={() => this._searchFilms()} />
<FlatList
data={this.state.films}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <FilmItem film={item} />}
onEndReachedThreshold={0.5}
onEndReached={() => {
if (this.page < this.totalPages) {
this._loadFilms();
}
}}
/>
{this._displayLoading()}
</View>
);
}
}
the functional component
const Search = () => {
const [films, setFilms] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [page, setPage] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const [searchedText, setSearchedText] = useState("");
const _loadFilms = () => {
if (searchedText.length > 0) {
setIsLoading(true);
getFilmsWithSearch(searchedText, page + 1).then((data) => {
setPage(data.page);
setTotalPages(data.total_pages);
setFilms([...films, ...data.results]);
setIsLoading(false);
});
}
};
useEffect(() => {
_loadFilms();
}, []);
const _searchTextInputChanged = (text) => {
setSearchedText(text);
};
const _searchFilms = () => {
setPage(0);
setTotalPages(0);
setFilms([]);
_loadFilms();
};
const _displayLoading = () => {
if (isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
};
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder="Titre du film"
onChangeText={(text) => _searchTextInputChanged(text)}
onSubmitEditing={() => _searchFilms()}
/>
<Button title="Rechercher" onPress={() => _searchFilms()} />
<FlatList
data={films}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <FilmItem film={item} />}
onEndReachedThreshold={0.5}
onEndReached={() => {
if (page < totalPages) {
_loadFilms();
}
}}
/>
{_displayLoading()}
</View>
);
};
With functional components, you cannot run effects (like getFilmsWithSearch) outside of useEffect.
From https://reactjs.org/docs/hooks-reference.html#useeffect
Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React’s render phase). Doing so will lead to confusing bugs and inconsistencies in the UI.
When you are calling _loadFilms from within then onSubmitEditing={() => _searchFilms()} event handler, you are not running inside useEffect, unlike the call to _loadFilms from useEffect that runs with the component mounts (because the second parameter to useEffect is [], it runs once on mount).
To solve this issue, you would typically have _searchFilms set a state variable (something like reloadRequested, but it does not have to be a boolean, see the article below for a different flavor) and have a second useEffect something like this:
useEffect(() => {
if (reloadRequested) {
_loadFilms();
setReloadRequested(false);
}
}
, [reloadRequested])
For a more complete example with lots of explanation, try this article https://www.robinwieruch.de/react-hooks-fetch-data.
Component renders with initial state before state is updated.
The initial state is null and onHandlePrint method updates the state when the button is clicked.
class App extends React.Component {
state = {
pdf: null,
};
updatePDF = (data) => {
}
onHandlePrint = (pdf) => {
this.setState({pdf}, () => {
this.updatePDF(this.state.pdf)
})
}
render() {
return (
<div className="container">
<Router>
<ActivityDetail results={this.state.results} clickPrint={this.onHandlePrint} />
<Switch>
<Route
path="/pdf"
render={() => (
<PDFDocument data={this.state.pdf} />
)}
/>
</Switch>
</Router>
</div>
);
}
}
The button is a Link using to open a new tab that will render a PDF document with the data passed into the event as the "obj"
const ActivityDetail = ({ results, clickPrint }) => {
const renderedList = results.map((obj, index) => {
return (
<li key={index}>
<div className="service-container">
<Link to="/pdf" target="_blank" className="print-button-container">
<button
className="print-button"
onClick={() => clickPrint(obj)}
>Print</button>
</Link>
</div>
</li>
);
});
return (
<div>
<ul>
{renderedList}
</ul>
</div>
);
};
export default ActivityDetail;
This is the PDF document that should get the data when the Print button is clicked but props is undefined.
const styles = StyleSheet.create({
page: {
flexDirection: 'row',
},
section: {
margin: 10,
padding: 10,
flexGrow: 1
}
})
const PDFDocument = (props) => {
const { NameOfService } = props
console.log('props:', props)
return(
<PDFViewer className="pdf-viewer">
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>
{NameOfService}
</Text>
</View>
</Page>
</Document>
</PDFViewer>
)
}
export default PDFDocument
EDIT
So what I know have is a callback to a method that handles the newly set state.
onHandlePrint = (pdf) => {
this.setState({pdf}, () => {
this.updatePDF(this.state.pdf)
})
}
My new question is how do I send that data from the updatePDF method to the component ?
You should not setState within a setState callback function. Instead it should return the new state. This:
onHandlePrint = (pdf) => {
this.setState({pdf}, () => {
this.setState({pdfStatus: true})
});
};
should be:
onHandlePrint = (pdf) => {
this.setState(() => {pdf, pdfStatus: true});
};
But really if you don't need to use previous state you don't need to use a callback. Just do:
onHandlePrint = (pdf) => {
this.setState({pdf, pdfStatus: true});
};
Use async & await
onHandlePrint = async (pdf) => {
await this.setState({pdf}, () => {
this.setState({pdfStatus: true})
});
};
I have an app that needs to be able to use two drawer navigators, one on the left and on on the right side of the header.
I am at the point where I can get both drawers to open with the slide gesture, however I need to be able to open it programmatically. I have found the navigation.openDrawer() function only works with one of the drawers and not the other because it is only able to use one of the navigation props (whichever comes first) from my drawer navigators.
Below are my rendering functions:
const LeftStack = createStackNavigator(
{
LeftDrawerStack
},
{
navigationOptions: ({navigation}) => ({
headerLeft: (
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon style={{marginLeft: 10}} name='menu'/>
</TouchableOpacity>
)
})
}
);
const RightStack = createStackNavigator(
{
RightDrawerStack
},
{
navigationOptions: ({navigation}) => ({
headerRight: (
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon style={{marginRight: 10}} name='ios-bulb'/>
</TouchableOpacity>
)
})
}
);
export const RouteStack = createStackNavigator(
{
screen: LoginScreen,
navigationOptions: ({navigation}) => ({
header: null
}),
LeftStack,
RightStack
}
);
and here are my drawer routes:
export const LeftDrawerStack = createDrawerNavigator(
{
Dashboard: {
screen: DashboardScreen
},
History: {
screen: HistoryScreen
},
Privacy: {
screen: PrivacyPolicyScreen
},
TermsAndConditions: {
screen: TermsAndConditionsScreen
}
}, {
initialRouteName: 'Dashboard',
contentComponent: LeftDrawerScreen
}
);
export const RightDrawerStack = createDrawerNavigator(
{
LeftDrawerStack,
Settings: {
screen: SettingsScreen
}
}, {
drawerPosition: 'right',
contentComponent: RightDrawerScreen
}
);
Here is a picture of what I have the navigation looking like so far, however both of the hamburger menus are opening up the same menu on the right instead of one menu on their respective sides.
I may be missing some parts but I will be sure to post more info if I forgot any!
I was able to do this with the following setup (try to make changes to your structure to be like this):
const LeftDrawer = createDrawerNavigator(
{
LeftDrawer: MyStackNavigator,
},
{
getCustomActionCreators: (route, stateKey) => { return { toggleLeftDrawer: () => DrawerActions.toggleDrawer({ key: stateKey }) }; },
drawerPosition: 'left',
contentComponent: MyLeftDrawer
}
);
const RightDrawer = createDrawerNavigator(
{
Drawer: LeftDrawer,
},
{
getCustomActionCreators: (route, stateKey) => { return { toggleRightDrawer: () => DrawerActions.toggleDrawer({ key: stateKey }) }; },
drawerPosition: 'right',
contentComponent: MyRightDrawer
}
);
export const RootNavigator = createStackNavigator(
{
Login: Login,
Home: RightDrawer
},
{
initialRouteName: 'Login',
navigationOptions: { header: null, gesturesEnabled: false }
}
);
The key is getCustomActionCreators. It allows you to call the function from any screen in MyStackNavigator like this: this.props.navigation.toggleLeftDrawer();.
This is solution I do
Step 1: Create two drawer navigation and nest them together
Step 2: Store first drawer navigation in other place (Singleton)
// Drawer 1
import ContentLeftMenu from '#components/ContentLeftMenu';
import { createDrawerNavigator } from '#react-navigation/drawer';
import * as React from 'react';
import DrawerCart from './DrawerCart';
import { setNavigationDrawerHome } from './RootNavigation';
import { isTablet } from 'react-native-device-info';
import MainStack from './MainStack';
const Drawer = createDrawerNavigator();
export default function DrawerHome() {
return (
<Drawer.Navigator
initialRouteName="DrawerCart"
drawerContent={() => <ContentLeftMenu />}
screenOptions={({ navigation, route }) => {
setNavigationDrawerHome(navigation)
}}
>
<Drawer.Screen name="DrawerCart" component={isTablet ? DrawerCart : MainStack} />
</Drawer.Navigator>
);
}
// Drawer 2
import CartScreen from '#features/cart/CartScreen';
import { createDrawerNavigator } from '#react-navigation/drawer';
import * as React from 'react';
import MainStack from './MainStack';
const Drawer = createDrawerNavigator();
export default function DrawerCart(props) {
return (
<Drawer.Navigator
initialRouteName="MainStackCart"
drawerContent={() => <CartScreen />}
drawerPosition='right'
>
<Drawer.Screen name="MainStackCart" component={MainStack} />
</Drawer.Navigator>
);
}
// set navigation of drawer 1 with navigationDrawerHome
let navigationDrawerHome = null
export const setNavigationDrawerHome = (navigation) => {
navigationDrawerHome = navigation
}
export const getNavigationDrawerHome = () => {
return navigationDrawerHome
}
And when I use in Mainstack I when open Drawer 1 in left I use navigationDrawerHome, with drawer 2 just use navigation props
const Stack = createStackNavigator();
function MainStack() {
const navigationDrawerHome = getNavigationDrawerHome()
return (
<Stack.Navigator
screenOptions={({ navigation, route }) => ({
headerLeft: () => <HeaderLeft />,
headerTitleAlign: 'center',
})}>
<Stack.Screen
name="home_user_tab"
component={HomeUserTabs}
options={({ navigation, route }) => ({
headerLeft: () => {
return (
<ButtonIcon
onPress={() => navigationDrawerHome.openDrawer()}
style={styles.buttonLeft}
icon={images.ic_left_menu}
/>
)
},
headerTitle: () => <LogoHeader />,
headerRight: () => (<HeaderCardSearch
navigation={navigation}
/>),
// header: null
})}
/>
</Stack.Navigator>
);
}
const HeaderCardSearch = (props) => {
const { navigation } = props;
return (
<View style={styles.headerRight}>
<ButtonIcon
onPress={() => navigation.openDrawer()}
style={styles.buttonCart}
icon={images.ic_cart}
/>
<ButtonIcon
onPress={() => navigate('search')}
style={styles.buttonSearch}
icon={images.ic_search}
/>
</View>
)
};
I managed to programmatically open both drawers this way:
// Opens left drawer
this.props.navigation.openDrawer();
// Opens right drawer
this.props.navigation.dangerouslyGetParent().dangerouslyGetParent().openDrawer();
And my navigation container looks like this:
static container = (): NavigationContainer => createDrawerNavigator({
right: createDrawerNavigator({
left: DeviceControlContainer
}, {
contentComponent: HistoryDrawerContainer,
overlayColor: drawerOverlayColor()
})
}, {
drawerPosition: 'right',
overlayColor: drawerOverlayColor(),
navigationOptions: DeviceControlScreen.navigationOptions,
contentComponent: DeviceControlDrawer
});
Got inspired from Anh Devit solution, here is mine. I'm using a header bar across the app without a Stack Navigator, so had to go a slightly different route.
For my RootNavigator, I put my Header above my first drawer. I use useState so when I pass down the navigation objects as props, the AppHeader can re-render correctly when the Drawers have been initialized.
import React, { useState } from "react";
import { NavigationContainer } from "#react-navigation/native";
import LeftDrawer from "./LeftDrawer";
import AppHeader from "./AppHeader";
const RootNavigator = () => {
const [navigationLeftDrawer, setNavigationLeftDrawer] = useState(null);
const [navigationRightDrawer, setNavigationRightDrawer] = useState(null);
return (
<NavigationContainer>
<AppHeader
navigationLeftDrawer={navigationLeftDrawer}
navigationRightDrawer={navigationRightDrawer}
/>
<LeftDrawer
navigationLeftDrawer={(navigation) =>
setNavigationLeftDrawer(navigation)
}
navigationRightDrawer={(navigation) =>
setNavigationRightDrawer(navigation)
}
/>
</NavigationContainer>
);
};
export default RootNavigator;
Here I'm creating my LeftDrawer, in which the RightDrawer will be nested. Notice setting the navigationLeftDrawer when the Drawer inits. Also, navigationRightDrawerhas to be passed down as a param and not a prop.
import React, { useContext } from "react";
import {
createDrawerNavigator,
DrawerContentScrollView,
DrawerItem,
} from "#react-navigation/drawer";
import RightDrawer from "./RightDrawer";
const LeftDrawer = ({ navigationLeftDrawer, navigationRightDrawer }) => {
const Drawer = createDrawerNavigator();
return (
<Drawer.Navigator
screenOptions={({ navigation }) => {
navigationLeftDrawer(navigation);
}}
drawerContent={(props) => {
const { navigate } = props.navigation;
return (<RightDrawerItems />)
);
}}
>
<Drawer.Screen
name="AccountDrawer"
component={RightDrawer}
initialParams={{ navigationRightDrawer }}
/>
</Drawer.Navigator>
);
export default LeftDrawer;
Likewise in RightDrawer, passing up navigationRightDrawer with the right navigation object.
import React from "react";
const RightDrawer = ({ route }) => {
const Drawer = createDrawerNavigator();
const { navigationRightDrawer } = route.params;
return (
<Drawer.Navigator
drawerPosition="right"
screenOptions={({ navigation }) => {
navigationRightDrawer(navigation);
}}
drawerContent={(props) => {
const { navigate } = props.navigation;
return ( <RightDrawerItems />);
}}
>
{userAuth ? (
<>
<Drawer.Screen name={routes.dashboard} component={DashboardScreen} />
</>
) : (
<Drawer.Screen name={routes.home} component={HomeStack} />
)}
<Drawer.Screen name={routes.about} component={AboutScreen} />
<Drawer.Screen name={routes.language} component={LanguageScreen} />
</Drawer.Navigator>
);
};
export default RightDrawer;
Finally, the AppHeader :
import React, { useContext } from "react";
const AppHeader = ({ navigationRightDrawer, navigationLeftDrawer }) => {
if (navigationRightDrawer == null || navigationLeftDrawer == null)
return null;
return (
<View style={styles.container}>
<Icon
name="menu"
color={colors.white}
onPress={() => {
navigationRightDrawer.closeDrawer();
navigationLeftDrawer.toggleDrawer();
}}
/>
{userAuth ? (
<TouchableWithoutFeedback
onPress={() => {
navigationLeftDrawer.closeDrawer();
navigationRightDrawer.toggleDrawer();
}}
>
<Image
style={styles.image}
source={{ uri: userProfile.profilePicture }}
/>
</TouchableWithoutFeedback>
) : (
<Icon
name="person"
color={colors.secondary}
onPress={() => navigationLeftDrawer.navigate(routes.login)}
/>
)}
</View>
);
};
export default AppHeader;