Skip to main content

Communication

The Communication is a tool that allows different Micro Frontends in the same screen and the Application Host to communicate between them. This is possible through a pub/sub events system.

caution

For the communication feature to work properly, the Micro Frontends and Application Host must use FrontHub version 6.1.0 or higher.

How does it work?

A Micro Frontend or the Application Host can emit one or more events in order to be listened to by other Micro Frontends or by the Application Host. A Micro Frontend or Application Host can also listen to one or more events emitted for other Micro Frontends or by the Application Host.

An event is identified by two elements, the channel and the message. The channel indicates the context of the event and are used to avoid events name collision, and the message is the event identifier for that context.

When emitted, the event can also come with a payload, a group of useful information that can be consumed by the listener.

Emitting events

By Micro Frontends

In order to be able to emit events, you need to configure your front-hub.config.js file and add the events key, as follows:

front-hub.config.js
module.exports = {
events: {
emitters: ['style:changeTitleColor'],
},
}

In your Micro Frontend component, you can use the emit method from the useCommunication hook as follows:

import React from 'react'
import { useCommunication } from '@resultadosdigitais/front-hub/react'

const App = () => {
const { emit } = useCommunication('style')

return (
<button onClick={() => emit('changeTitleColor', { color: 'red' })}>
Change Color
</button>
)
}

You can emit different messages on different channels. In the snippet above we are emitting a 'changeTitleColor' message on 'style' channel passing a payload with the color value. Other Micro Frontends or the Application Host can now subscribe to that event.

info

when present, the payload must always be an object.

By the Application Host

In the Application Host, you don't need to do any previous configuration to emit events. Just use command 'communication' with option 'emit' plus event identifier and the payload as arguments of fronthub function.

<button>Change Color</button>

<script>
const button = document.querySelector('button')

button.addEventListener('click', () => {
fronthub('communication', 'emit', 'style:changeTitleColor', {
color: 'red',
})
})
</script>

Listening to events

By Micro Frontends

You need to configure your front-hub.config.js to listen to events as follows:

front-hub.config.js
module.exports = {
events: {
listeners: ['style:changeTitleColor'],
},
}

In your Micro Frontend component, you can use the useListener method from the useCommunication hook as follows:

import React, { useState } from 'react'
import { useCommunication } from '@resultadosdigitais/front-hub/react'

const App = () => {
const [color, setColor] = useState('blue')
const { useListener } = useCommunication('style')

useListener('changeTitleColor', ({ color: newColor }) => {
setColor(newColor)
})

return <h1 style={{ color }}>Hello Front-Hub!</h1>
}

When a Micro Frontend or Application Host dispatches the event, the Micro Frontend listener will be notified and the useListener callback will be called.

info

The callback is registered only once, this occurs when the useListener is called for the first time during the evaluation of the component where it is inserted. If you need to update this record because the values referenced inside this callback change, include the dependency array at the end of the useListener, like to the useCallback hook.

import React, { useState } from 'react'
import { useCommunication } from '@resultadosdigitais/front-hub/react'

const App = () => {
const [count, setCount] = useState(0)
const { useListener } = useCommunication('count')

useListener('add', () => setCount(count + 1), [count])

return <span>{count}</span>
}

By the Application Host

As well as the event emission, you don't need to do any previous configuration to listen events in Application Host. Just use command 'communication' with option 'listen' plus event identifier and the callback as arguments of fronthub function.

<h1 style="color: blue">Change Color</h1>

<script>
const title = document.querySelector('h1')

fronthub(
'communication',
'listen',
'style:changeTitleColor',
({ color: newColor }) => {
title.style.color = newColor
},
)
</script>

Unlistening events

When you listen to an event, whenever it is emitted, the associated callback will be called. However, it may be useful to stop this behavior in some circumstances.

Within the Micro Frontend, the value returned by the useListner is a function that unlistens to the event immediately after being called.

import React, { useState } from 'react'
import { useCommunication } from '@resultadosdigitais/front-hub/react'

const App = () => {
const [color, setColor] = useState('blue')
const { useListener } = useCommunication('style')

const unlisten = useListener('changeTitleColor', ({ color: newColor }) => {
setColor(newColor)
})

return (
<>
<h1 style={{ color }}>Hello Front-Hub!</h1>
<button onClick={unlisten}>Fix color</button>
</>
)
}

