While migrating our Onsen v1 app (based on AngularJS1) to Onsen v2 with React, I bumped into a little problem: implementing logic regarding user authentication (redirecting to login screen if there are no "saved credentials")
Here's how my app is structured...
Entry point is the App.js file, which looks like this:
import React from 'react';
import {Navigator} from 'react-onsenui';
import Home from './Home';
import Login from './Login';
class App extends React.Component {
renderPage(route, navigator) {
route.props = route.props || {};
route.props.key = route.comp.displayName;
route.props.navigator = navigator;
return React.createElement(route.comp, route.props);
}
render() {
return (
<Navigator
initialRoute={{comp: Home}}
renderPage={this.renderPage}
/>
);
}
}
export default App;
You can see that I have my references for both 'Home' and 'Login' components, however, I want to decide which component to render.
I tried the following: use 'Home' as the initialRoute, and decide in the Home components constructor if we need to go to the Login page.
constructor(props) {
super(props);
this.state = {
isOpen: false
};
const authenticated = GetAuthenticated();
if(!authenticated) {
this.props.navigator.pushPage({comp: Login});
}
}
This simply did not work, the Login page was never shown.
So basically, what I would like to do is to look in the localStorage for user credentials (or any other place for storing these kind of information), and based on this decide whether to load up the 'Home' page or the 'Login' page.
If GetAuthenticated() is a fetch call you need to remember it acts asynchronously. I'd recommend a loading section whilst the fetch is pending and then a decision on the result of the method.
This link is a good example of how you can use componentDidMount to demonstrate pending async calls. Once that is implemented you can go on to do your switch.
DO NOT, and I can't stress this enough, store user credentials in localStorage ever. Never ever.
Related
I'm going through this article and I'm trying to figure out how the persistence is supposed to occur in Option 4. From what I can tell, you'd need to redefine the .getLayout for every page. I'm not sure how the logic for nesting is incorporated into further urls.
Here's the code from the article
// /pages/account-settings/basic-information.js
import SiteLayout from '../../components/SiteLayout'
import AccountSettingsLayout from '../../components/AccountSettingsLayout'
const AccountSettingsBasicInformation = () => <div>{/* ... */}</div>
AccountSettingsBasicInformation.getLayout = page => (
<SiteLayout>
<AccountSettingsLayout>{page}</AccountSettingsLayout>
</SiteLayout>
)
export default AccountSettingsBasicInformation
// /pages/_app.js
import React from 'react'
import App from 'next/app'
class MyApp extends App {
render() {
const { Component, pageProps, router } = this.props
const getLayout = Component.getLayout || (page => page)
return getLayout(<Component {...pageProps}></Component>)
}
}
export default MyApp
For example, say AccountSettingsBasicInformation.getLayout is /settings/, how would I use this template to produce something at /settings/username
P.S. If someone has done something in the past they'd recommend over this, I'm open to ideas.
Yes, you have to redefine the getLayout function to every page. As long as the SiteLayout component stays “unchanged” (eg.no props change) the rendered content in that layout component (not the page content itself) stays persistent. This is because React wont rerender that component.
I used Adam’s article when I was building next.js lib for handlin modal routes. You can check the example folder where you can see I am defining the getLayout property on every page which should be rendered with layout.
Example: https://github.com/svobik7/next-bodies/tree/master/example
I would like to refactor my Next.js webapp to have different pages handle different screens. Currently, I have this component holding several states to know in which screen I'm in. In the jsx section, I'm using {value && ... } to render the right component.
But I feel this is not good design, and won't be maintainable when adding more and more screens.
I would also like to avoid Redux as it is overkill for my project.
I was thinking about persisting data in cookies so I can retrieve them with getInitialProps in every component when rendering a new page, but is there a more elegant way?
I've read about tweaking the _app.js but I'm not sure to understand the consequences of doing so, and how it could help me..
Any suggestion?
When multiple of your pages need to make use of same data, you can make use of Context to store the result. It a good way to make a centralized storage without using complex and more self sufficient libraries like redux
You can implement context inside of _app.js file which must reside inside your root folder. This way next.js treats it as a root wrapper and you would just need to use 1 instance of Context
contexts/appContext
import React from 'react';
const AppContext = React.createContext();
export const AppProvider = AppContext.Provider;
export const AppConsumer = AppContext.Consumer;
export default AppContext;
_app.js
import React from 'react'
import App from 'next/app'
import AppProvider from '../contexts/appContext';
class MyApp extends App {
state={
data:[]
}
render() {
const { Component, pageProps } = this.props;
// You can implement logic in this component to fetch data and update state
return (
<div>
<AppProvider value={this.state.data}> // pass on value to context
<Component {...pageProps} />
</AppProvider>
</div>
)
}
}
export default MyApp
Now further each component can make use of context value by using AppConsumer or using useContext if you use hooks
Please read more about how to use Context here
I am new to React/Redux, and I am working on a React application. I have been learning the basic concepts of Redux such as Store, Actions, Reducers, Middleware and the connect() function.
However, for some of the pages in my application, I want to allow only users that are logged in to be able to access them, otherwise they are redirected back to the home page of the application.
I was wondering what is the best method to be able to achieve this task? I have heard that React Router can be used to do this, but are there any better ways? Any insights are appreciated.
As you suggested the router already, using it this works neatly n safely supposing the user is only set in the redux state, when properly authenticated in the login:
import { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom':
const mapStateToProps = (state) => {
return {
user: state.user
}
}
class MyPageWrapper extends Component {
componentWillMount() {
if(!this.props.user) this.props.history.push('/login');
}
render() {
return (
<div>
<YourPage/>
</div>
);
}
}
export default connect(mapStateToProps)(withRouter(MyPageWrapper));
I am trying to find a way to have my 3rd party Toast component, available in child components. In my example, I have it working with direct children of the app.js. But I'm not sure how to get any grand children to make use of it.
I have a repo at https://github.com/CraigInBrisbane/ReactLearning if that helps. (Never done open source stuff, so not sure if you can edit)
I am trying to use this component:
(https://github.com/jossmac/react-toast-notifications)
In my app.js importing the packages, and then loading the child components with:
<Route exact path="/accounts" component={withToastManager(Accounts)} />
Then (somehow) my child component has access to the toastManager:
const { toastManager } = this.props;
And I can show toast notifications with:
toastManager.add('Login Success', {
appearance: 'success',
autoDismiss: true,
pauseOnHover: false,
});
However, for my NavBar... I have a bit of a structure.
Header is my component that I call from app.js... Header it's self doesn't use the Toast. But it has a child called NavBar, which has a Logout button. When I click Logout, I want to show toast.
But it has no idea what ToastManager is.
I am trying this:
handleLogout() {
const { toastManager } = this.props;
const {pathname} = this.props.location;
this.context.changeAuthenticated(false);
if(pathname !== "/") {
this.props.history.push("/");
}
toastManager.add('You\re now logged out', {
appearance: 'success',
autoDismiss: true,
pauseOnHover: false,
});
}
But toastManager is not an item available to me.
Is there a way I can make any component that will use Toast notifications, be aware of them? I think I need to wrap my navbar in withToastManager, but then all my siblings need to have a reference to ToastManager.
Any guidance would be great.
You should take a look at react's Higher Order Component (HOC) documentation, but basically you want to wrap each component individually that needs the withToastManager prop(s) injected.
In your navbar.js file you'll want to import the withToastManager from 'react-toast-notifications', and wrap the export:
import { toastManager } from 'react-toast-notifications';
...
export default withToastManager(
withRouter(Navbar)
);
This will add the toast manager as a prop that you can access in the component via this.props.toastManager.
A side note, typically you'll also want to use this same pattern of wrapping the export of components that need props/functionality that the HOCs provide instead of elsewhere in the app, like in your router in app.js.
We are building a React Native app that uses redux-persist to store the app state, including the navigation state. I would like for this app to behave like a native app in terms of navigation:
When a native Android app goes into the background, is eventually stopped by the OS and is then moved into the foreground, it will resume in the Activity where the user previously left off. If the same app is killed by the user (or a crash), it will open at the main Activity.
For a RN app, this means that redux-persist should persist and restore the navigation state in the componentWillMount of the app, but only if the app was not killed by the user.
The following code works:
componentWillMount() {
if (global.isRelaunch) {
// purge redux-persist navigation state
}
global.isRelaunch = true;
...
But it looks hackish and I also do not understand why the global scope survives.
What is the proper way to detect whether an RN app was re-opened from the background? (ideally with iOS support)
You should take a look to AppState which is an api that provided by react-native
check this example:
import React, {Component} from 'react'
import {AppState, Text} from 'react-native'
class AppStateExample extends Component {
state = {
appState: AppState.currentState
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
console.log('App has come to the foreground!')
}
this.setState({appState: nextAppState});
}
render() {
return (
<Text>Current state is: {this.state.appState}</Text>
);
}
}
#semirturgay's answer is one way to do detect leaving the app. For Android, it is way better to detect home or recent app button clicks. This is because fragments within your app from other apps like social media or photos will also trigger background state, which you don't want because they are still in the app adding a photo to a profile from the camera etc. You can easily detect home and recent app button clicks on Android with react-native-home-pressed. This library simply exposes the android button events.
First install the library with npm i react-native-home-pressed --save and then link it react-native link. Then rebuild your app and add the following snippet.
import { DeviceEventEmitter } from 'react-native'
class ExampleComponent extends Component {
componentDidMount() {
this.onHomeButtonPressSub = DeviceEventEmitter.addListener(
'ON_HOME_BUTTON_PRESSED',
() => {
console.log('You tapped the home button!')
})
this.onRecentButtonPressSub = DeviceEventEmitter.addListener(
'ON_RECENT_APP_BUTTON_PRESSED',
() => {
console.log('You tapped the recent app button!')
})
}
componentWillUnmount(): void {
if (this.onRecentButtonPressSub) this.onRecentButtonPressSub.remove()
if (this.onHomeButtonPressSub) this.onHomeButtonPressSub.remove()
}
}