ReactNative navigation 5 - how to obtain `navigation.state.params` (upgrade from 1.x) - react-navigation

Trying to update old react navigation 1.x to current version 5.x.
I need to display different tab icon depend on what value is in navigation.state.params, that were used in 1.x version. This value is set in one of the screen using navigation.dispatch(data).
this is simplified code used with navigation 1.x:
import {TabBarBottom, TabNavigator} from 'react-navigation';
import {MyIcon, AnotherIcon} from './icons.js';
export default TabNavigator({
Home: {screen: HomeRouter},
Profile: {screen: ProfileRouter},
}, {
navigationOptions: ({navigation}) => ({
tabBarIcon: ({focused}) => {
const {routeName, params} = navigation.state;
...
if (params.data === 1) {
return <AnotherIcon />
}
...
return <MyIcon />;
},
}),
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
});
how need to be change to work with React navigation v.5.x? or all I could do is to use React.Context?

You can get the params using the new hook useRoute available in v5.x
Documentation here

Related

Is it possible to use the layout in the _app.jsx component with next-i18next?

To create a site, I use nextjs, when creating pages, I took the general layout with the header and footer into a separate hoc component and wrapped the page components in the file with it _app.jsx:
function App({ Component, ...rest }) {
const { store, props } = wrapper.useWrappedStore(rest)
return (
<Provider store={store}>
<Layout>
<Component {...props.pageProps} />
</Layout>
</Provider>
)
}
Everything worked fine until localization became a problem, after using the next-18next library for translations and adding serverSideTranslations, two errors began to appear on each page:
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
frontend-node_1 | TypeError: Cannot read properties of undefined (reading 'label')
frontend-node_1 | at DropdownSwitcher (webpack-internal:///./src/components/header/translation/DropdownSwitcher.jsx:45:36)
frontend-node_1 | at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5658:16)
frontend-node_1 | at renderIndeterminateComponent (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5731:15)
frontend-node_1 | at renderElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5946:7)
frontend-node_1 | at renderMemo (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5868:3)
frontend-node_1 | at renderElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6011:11)
frontend-node_1 | at renderNodeDestructiveImpl (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6104:11)
frontend-node_1 | at renderNodeDestructive (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6076:14)
frontend-node_1 | at renderNode (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6259:12)
frontend-node_1 | at renderHostElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5642:3)
The error with "label" occurs because the i18n object is empty on the server:
const DropdownSwitcher = () => {
const { i18n } = useTranslation()
const currentLanguage = useMemo(() => { // language as undefined
return LANGUAGES.find((item) => item.language === i18n.language)
}, [i18n.language])
....
But everything is fine on the client and there are no errors. What could be the reason and how to fix it, since the App itself from the _app.jsx file is wrapped in appWithTranslation from next-i18next.
Therefore, two questions arise, how to fix react-i18next:: You will need to pass in an i18next instance by using initReactI18next and why there is no i18n object on the server?
I moved the layout to the level of the page itself, removing it from _app.js, but for some reason, then something, useEffect() is repeated in the header, although the header component has not changed in any way and bringing the layout to the level of _app.jsx fixes it
If there is not enough information or you need a visual example, I will try to create a small program that demonstrates this with open source. Please write in a comment.
I solved my problem, but I forgot to provide an answer here, but I noticed that someone also has this problem, so I will try to help people who come across this post, although it is relevant only for nextjs version 12, since with the appearance of version 14, the structure there has improved a lot with as I think there should be no more questions like mine.
1. Rendering the layout
In the official doc, there is a whole section that describes how to correctly divide the layout so that it works according to the SPA type.
pages/index.jsx
// pages/index.jsx
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
export default function Page() {
return (
/** Your content */
)
}
Page.getLayout = function getLayout(page) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
pages/_app.js
// pages/_app.js
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
This component method approach is much better than using its direction in _app.jsx because you can extend or replace them and not make a crude monolith, example how I used it:
// pages/ingex.jsx
function HomePage() {
return (
<HomeLayout>
<Main />
</HomeLayout>
)
}
HomePage.getLayout = (page) => <MainLayout>{page}</MainLayout>
// pages/about-us.jsx
const AboutUsPage = () => {
return (
<>
<HomeLayout>
<AboutUs />
</HomeLayout>
</>
)
}
AboutUsPage.getLayout = (page) => (
<MainLayout withNav>
<LayoutContext.Consumer>
{({ device }) => device.isMobile && <NavigationMobile />}
</LayoutContext.Consumer>
{page}
</MainLayout>
)
With this approach, react still works like a spa and a similar page to about-us, which will also have NavigationMobile, will simply compare it.
2. Error with next-i18next
The whole point was that the next-i18next library was configured incorrectly in the first place (more precisely, it needed to be corrected). In order to configure everything correctly, I had to do the following:
- Move the folder with translation files to the public folder. This is necessary so that the library config, which we will configure a little below, can see the translation files and interact with them
- Configure next-i18next.config.js to work with the client. Here is an example setup with some comments. And also a link to the documentation, and some other resources I found while setting up.
next-i18next.config.js
const path = require('path')
const LANGUAGES = ['en', 'pl', 'uk']
const DEFAULT_LANGUAGE = 'en'
// if it is the server, then the full path, if the client, then the relative path.
const localePath =
typeof window === 'undefined' ? path.resolve('public', 'translation') : '/public/translation'
module.exports = {
i18n: {
defaultLocale: DEFAULT_LANGUAGE,
locales: LANGUAGES,
fallbackLng: LANGUAGES,
nsSeparator: '::',
keySeparator: '::',
// How to use libraries for i18next like LanguageDetector
use: [require('i18next-intervalplural-postprocessor')],
serializeConfig: false,
},
localePath: localePath,
}
- Configure next-i18next in the _app.jsx file. Here everything is as described in the documentation.
import { appWithTranslation } from 'next-i18next'
import nextI18NextConfig from '../../next-i18next.config'
function App({ Component, ...rest }) {
const { store, props } = wrapper.useWrappedStore(rest)
const getLayout = Component.getLayout || ((page) => page)
//WARNING!!! You don't have to have your own i18next initialization like i18next.use(LanguageDetector).use(intervalPlural).init({ detection: options }) this is all done by the next-i18next library
return (
<Provider store={store}>
<AppHOC>{getLayout(<Component {...props.pageProps} />)}</AppHOC>
</Provider>
)
}
export default appWithTranslation(App, nextI18NextConfig)
- You need to pass the config when calling the serverSideTranslations function. To make your life easier, it is better to transfer the implementation of this function to another file, here is an example of how I did it:
// utils/serverSideTranslations.js
import { serverSideTranslations as baseServerSideTranslations } from 'next-i18next/serverSideTranslations'
import { dt } from '../../constants/defaultTranslate'
import { DEFAULT_LANGUAGE } from '../../constants/languages'
import nextI18NextConfig from '../../../next-i18next.config.js'
const serverSideTranslations = async (locale, domains = []) => {
return await baseServerSideTranslations(locale, [...dt, ...domains], nextI18NextConfig, [
DEFAULT_LANGUAGE,
])
}
export default serverSideTranslations
- And finally, use this function on the pages.
import MainLayout from '../components/layouts/MainLayout'
import serverSideTranslations from '../utils/serverSideTranslations'
import HomeLayout from '../components/home/HomeLayout'
import Main from '../components/home/main/Main'
function HomePage() {
return (
<HomeLayout>
<Main />
</HomeLayout>
)
}
HomePage.getLayout = (page) => <MainLayout>{page}</MainLayout>
export const getServerSideProps = async ({ locale }) => {
// Wrapping in Promis.all is not necessary, I use it simply so that if there are any other asynchronous operations, then not to use them through await and not to block each other's work
const [translations] = await Promise.all([
serverSideTranslations(locale, ['home']),
])
return {
props: {
...translations,
},
}
}
export default HomePage
I hope this helped someone, if you have any comments, write in the comments

vuepress local search not showing up [version 2.0.0-beta.22]

I am new to vueppress.
I followed the docs here to create a documentation site. things went well but the search field/input didn't show up. I tried to follow the plugin installation docs here but I got:
I need to install #vuepress/shared-utils
after that I had to install #vue/component-compiler-utils too
but was unable to see the search input. I also tried to add the following to my ./docs/.vuepress/config.ts but still no luck.
plugins: [
[
'#vuepress/plugin-search',
{
searchMaxSuggestions: 10
}
],
]
I don't want to use Algoia search as this is internal documentation.
I had the same issue. Everything was working except the search box was not visible.
The issue was that my ...docs/.vuepress/config.ts was not structured properly. To fix it I followed exactly what the VuePress documentation instructed.
The working config.ts structure
import { defaultTheme } from '#vuepress/theme-default'
import { searchPlugin } from '#vuepress/plugin-search'
module.exports = {
theme: defaultTheme({
...
}),
plugins: [
searchPlugin({
...
})
]
}
Currently I am using VuePress v2.0.0-beta.45
and I used the following to install what I needed:
npm i -D #vuepress/plugin-search#next
npm i -D #vuepress/plugin-register-components#next
Detailed config.ts that is working for me
import { path } from '#vuepress/utils'
import { defaultTheme } from '#vuepress/theme-default'
// Plugins
import { searchPlugin } from '#vuepress/plugin-search'
import { registerComponentsPlugin } from '#vuepress/plugin-register-components'
import navBarItems from './public/navbar'
import sideBar from './public/sidebar'
// SEE: https://v2.vuepress.vuejs.org/reference/default-theme/config.html#config
module.exports = {
// Site Config: https://v2.vuepress.vuejs.org/reference/config.html#site-config
lang: 'en-US',
title: 'Title on Tab and Navbar',
description: '',
// https://v2.vuepress.vuejs.org/reference/default-theme/config.html
theme: defaultTheme({
logo: 'logo-light.png',
logoDark: 'logo-dark.png',
//https://v2.vuepress.vuejs.org/reference/default-theme/config.html#navbar
navbar: navBarItems,
// https://v2.vuepress.vuejs.org/reference/default-theme/config.html#sidebar
sidebar: sideBar
}),
plugins: [
// https://v2.vuepress.vuejs.org/reference/plugin/register-components.html
registerComponentsPlugin({
componentsDir: path.resolve(__dirname, './components')
}),
// https://v2.vuepress.vuejs.org/reference/plugin/search.html#search
searchPlugin({
// getExtraFields: (page) => page.frontmatter.tags,
maxSuggestions: 15,
hotKeys: ['s', '/'],
locales: {
'/': {
placeholder: 'Search',
}
}
})
],
}
Note that I keep my sidebar array and navbar object in different files.
Also I couldn't find any TypeScript reference for the config in VuePress 2x

Spartacus Storefront Multisite I18n with Backend

We've run into some problems for our MultiSite Spartacus setup when doing I18n.
We'd like to have different translations for each site, so we put these on an API that can give back the messages dependent on the baseSite, eg: backend.org/baseSiteX/messages?group=common
But the Spartacus setup doesn't let us pass the baseSite? We can
pass {{lng}} and {{ns}}, but no baseSite.
See https://sap.github.io/spartacus-docs/i18n/#lazy-loading
We'd could do it by overriding i18nextInit, but I'm unsure how to achieve this.
In the documentation, it says you can use crossOrigin: true in the config, but that does not seem to work. The type-checking say it's unsupported, and it still shows uw CORS-issues
Does someone have ideas for these problems?
Currently only language {{lng}} and chunk name {{ns}} are supported as dynamic params in the i18n.backend.loadPath config.
To achieve your goal, you can implement a custom Spartacus CONFIG_INITIALIZER to will populate your i18n.backend.loadPath config based on the value from the BaseSiteService.getActive():
#Injectable({ providedIn: 'root' })
export class I18nBackendPathConfigInitializer implements ConfigInitializer {
readonly scopes = ['i18n.backend.loadPath']; // declare config key that you will resolve
readonly configFactory = () => this.resolveConfig().toPromise();
constructor(protected baseSiteService: BaseSiteService) {}
protected resolveConfig(): Observable<I18nConfig> {
return this.baseSiteService.getActive().pipe(
take(1),
map((baseSite) => ({
i18n: {
backend: {
// initialize your i18n backend path using the basesite value:
loadPath: `https://backend.org/${baseSite}/messages?lang={{lng}}&group={{ns}}`,
},
},
}))
);
}
}
and provide it in your module (i.e. in app.module):
#NgModule({
providers: [
{
provide: CONFIG_INITIALIZER,
useExisting: I18nBackendPathConfigInitializer,
multi: true,
},
],
/* ... */
})
Note: the above solution assumes the active basesite is set only once, on app start (which is the case in Spartacus by default).

Parse iOS Universal Links with Nativescript Angular?

Following the apple documentation and Branch's documentation here, I have set up a working universal link in my Nativescript Angular (iOS) app. But, how do I parse the link when the app opens?
For example, when someone opens the app from the link, I want to have my app read the link so it can go to the correct page of the app.
There is some helpful code in this answer, but I keep getting errors with it. This could be bc the code is written in vanilla JS and I am not translating it into Angular correctly. The use of "_extends" and "routeUrL" both cause errors for me.
And the Nativescript url-handler plugin does not seem to work without further code.
So, after setting up the universal link, and installing the nativescript url-handler plugin, I have entered the following in app.module.ts:
const Application = require("tns-core-modules/application");
import { handleOpenURL, AppURL } from 'nativescript-urlhandler';
declare var NSUserActivityTypeBrowsingWeb
if (Application.ios) {
const MyDelegate = (function (_super) {
_extends(MyDelegate, _super);
function MyDelegate() {
_super.apply(this, arguments);
}
MyDelegate.prototype.applicationContinueUserActivityRestorationHandler = function (application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
this.routeUrl(userActivity.webpageURL);
}
return true;
};
MyDelegate.ObjCProtocols = [UIApplicationDelegate];
return MyDelegate;
})(UIResponder);
Application.ios.delegate = MyDelegate;
}
...
export class AppModule {
ngOnInit(){
handleOpenURL((appURL: AppURL) => {
console.log('Got the following appURL = ' + appURL);
});
}
}
The trouble seems to be mostly with "_extends" and "_super.apply". For example, I get this error:
'NativeScript encountered a fatal error: TypeError: undefined is not an object (evaluating '_extends')
EDIT: Note that the nativescript-urlhandler plugin is no longer being updated. Does anyone know how to parse universal links with Nativescript?
I have figured out a method to get this working:
The general idea is to use the iOS App Delegate method: applicationContinueUserActivityRestorationHandler.
The syntax in the Nativescript documentation on app delegates did not work for me. You can view that documentation here.
This appears to work:
--once you have a universal link set up, following documentation like here, and now you want your app to read ("handle") the details of the link that was tapped to open the app:
EDIT: This code sample puts everything in one spot in app.module.ts. However, most of the time its better to move things out of app.module and into separate services. There is sample code for doing that in the discussion here. So the below has working code, but keep in mind it is better to put this code in a separate service.
app.module.ts
declare var UIResponder, UIApplicationDelegate
if (app.ios) {
app.ios.delegate = UIResponder.extend({
applicationContinueUserActivityRestorationHandler: function(application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
let tappedUniversalLink = userActivity.webpageURL
console.log('the universal link url was = ' + tappedUniversalLink)
}
return true;
}
},
{
name: "CustomAppDelegate",
protocols: [UIApplicationDelegate]
});
}
NOTE: to get the NSUserActivity/Application Delegate stuff to work with typescript, I also needed to download the tns-platforms-declarations plugin, and configure the app. So:
$ npm i tns-platforms-declarations
and
references.d.ts
/// <reference path="./node_modules/tns-platform-declarations/ios.d.ts" />
The above code works for me to be able to read the details of the tapped universal link when the link opens the app.
From there, you can determine what you want to do with that information. For example, if you want to navigate to a specific page of your app depending on the details of the universal link, then I have found this to work:
app.module.ts
import { ios, resumeEvent, on as applicationOn, run as applicationRun, ApplicationEventData } from "tns-core-modules/application";
import { Router } from "#angular/router";
let univeralLinkUrl = ''
let hasLinkBeenTapped = false
if (app.ios) {
//code from above, to get value of the universal link
applicationContinueUserActivityRestorationHandler: function(application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
hasLinkBeenTapped = true
universalLinkUrl = userActivity.webpageURL
}
return true;
},
{
name: "CustomAppDelegate",
protocols: [UIApplicationDelegate]
});
}
#ngModule({...})
export class AppModule {
constructor(private router: Router) {
applicationOn(resumeEvent, (args) => {
if (hasLinkBeenTapped === true){
hasLinkBeenTapped = false //set back to false bc if you don't app will save setting of true, and always assume from here out that the universal link has been tapped whenever the app opens
let pageToOpen = //parse universalLinkUrl to get the details of the page you want to go to
this.router.navigate(["pageToOpen"])
} else {
universalLinkUrl = '' //set back to blank
console.log('app is resuming, but universal Link has not been tapped')
}
})
}
}
You can use the nativescript-plugin-universal-links plugin to do just that.
It has support for dealing with an existing app delegate so if you do have another plugin that implements an app delegate, both of them will work.
Here's the usage example from the docs:
import { Component, OnInit } from "#angular/core";
import { registerUniversalLinkCallback } from "nativescript-plugin-universal-links";
#Component({
selector: "my-app",
template: "<page-router-outlet></page-router-outlet>"
})
export class AppComponent {
constructor() {}
ngOnInit() {
registerUniversalLinkCallback(ul => {
// use the router to navigate to the screen
});
}
}
And the callback will receive a ul (universal link) param that looks like this
{
"href": "https://www.example.com/blog?title=welcome",
"origin": "https://www.example.com",
"pathname": "/blog",
"query": {
"title": "welcome"
}
}
Disclaimer: I'm the author of the plugin.