Something similar can be done in Application Host:

<h1 style="color: blue">Change Color</h1>
<button>Fix color</button>

<script>
const title = document.querySelector('h1')
const button = document.querySelector('button')

const unlisten = fronthub(
'communication',
'listen',
'style:changeTitleColor',
({ color: newColor }) => {
title.style.color = newColor
},
)

button.addEventListener('click', unlisten)
</script>

The fronthub function, when called with the 'communication' command and the 'listen' option, returns a unlisten function for the event.

How to test

Testing that an event is emitted properly by a Micro Frontend

Suppose this Micro Frontend that emits an event:

front-hub.config.js
module.exports = {
events: {
emitters: ['style:changeTitleColor'],
},
}
App.js
import React from 'react'
import { useCommunication } from '@resultadosdigitais/front-hub/react'

const App = () => {
const { emit } = useCommunication('style')

return (
<button onClick={() => emit('changeTitleColor', { color: 'red' })}>
Change color
</button>
)
}

export default App

A possible test scenario would be:

App.test.js
import React from 'react'
import {
clearCommunicationContext,
renderWithCommunication,
} from '@resultadosdigitais/front-hub/react/jest'
import { fireEvent, screen } from '@testing-library/react'
import App from './App.js'

beforeEach(clearCommunicationContext)

describe('App', () => {
describe('when the button is clicked', () => {
const changeTitleColorEvent = 'style:changeTitleColor'

it(`emits "${changeTitleColorEvent}" event with the proper paylod`, () => {
const payload = { color: 'red' }
const { emitObserver } = renderWithCommunication(<App />)
const button = screen.getByText('Change color')

fireEvent.click(button)

expect(emitObserver).toBeCalledWith(changeTitleColorEvent, payload)
})
})
})
  • Use clearCommunicationContext to clear FrontHub event data and keep the tests independent of each other
  • Use renderWithCommunication to render your component, it returns the same properties as the render from the React Testing Library, with the addition of fireEmit and emitObserver functions
  • The emitObserver is a mock function that allows you to check when an event was emitted and with which payload

Testing that a Micro Frontend reacts properly to the event emission

Suppose this Micro Frontend that listen an event:

front-hub.config.js
module.exports = {
events: {
listeners: ['style:changeTitleColor'],
},
}
App.js
import React, { useState } from 'react'
import { useCommunication } from '@resultadosdigitais/front-hub/react'

const App = () => {
const [color, setColor] = useState('blue')
const { useListener } = useCommunication('style')

const unlisten = useListener('changeTitleColor', ({ color: newColor }) => {
setColor(newColor)
})

return (
<>
<h1 style={{ color }}>Hello Front-Hub!</h1>
<button onClick={unlisten}>Fix color</button>
</>
)
}

export default App

A possible test scenario would be:

App.test.js
import React from 'react'
import {
clearCommunicationContext,
renderWithCommunication,
} from '@resultadosdigitais/front-hub/react/jest'
import { screen, act } from '@testing-library/react'
import App from './App.js'

beforeEach(clearCommunicationContext)

describe('App', () => {
const changeTitleColorEvent = 'style:changeTitleColor'

it('starts with the blue title', () => {
renderWithCommunication(<App />)

const title = screen.getByText('Hello Front-Hub!')

expect(title.style.color).toBe('blue')
})

describe(`when event "${changeTitleColorEvent}" is emitted`, () => {
it('changes the title color to the event payload color', () => {
const payload = { color: 'red' }
const { fireEmit } = renderWithCommunication(<App />)
const title = screen.getByText('Hello Front-Hub!')

act(() => {
fireEmit(changeTitleColorEvent, payload)
})

expect(title.style.color).toBe(payload.color)
})
})
})
  • Use clearCommunicationContext to clear FrontHub event data and keep the tests independent of each other
  • It is good practice to test the component's initial state before it reacts to the event
  • Use renderWithCommunication to render your component, it returns the same properties as the render from the React Testing Library, with the addition of fireEmit and emitObserver functions
  • The fireEmit allows you to simulate the emission of an event