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.
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:
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.
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:
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.
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:
module.exports = {
events: {
emitters: ['style:changeTitleColor'],
},
}
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:
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 therender
from the React Testing Library, with the addition offireEmit
andemitObserver
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:
module.exports = {
events: {
listeners: ['style:changeTitleColor'],
},
}
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:
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 therender
from the React Testing Library, with the addition offireEmit
andemitObserver
functions - The
fireEmit
allows you to simulate the emission of an event