diff --git a/docs/migration/v12.md b/docs/migration/v12.md
index 8bf25c7b..13cd6e3a 100644
--- a/docs/migration/v12.md
+++ b/docs/migration/v12.md
@@ -93,17 +93,114 @@ React 18 has minimal effects on the operation of an app in a browser, but it has
#### React Testing Library
-Testing-library supports React 18 since v13, so the first step is to upgrade that library and the supporting libraries around it. These major versions bumps come with a few breaking changes that we're trying to document here:
+Testing-library supports React 18 since v13, so the first step is to upgrade that library and the supporting libraries around it. These major versions bumps come with a few breaking changes that we will document here with some examples to make migration easier.
-- `userEvent` is async now in `@testing-library/user-event`. Check the [release notes](https://github.com/testing-library/user-event/releases/tag/v14.0.0) for an idea of the improvements and new API
- - In practice, this means mostly that you'd need to add an `await` for `userEvent` interactions like `userEvent.click()`
-- `renderHook` is not a separate library anymore; it is part of the `@testing-library/react`, and it has quite a different API. NB: most docs online still point to the old version, which can be confusing.
- - Use `import { renderHook } from '@testing-library/react'` instead of `import { renderHook } from '@testing-library/react-hooks`
- - The return type for renderHook is different. One major difference is the absence of `waitForNextUpdate` returned from `renderHook`. Now it can be replaced with the `waitFor` from `@testing-library`, and adding an expectation to wait for, for example.
- - Testing hooks that throw errors is different. Use `expect(renderHook(() => { ... })).toThrow(....)`
-- Faking timers can help with tests that seem to fail with concurrency issues
+Examples come from the migration for the Aggregate Data Entry app; you can check out the PR [here](https://github.com/dhis2/aggregate-data-entry-app/pull/396) for more examples in context.
-[To do: add details to each bullet; get examples from PRs]
+##### `userEvent`
+
+`userEvent` is async now in `@testing-library/user-event`. Check the [release notes](https://github.com/testing-library/user-event/releases/tag/v14.0.0) for an idea of the improvements and new API.
+
+In practice, this means mostly that you'd need to add an `await` for `userEvent` interactions like `userEvent.click()`:
+
+```diff
++ import userEvent from '@testing-library/user-event'
+
+- it('should allow re-running validation', () => {
++ // Make the function async:
++ it('should allow re-running validation', async () => {
+ // ...
+
+- userEvent.click(getByText('Run validation again'))
+- // Or this other form:
+- fireEvent.click(getByText('Run validation again'))
++ // Now that it's asynchronous, await the event:
++ await userEvent.click(getByText('Run validation again'))
+
+ await findByText('2 medium priority alerts')
+ expect(queryByText('There was a problem running validation')).toBeNull()
+})
+```
+
+##### `renderHook`
+
+`renderHook` is not a separate library anymore; it is part of the `@testing-library/react`, and it has [quite a different API](https://testing-library.com/docs/react-testing-library/api/#renderhook). (NB: Some docs online still point to the old version, which can be confusing.)
+
+- Use `import { renderHook } from '@testing-library/react'` instead of `import { renderHook } from '@testing-library/react-hooks`
+- The [return type](https://testing-library.com/docs/react-testing-library/api/#renderhook-result) for renderHook is different. One major difference is the absence of `waitForNextUpdate` returned from `renderHook`. Now it can be replaced with the `waitFor` from `@testing-library`, and adding an expectation to wait for, for example.
+- Testing hooks that throw errors is different. Use `expect(renderHook(() => { ... })).toThrow(....)`
+
+This example demonstrates some of the changes:
+
+```diff
+- import { renderHook } from '@testing-library/react-hooks'
++ // Update import, and include waitFor:
++ import { renderHook, waitFor } from '@testing-library/react'
+import React from 'react'
+import { useRootOrgData } from './use-root-org-data.js'
+
+it('should provide the org unit data', async () => {
+- const { result, waitForNextUpdate } = renderHook(
+- () => useRootOrgData(['A0000000000']),
+- { wrapper }
+- )
++ // waitForNextUpdate is no longer part of the result:
++ const { result } = renderHook(() => useRootOrgData(['A0000000000']), {
++ wrapper,
++ })
+
+- await waitForNextUpdate()
++ // Instead, use waitFor:
++ await waitFor(() => {})
+ // ...
+})
+```
+
+##### Fake timers
+
+Faking timers can help with tests that seem to fail with concurrency issues:
+
+```diff
+describe('', () => {
++ // Set up fake timers:
++ beforeEach(() => {
++ jest.useFakeTimers
++ })
+
+ afterEach(() => {
+ // ...
+ })
+
+ it('shows a loading indicator when submitting a comment change', async () => {
+ // ...
+
+- const { getByRole, queryByRole } = render()
++ const { getByRole, queryByRole, findByRole } = render(
++
++ )
+
+- userEvent.click(getByRole('button', { name: 'Edit comment' }))
++ const editButton = await findByRole('button', { name: 'Edit comment' })
++ // Set up user event with fake timers:
++ const user = userEvent.setup({
++ advanceTimers: jest.advanceTimersByTime,
++ })
++ await user.click(editButton)
+
+ // ...
+
+ expect(queryByRole('progressbar')).not.toBeInTheDocument()
+- userEvent.click(getByRole('button', { name: 'Save comment' }))
+- expect(getByRole('progressbar')).toBeInTheDocument()
++ // Use the setup from above:
++ const btnSaveComment = await findByRole('button', {
++ name: 'Save comment',
++ })
++ await user.click(btnSaveComment)
++ await findByRole('progressbar')
+ })
+})
+```
#### Enzyme