ionic 2 caching images

I am writing an ionic 2 application, and want to cache images.
After long searching on the web I found these references:
https://gist.github.com/ozexpert/d95677e1fe044e6173ef59840c9c484e
https://github.com/chrisben/imgcache.js/blob/master/js/imgcache.js
I implemented the given solution, but i see that the ImgCache module does not behave as expected - the ImgCache.isCached callback is never called.
Any idea or other good solution for caching images in ionic 2?
======== UPDATE ==========
Here is the directive code I use:
import { Directive, ElementRef, Input } from '#angular/core';
import ImgCache from 'imgcache.js';
#Directive({
selector: '[image-cache]'
})
export class ImageCacheDirective {
constructor (
private el: ElementRef
) {
// init
}
ngOnInit() {
// This message is shown in console
console.log('ImageCacheDirective *** ngOnInit: ', this.el.nativeElement.src);
this.el.nativeElement.crossOrigin = "Anonymous"; // CORS enabling
ImgCache.isCached(this.el.nativeElement.src, (path: string, success: any) => {
// These message are never printed
console.log('path - '+ path);
console.log('success - '+ success);
if (success) {
// already cached
console.log('already cached so using cached');
ImgCache.useCachedFile(this.el.nativeElement);
} else {
// not there, need to cache the image
console.log('not there, need to cache the image - ' + this.el.nativeElement.src);
ImgCache.cacheFile(this.el.nativeElement.src, () => {
console.log('cached file');
// ImgCache.useCachedFile(el.nativeElement);
});
}
});
}
}
In app.nodule.es I do:
import { ImageCacheDirective } from '../components/image-cache-directive/image-cache-directive';
and then in home.html:
<img src="http://localhost/ionic-test/img/timeimg.php" image-cache>
It's late but probably this is the solution:
1. Install cordova FileTransfer:
ionic plugin add cordova-plugin-file-transfer --save
2. Init ImgCache when the deviceready event of cordova fires. In src/app/app.component.ts add these methods (or integrate them with your initializeApp() method - this method comes up with a default project start):
initImgCache() {
// activated debug mode
ImgCache.options.debug = true;
ImgCache.options.chromeQuota = 100 * 1024 * 1024; // 100 MB
ImgCache.init(() => { },
() => { console.log('ImgCache init: error! Check the log for errors'); });
}
initializeApp() {
this.platform.ready().then(() => {
this.initImgCache();
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
StatusBar.styleDefault();
Splashscreen.hide();
});
}
Another option is to use a dedicated cache manager for ionic. instead of implementing everything on your own.
Here are 2 options :
1. A generic cache implementation :https://github.com/Nodonisko/ionic-cache
2. This one is better for images: https://github.com/BenBBear/ionic-cache-src
EDIT:
This is not a "link only" answer.. it tells the user to use a ready made implementations instead of trying to implement from scratch.

Resources