HOCs, Context API and Next Pages - server-side-rendering

Its possible to use Hoc with context api inside a next page?
I have a next page generated by SSR, and a HOC privateRoute to validate authorization on this page. But for every access, we have a authorization request and its sound's me like a problem.
My idea is to use contexApi to get data one time, and reuse that on auth private route.
Anyone has a minimal exemple about?
Thanks.

I found a solution.
The problem was to use Context inside react class component.
mport React, { Component } from 'react'
import UserContext from './UserContext'
class HomePage extends Component {
static contextType = UserContext
componentDidMount() {
const user = this.context
console.log(user) // { name: 'Tania', loggedIn: true }
}
for more, see https://www.taniarascia.com/using-context-api-in-react/
render() {
return <div>{user.name}</div>
}
}

Related

Ionic discards changes in global css variable after closing the app

I want to enable my users to set certain global colors when using the app. Therefor I have created a 'dynamicVariables.css' file:
:root {
--my-color: violet;
}
It is imported in 'global.scss' file:
#import "./theme/dynamicVariables.css";
Also, I've added a colorpicker on one page and I can set the --my-color variable fine from there.
onColorChange(data: any) {
document.documentElement.style.setProperty('--my-color', data);
}
Just when closing the app on my device (I've deployed it with ionic capacitor run android), it resets the css variable, because when I run it again the color is back to its default value.
I'm pretty sure, I have a general misconception here and would be grateful for some clarification. I'm generally new to web development and would be grateful for any help.
Thanks in advance.
just like how Mustafa explained in comments, you need to make these changes outside app "runtime" and in the device's memory, that would stay there even after the app (whether web or native) is closed. for example you can use ionic storage and save your data with keys and values same as your css keys, and load it up whenever the app opens.
Thanks to the responds, I was able to solve the problem with the help of Ionic Storage.
First, I created a Storage Service:
import { Storage } from '#ionic/storage-angular';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class StorageService {
private _storage: Storage | null = null;
constructor(private storage: Storage) {
}
async init() {
const storage = await this.storage.create();
this._storage = storage;
}
public set(key: string, value: any) {
this._storage?.set(key, value);
}
public get(key: string) {
return this._storage?.get(key);
}
}
When starting the app, I run the following code in the app.component.ts
async ngOnInit() {
await this.storageService.init();
let storedPathologicalColor = await this.storageService.get('--my-color');
if (storedPathologicalColor == null)
document.documentElement.style.setProperty('--my-color', getComputedStyle(document.documentElement).getPropertyValue('--my-color'))
else
document.documentElement.style.setProperty('--my-color', storedPathologicalColor);
}
It is important to init() the service from outside. When setting a new css variable, I also set a new key/value pair to the Storage.
Thanks again.

xamarin.forms authorization without 3rd party libraries

I'm trying to make authorization in xamarin forms and i dont know how to structure it. I'm using MVVM and for my authorization i wanna use JWT . I want to check if the token is valid then go to certain page . when i put the validation code inside the onappearing method the page is still visible for a very small amount of time and when i put it inside the constructor of the page the navigation doesn't work. How should this authorization should be done?(should i make another transition page with something like an activity indicator ?)
this is the code i use for the token validation
public async Task CheckIfUserIsLoggedIn()
{
if (!await ValidateToken())
{
await _dependencyService.Get<INavigationService>().PushAsync(ViewNames.LOGINVIEW);
}
}
when i put it inside the constructor of the page the navigation
doesn't work.
You can call the push service like this in Main thread:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new myViewModel();
CheckIfUserIsLoggedIn();
}
public async void CheckIfUserIsLoggedIn()
{
if (!await ValidateToken())
{
Device.BeginInvokeOnMainThread(async () => {
await _dependencyService.Get<INavigationService>().PushAsync(ViewNames.LOGINVIEW);
});
}
}
}

SwiftUI : how to access UINavigationController from NavigationView

