Hooks
Client-side hooks
renderRoot
This hook is used to define the root component in which all child components of the application will be rendered. The renderRoot hook allows you to change the standard rendering behavior and its structure.
If you have multiple plugins, each of which calls renderRoot, then each subsequent plugin will receive in children what
is returned after the previous plugin's work. This ensures "nesting".
Let's examine this hook in more detail with an example. Suppose we have two plugins. The first plugin will add a red bar with the text "Red block" to our application. It looks like this:
export default createPlugin({
name: 'redPlugin',
client: () => ({
renderRoot ({ children }) {
return <>
<RedBlock />
{children}
</>
}
})
})
const RedBlock = observer(({ children }) => {
return pug`
Div.root(row)
Span Red block
`
styl`
.root
background-color var(--color-bg-error)
width: '100%'
`
})
Next, we'll connect the second plugin:
export default createPlugin({
name: 'greenPlugin',
client: () => ({
renderRoot ({ children }) {
return <>
<GreenBlock />
{children}
</>
}
})
})
const GreenBlock = observer(({ children }) => {
return pug`
Div.root(row)
Span Green block
`
styl`
.root
background-color var(--color-bg-success)
width: '100%'
`
})
It will add a green bar with the text "Green block". At the same time, we will still see the red bar on the screen too.
Using the 'customFormInputs' hook, you can add new types of Input component, which, in turn, is part of the Form component logic.
You can read more about Form here:
https://github.com/startupjs/startupjs/blob/master/packages/ui/components/forms/Form/Form.en.mdx
export default createPlugin({
name: 'userCustomForm',
client: ({ minAge }) => ({
customFormInputs: () => ({
age: observer(({ $value }) => {
function setAge (age) {
if (age < minAge) age = minAge
$value.set(age)
}
return <NumberInput value={$value.get()} onChangeNumber={setAge} />
})
})
})
})
Server-side hooks
api
The 'api' hook defines API routes for handling server requests.
api: (expressApp) => {
// Creating a route for handling GET requests
expressApp.get('/api/data', async (req, res) => {
// Handling GET request
res.json({ message: 'Data received from server' })
})
}
beforeSession
The 'beforeSession' hook is called before session initialization on the server. It provides the ability to perform any operations or set configurations before the server starts processing requests. Sessions on the server are implemented using the express-session package, more details about its capabilities can be read on the official page.
beforeSession: (expressApp) => {
// Example of adding middleware before session initialization
expressApp.use('/api', (req, res, next) => {
// Example of session check before initialization
if (!req.headers['authorization']) {
return res.status(401).json({ error: 'Unauthorized' })
}
// If everything is ok, continue request execution
next()
})
}
afterSession
The 'afterSession' hook is called after session initialization on the server.
afterSession: (expressApp) => {
// Example of adding middleware after session initialization
expressApp.use('/api', (req, res, next) => {
// Output request information
console.log(`Request path: ${req.url}`);
next()
})
}
middleware
The 'middleware' hook defines a middleware handler. This hook can be used to add common operations or checks.
middleware: (expressApp) => {
// Example of adding middleware
expressApp.use('/api', (req, res, next) => {
const lang = req.session.lang
if (lang) req.model.set('_session.lang', lang)
next()
})
}
serverRoutes
The 'serverRoutes' hook is used to add server endpoints that can be used both for HTML rendering and for implementing webhooks and similar functionality.
serverRoutes: (expressApp) => {
// Creating a route for handling GET requests
expressApp.get('/promo-page', (req, res) => {
// Sending HTML as response to request
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Promo Page</title>
</head>
<body>
<!-- some code -->
</body>
</html>
`)
})
}
Isomorphic hooks
In isomorphic hooks, you can place code that will execute both on the server and on the client.
models
The 'models' hook receives models (projectModels) that have been added to the project. Using this hook, you can modify models or add new ones.
Here we'll show how to add a hook in general, and more detailed usage examples can be found here
export default createPlugins({
name: 'addPersonModel',
// Don't forget that models is an isomorphic hook
isomorphic: () => ({
// Hook receives project models (projectModels)
models: (projectModels) => {
return {
...projectModels,
// below for each collection or document, you need to specify an object with the same fields
// that are usually exported from the model file.
// Let's add a persons collection model
persons: {
// in default, specify the ORM class with custom method implementations for this collection model
default: PersonsModel,
// for schema, pass schema
schema
// ... other data, for example, indexes or constants,
// that were exported from the model file
}
}
}
})
})
orm
The 'orm' hook is an advanced hook for overriding Racer, which is used under the hood for ORM implementation. In particular, it can be used if you need to connect plugins for racer (via racer.use()) or extend racer's standard functionality. A Racer instance is passed as an argument to this hook.
You can learn more about Racer in the documentation at https://github.com/derbyjs/racer
// import plugin
import racerPlugin from './myRacerPlugin.js';
orm: (racer) => {
// Connect racer plugin
racer.use(racerPlugin);
}
Plugin creation example
Suppose we have a button in client code that, when clicked, should get data from the server.
All the client file code will be like this:
import { useState } from 'react'
import { observer } from 'startupjs'
import { Div, Button, Span } from '@startupjs/ui'
import axios from 'axios'
export default observer(function SomeScreen () {
const [data, setData] = useState()
async function fetchData () {
try {
const response = await axios.get('/api/get-data')
setData(response.data)
} catch (error) {
console.log('error', error)
}
}
return (
<Div>
<Button onPress={fetchData}>Fetch by plugin</Button>
{data && data.message && <Span>Text: {data.message}</Span>}
</Div>
)
})
Let's create a plugin file named test.plugin.js.
In this example, we'll use the "api" hook, which will return some data from the server.
import { createPlugin } from '@startupjs/registry'
export default createPlugin({
name: 'test',
enabled: true,
// runtime environment - server
// we receive pluginOptions, to which we passed appName (described below)
server: (pluginOptions) => ({
// use api hook
api: (expressApp) => {
// on GET request to '/api/get-data'
// return our text, to which we add custom appName from pluginOptions
expressApp.get('/api/get-data', async (req, res) => {
res.json({ message: `Text returned by plugin ${pluginOptions.appName}` })
})
}
})
})
Let's add plugin information to startupjs.config.js and pass the necessary parameters to it (they will be in pluginOptions)
import testPlugin from './test.plugin.js'
export default {
plugins: {
[testPlugin]: {
// pass custom pluginOptions for hooks
server: {
// let this be some application name that will be in appName
// we'll get this data in the plugin itself and can use it at our discretion
appName: 'TEST APP'
}
}
}
}
Add this file to "exports" in package.json so it automatically loads into your application:
"exports": {
"./test.plugin": "./test.plugin.js"
}
Read more: