@karrotframe/navigator-plugin
한국어
extensible plugin for @karrotframe/navigator
- 🧩 lifecycle hooks to control event for @karrotframe/navigator
- 📭 apply middleware to lifecycle hooks
- 🖇️ independent state manage of plugin to support various scenarios
Install
$ yarn add @karrotframe/navigator-plugin
Create simple plugin
Define plugin
plugins/index.ts
import type {
PluginType,
NavigatorPluginType,
} from '@karrotframe/navigator-plugin'
const pluginName = 'SimplePlugin'
export const useSimplePlugin = (): PluginType & {
pluginName: string
} => {
return pluginName
}
export const simplePlugin: NavigatorPluginType = {
name: pluginName,
executor: useSimplePlugin,
}
Apply simple plugin
App.tsx
import { simplePlugin } from './plugins'
const App: React.FC = () => {
return (
<Navigator plugins={[simplePlugin]}>
<Screen path="/" component={Main} />
</Navigator>
)
}
Main.tsx
import { useSimplePlugin } from './plugins'
const Main: React.FC = () => {
const { pluginName } = useSimplePlugin()
return (
<div>
<span>Main</span>
<span>{pluginName}</span>
</div>
)
}
Create plugin with lifecycle hook of Navigator
Define plugin
plugins/index.ts
import type {
NavigatorPluginType,
PluginType,
} from '@karrotframe/navigator-plugin'
export const loggerPlugin: NavigatorPluginType = {
name: 'loggerPlugin',
executor: (): PluginType => ({
lifeCycleHooks: {
onPoppedWithData: async ({ from, data }) => {
console.log('from: ', from)
console.log('data: ', data)
},
},
}),
}
onPoppedWithData
hook is called when user calls pop().send(data)
.
This hook takes from
and data
arguments as you know.
You could also check other lifecycle hooks like onPoppedWithData
.
Create plugin to control state from plugin
Define plugin
plugins/index.ts
import React, { createContext, useContext, useState, useMemo } from 'react'
import type {
NavigatorPluginType,
PluginType,
} from '@karrotframe/navigator-plugin'
export const ContextDataPlugin = createContext<{
data: any
setData: (data: any) => void
}>(null as any)
export const DataPluginProvider: React.FC = (props) => {
const [data, setData] = useState<any>(null)
return (
<ContextDataPlugin.Provider value={{ data, setData }}>
{props.children}
</ContextDataPlugin.Provider>
)
}
export const useDataPlugin = (): PluginType & {
dataFromNextPage: (params: { from: string }) => any
} => {
const context = useContext(ContextDataPlugin)
return useMemo(() => {
return {
lifeCycleHooks: {
onPoppedWithData: async ({ from, data }) => {
context.setData({ [from]: data })
},
},
dataFromNextPage: ({ from }: { from: string }) => context?.data?.[from],
}
}, [context])
}
export const dataPlugin: NavigatorPluginType = {
name: 'dataPlugin',
provider: DataPluginProvider,
executor: useDataPlugin,
}
You could use context api to control state of plugin from component.
In this example, this Provider would wrap Navigator
to pass states.
And then you could access states of plugin from component.
Apply plugin
App.tsx
import { dataPlugin } from './plugins'
const App: React.FC = () => {
return (
<Navigator plugins={[dataPlugin]}>
<Screen path="/" component={Main} />
<Screen path="/other" component={Other} />
</Navigator>
)
}
Main.tsx
import { useDataPlugin } from './plugins'
const Main: React.FC = () => {
const { dataFromNextPage } = useDataPlugin()
const result = useMemo(
() => dataFromNextPage({ from: '/other' }),
[dataFromNextPage]
)
return (
<div>
<span>Main</span>
<span>{result}</span>
</div>
)
}
Apply middleware to lifecycle hook
Define plugin
import type {
BeforePushType,
NavigatorPluginType,
PluginType,
} from '@karrotframe/navigator-plugin'
import { composeMiddlewares } from '@karrotframe/navigator-plugin'
const filterPathMiddleware = async (
ctx: BeforePushType,
next: () => Promise<BeforePushType | void>
): Promise<BeforePushType | void> => {
if (ctx.to === 'not valid') {
await next({
...ctx,
to: 'valid',
})
}
await next()
}
const loggerMiddleware = async ({
to,
}: BeforePushType): Promise<BeforePushType | void> => {
console.log('to: ', to)
}
export const pluginWithMiddleware: NavigatorPluginType = {
name: 'pluginWithMiddleware',
executor: (): PluginType => ({
lifeCycleHooks: {
beforePush: composeMiddlewares<BeforePushType>([
filterPathMiddleware,
loggerMiddleware,
]),
},
}),
}
You could call next function as second argument of callback function when you use composeMiddlewares
for hook.
You could control lifecycle hook by stages with middleware.
And next function could take custom value to pass this value to next middleware.
Lifecycle Hooks
beforePush
This hook calls callback function before push()
to | String | Route path by push() . | "/product" |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
screenInstancePtr | number | The pointer to indicate current screen | 3 |
options | Options | push, replace, pop could be called from callback function | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
onPushed
This hook calls callback function immediately before route event(push).
to | String | Route path by push() . | "/product" |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
screenInstancePtr | number | The pointer to indicate current screen | 3 |
options | Options | push, replace, pop could be called from callback function | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
beforeReplace
This hook calls callback function before replace()
to | String | Route path by replace() | "/account" |
options | Options | push, replace, pop could be called from callback function | |
onReplaced
This hook calls callback function immediately before route event(replace).
to | String | Route path by replace() | "/account" |
options | Options | push, replace, pop could be called from callback function | |
beforePop
This hook calls callback function before pop()
from | String | Route path by pop() | "/product/1" |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
screenInstancePtr | number | The pointer to indicate current screen | 3 |
options | Options | push, replace, pop could be called from callback function | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
onPopped
This hook calls callback function before go back with provided depth(backwards count)
from | String | Route path by pop() | "/product/1" |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
screenInstancePtr | number | The pointer to indicate current screen | 3 |
options | Options | push, replace, pop could be called from callback function | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
onPoppedWithData
This hook calls callback function when pop().send()
is called.
from | String | Route path by pop() | "/product/1" |
data | object | The data as argument of pop().send() | { name: 'John Doe' } |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
screenInstancePtr | number | The pointer to indicate current screen | 3 |
options | Options | push, replace, pop could be called from callback function | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
beforeRegisterScreen
Each screen is registered during render by ReactDOM,
if Screen
components are defined as child component of Navigator
component.
This hook calls callback function before register this screens.
screen | IScreen | The object that contains screen info as props for Screen component | |
screens | IScreen[] | Array that contains each screen info as child components of Navigator component | |
screen
{
id: '/main',
path: '/main',
component: Main
}
onRegisterScreen
Each screen is registered during render by ReactDOM,
if Screen
components are defined as child component of Navigator
component.
This hook calls callback function after register this screens.
screen | IScreen | The object that contains screen info as props for Screen component | |
screens | IScreen[] | Array that contains each screen info as child components of Navigator component. This array also contains screen object above. | |
screen
{
id: '/main',
path: '/main',
component: Main
}
beforeInsertScreenInstance
ScreenInstance would be instantiated with screen info which is registered,
when any route event like push
is triggered.
This hook calls callback function before such ScreenInstance is registered.
screenInstance | IScreenInstance | screen instance info from client | |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
ptr | number | The pointer that indicates current screenInstance | 5 |
options | Options | setScreenInstances, setScreenInstancePtr could be called from callback function | |
screenInstances
;[
{
id: 4,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
onInsertScreenInstance
ScreenInstance would be instantiated with screen info which is registered,
when any route event like push
is triggered.
This hook calls callback function just after such ScreenInstance is registered.
screenInstance | IScreenInstance | screen instance info from client | |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
ptr | number | The pointer that indicates current screenInstance | 5 |
options | Options | setScreenInstances, setScreenInstancePtr could be called from callback function | |
screenInstances
;[
{
id: 4,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
beforeMapScreenInstance
You would need to modify properties of screenInstance in specific case with mapper function.
This hook calls callback functions before mapper function that will modify screenInstance is called
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
ptr | number | The pointer that indicates current screenInstance | 4 |
options | Options | mapperScreenInstance could be called in callback function | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
onMapScreenInstance
You would need to modify properties of screenInstance in specific case with mapper function.
This hook calls callback functions right after mapper function that will modify screenInstance is called
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
ptr | number | The pointer that indicates current screenInstance | 4 |
options | Options | mapperScreenInstance could be called in callback function | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
beforeAddScreenInstancePromise
resolve
function of the promise for push
will be declared to an object, screenInstancePromiseMap
,
when push
event is triggered or pop()
event which includes pop().send()
is triggered.
This hook calls callback function before initializing screenInstancePromiseMap by screenInstanceId
with resolve
function
screenInstanceId | string | screenInstanceId which is matched with screenInstancePromise | |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
screenInstancePtr | number | The pointer to indicate current screen | 4 |
screenInstancePromise | IScreenInstancePromise | The object to save resolve function for screenInstance with screenInstanceId | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
onAddScreenInstancePromise
resolve
function of the promise for push
will be declared to an object, screenInstancePromiseMap
,
when push
event is triggered or pop()
event which includes pop().send()
is triggered.
This hook calls callback function right after initializing screenInstancePromiseMap by screenInstanceId
with resolve
function
screenInstanceId | string | screenInstanceId which is matched with screenInstancePromise | |
screenInstances | IScreenInstance[] | Array that contains screen instance info from client | |
screenInstancePtr | number | The pointer to indicate current screen | 4 |
screenInstancePromise | IScreenInstancePromise | The object to save resolve function for screenInstance with screenInstanceId | |
screenInstances
;[
{
id: 2,
screenId: '/product',
nestedRouteCount: 0,
present: false,
as: '/product',
},
]
onMountNavbar
Props of ScreenHelmet
are declared to set up App bar of top from @karrotframe/navigator
.
A navbar should be mounted internally to render this App Bar.
This hook calls callback function when the navbar is mounted.
screenHelmetProps | IScreenHelmetProps | screenInstanceId which is matched with screenInstancePromise | |
screenHelmetProps
{
appendLeft: "button",
appendRight: {
$$typeof: Symbol(react.element)
key: null
props: {children: '', onClick: ƒ}
ref: null
type: "div"
},
closeButtonLocation: "left",
customBackButton: null,
customCloseButton: null,
disableScrollToTop: false,
noBackButton: false,
noBorder: false,
noCloseButton: false,
onTopClick: () => { console.log("hello world"); },
preventSwipeBack: false,
title: "Main",
visible: true,
}
onUnmountNavbar
Props of ScreenHelmet
are declared to set up App bar of top from @karrotframe/navigator
.
A navbar should be mounted internally to render this App Bar.
This hook calls callback function when the navbar is unmounted.
screenHelmetProps | IScreenHelmetProps | screenInstanceId which is matched with screenInstancePromise | |
screenHelmetProps
{
appendLeft: "button",
appendRight: {
$$typeof: Symbol(react.element)
key: null
props: {children: '', onClick: ƒ}
ref: null
type: "div"
},
closeButtonLocation: "left",
customBackButton: null,
customCloseButton: null,
disableScrollToTop: false,
noBackButton: false,
noBorder: false,
noCloseButton: false,
onTopClick: () => { console.log("hello world"); },
preventSwipeBack: false,
title: "Main",
visible: true,
}
Interfaces
type NavigatorPluginType = {
name: string
provider?: React.FC
executor: () => PluginType
}
interface IScreen {
id: string
path: string
Component: React.ComponentType
}
interface IScreenInstance {
id: string
screenId: string
nestedRouteCount?: number
present: boolean
as: string
}
interface IScreenInstancePromise {
resolve: (data: any | null) => void
}
interface IScreenInstancePromise {
title?: React.ReactNode
appendLeft?: React.ReactNode
appendRight?: React.ReactNode
closeButtonLocation?: 'left' | 'right'
customBackButton?: React.ReactNode
customCloseButton?: React.ReactNode
noBorder?: boolean
disableScrollToTop?: boolean
onTopClick?: () => void
visible?: boolean
preventSwipeBack?: boolean
noBackButton?: boolean
noCloseButton?: boolean
}