I am developing an app using SwiftUI. The app is based around a NavigationView.
I am using a third-party framework that provides UIKit components and the framework has not been updated to support SwiftUI yet.
One framework method is expecting a parameter of type UINavigationController
How can I supply this framework the NavigationController created by SwiftUI ? Or how can I create a UINavigationController that will replace SwiftUI's default ?
I read https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit and https://sarunw.com/posts/uikit-in-swiftui but these seems to address another question : they explain how to use UIKit components in a SwiftUI app. My problem is the other way around, I want to use SwiftUI App and access underlying NavigationController object.
[UPDATE]
The code implementing my solution is available from this workshop : https://amplify-ios-workshop.go-aws.com/30_add_authentication/20_client_code.html#loginviewcontroller-swift
Thanks to Yonat's explanation I understood how to do this and here is my solution, hoping it will help others.
Part 1 : The UI View Controller that will be used from Swift UI. It calls a third-party authentication library, passing the UINavigationControler as parameter. The UINavigationController is an empty view, just there to allow the third-party authentication library to have a Navigation Controller to pop up the Login Screen.
struct LoginViewController: UIViewControllerRepresentable {
let navController = UINavigationController()
func makeUIViewController(context: Context) -> UINavigationController {
navController.setNavigationBarHidden(true, animated: false)
let viewController = UIViewController()
navController.addChild(viewController)
return navController
}
func updateUIViewController(_ pageViewController: UINavigationController, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject {
var parent: LoginViewController
init(_ loginViewController: LoginViewController) {
self.parent = loginViewController
}
}
func authenticate() {
let app = UIApplication.shared.delegate as! AppDelegate
let userData = app.userData
userData.authenticateWithDropinUI(navigationController: navController)
}
}
Part 2 : The Swift UI View is displaying the (empty) UINavigationControler and overlays a SwiftUI view on top of it.
import SwiftUI
struct LandingView: View {
#ObservedObject public var user : UserData
var body: some View {
let loginView = LoginViewController()
return VStack {
// .wrappedValue is used to extract the Bool from Binding<Bool> type
if (!$user.isSignedIn.wrappedValue) {
ZStack {
loginView
// build your welcome view here
Button(action: { loginView.authenticate() } ) {
UserBadge().scaleEffect(0.5)
}
}
} else {
// my main app view
// ...
}
}
}
}
I don't think you can do that right now. Looking at the view debugger for NavigationView I get the image below.
So it seems to you will have to go the other way around:
Start with a UINavigationController, and wrap the SwiftUI view(s) in UIHostingController.
I tried to do the same thing because I wanted to make the interactivePopGestureRecognizer work on the whole view.
I managed to access the current navigation controller using an UINavigationController extension and overriding viewDidAppear, checking if the interactivePopGestureRecognizer was enabled and changed it ( https://stackoverflow.com/a/58068947/1745000)
At the end my effort was pointless. When the navigation view presented the DetailHostingController, it toggled off interactivePopGestureRecognizer.isEnabled!
The hosting view via topViewController.view does contain a gesture recogniser of private type SwiftUI.UIGestureRecognizer. No targets are set though...
Embedding a traditional UINavigationController may also be preferred because navigation view's own pop gesture isn't cancellable (if you drag the view a little bit and stop, it snaps back and then dismiss the detail view.

React Native persisting user data

In my app I have 2 components (Page and Home).
Home extends Page like so:
export default class Home extends Page
and Page just extends Component.
In Page I have a method to get user data from AsyncStorage
async getUser() {
// get from storage
this.setState({user});
}
The above is called on the constructor of Page.
The problem I have is that Home has a method on componentWillMount that relies on this.state.user. Obviously this isn't going to work since the getUser method is async.
Is there a way I can get the user information and only call specific methods once I have that info?
export default class Page extends Component {
async getUser() {
// get from AsyncStorage
this.setState({user});
}
componentWillMount() {
this.getUser();
}
}
export default class Home extends Page {
async foo(user_id) {
this.setState({something});
}
componentWillMount() {
this.foo(this.state.user);
}
render() {
return <Text>{this.state.something}</Text>;
}
}
Is there a way I can get the user information and only call specific
methods once I have that info?
Not in a general sense, but of course there are some solutions. What this comes down to is that you have an async data dependency. You need to write the code in such a way that a call to dependent functions is only made after the data becomes available.
Perhaps the easiest way to do this is to use componentWillUpdate instead of componentDidMount and check if you are receiving the required data:
componentWillUpdate(nextProps, nextState) {
if (nextState.user != null && this.state.user !== nextState.user) {
this.prepareForUser(nextState.user);
}
}
(Note that you can't setState directly in componentWillUpdate, but since your method is async it won't happen until later. You can use componentDidUpdate to chain another setState call.)
Another option (which I like to use) is to use composition instead of inheritance. This makes the life-cycle easier to control through rendering: the parent can only render a child when all the child's dependent data is loaded, then the child does not need to worry about any timing issues related to the initial loading of data and can do whatever it wants from its componentDidMount:
class UserContainer extends Component {
state = {};
componentDidMount() {
this.getUser();
}
async getUser() {
// get user from async store
this.setState({user});
}
render() {
return (
<div>
{ this.state.user ? <UserView user={this.state.user} /> : <Spinner /> }
</div>
);
}
}
class UserView extends Component {
componentDidMount() {
this.props.user != null
}
}
This is pretty much the "container component" pattern.

How to define global variables in Angular 2 in a way that I can use them for property binding in templates?

In my Angular 2 (beta 14) application I need to keep track of user login status in order to hide/display certain elements.
The issue I'm getting is that property binding is not working the way I did as follows.
I created a class to store and update global variables:
app-global.ts
import {Injectable} from "angular2/core";
#Injectable() export class AppGlobals {
// use this property for property binding
public isUserLoggedIn: boolean = false;
setLoginStatus(isLoggedIn){
this.isUserLoggedIn = isLoggedIn;
}
getLoginStatus(){
return this.isUserLoggedIn;
} }
In the login component I import AppGlobals
export class LoginComponent {
constructor(private _appGlobals: AppGlobals) { }
and set login state by
this._appGlobals.setLoginStatus(true);
In another component I inject AppGlobals as I do in LoginComponent
I define a class (component)'s property
isLoggedIn: boolean = this._appGlobals.isUserLoggedIn; // I also tried by using the getter instead of the public property (see above)
which I then use in the component's template to show/hide a certain element:
<!-- here I also tried with {{!isLoggedIn}} but results in a syntax error whereas using [(hidden)] instead of [hidden] changes nothing -->
<div id="some-element" [hidden] = "!isLoggedIn">
Finally, the binding works but there is no update (this component is part of AppComponent template and shown in every page) when another component (e.g. LoginComponent) sets the login status.
EDIT I tried to apply Gunter's answer but I get the following errors:
app/app-globals.ts(10,54): error TS2346: Supplied parameters do not match any signature of call target.
app/app-globals.ts(13,29): error TS2339: Property 'emit' does not exist on type 'BehaviorSubject<boolean>'.
Error at line 10 comes from [SOLVED]
public isUserLoggedIn:BehaviorSubject = new BehaviorSubject().startWith(false);
and it's apparently caused by BehaviorSubject expecting 1 parameter
Error at line 13 comes from
this.isUserLoggedIn.emit(isLoggedIn);
and it's apparently cause by a non-existing emit method.
Also, I don't understand how shall I use AppGlobals so that the property binding auto updates in another component (see last example before EDIT)
Further, in LoginComponent I replaced isLoggedIn boolean type with BehaviorSubject because isUserLoggedIn has type BehaviorSubject in AppGlobals
but
this._appGlobals.isUserLoggedIn.subscribe(value => this.isLoggedIn = value);
returns a TypeError:
Assigned expression type boolean is not assignable to type BehaviorSubject
isLoggedIn: boolean = this._appGlobals.isUserLoggedIn;
is a one-time action that copies the value at the time when this line is executed. If you want subsequent changes to be propagated use observables
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/startWith';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class AppGlobals {
// use this property for property binding
public isUserLoggedIn:BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
setLoginStatus(isLoggedIn){
this.isUserLoggedIn.next(isLoggedIn);
}
}
and use it like:
export class LoginComponent {
constructor(private _appGlobals: AppGlobals) {
this._appGlobals.isUserLoggedIn.subscribe(value => this.isLoggedIn = value);
}
See also https://stackoverflow.com/a/35568924/217408
had a similar need, ended up implementing this via simple getter - and it binds the property in the template (--> changes propagate). less code.
you can set the global var either directly or implement setters/getters in the Globals class.
globals.ts
export class Globals{
public static server_call_in_progress:boolean = true;
}
app.component.ts
import {Globals} from "./shared/globals";
export class AppComponent{
constructor(){}
ngOnInit(){}
get server_call_in_progress(){
return Globals.server_call_in_progress;
}
}
app.component.html
<div *ngIf="server_call_in_progress">
<div class="loader"></div>
</div>

Resources