forked from ant-design/ant-design
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Message hooks API (ant-design#25422)
* chore: comment on usePatchElement * refactor: conform message & notifaction code logic * feat: message useMessage, wip * feat: message.useMessage, it works now * fix: promise on regular api * feat: message hooks * chore: fix lint * chore: new line * chore: revert new line * refactor: prefixCls * fix: prefixCls * test: cov * chore * chore * chore * chore * docs * docs: message hooks faq * test: remove useless config provider * chore: remove some test codes * chore * docs: hooks version
- Loading branch information
Showing
10 changed files
with
515 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ lib/**/* | |
node_modules | ||
_site | ||
dist | ||
coverage | ||
**/*.d.ts | ||
# Scripts | ||
scripts/previewEditor/**/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
/* eslint-disable jsx-a11y/control-has-associated-label */ | ||
import React from 'react'; | ||
import { mount } from 'enzyme'; | ||
import message from '..'; | ||
import ConfigProvider from '../../config-provider'; | ||
|
||
describe('message.hooks', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
afterEach(() => { | ||
message.destroy(); | ||
}); | ||
|
||
it('should work', () => { | ||
const Context = React.createContext('light'); | ||
|
||
const Demo = () => { | ||
const [api, holder] = message.useMessage(); | ||
|
||
return ( | ||
<ConfigProvider prefixCls="my-test"> | ||
<Context.Provider value="bamboo"> | ||
<button | ||
type="button" | ||
onClick={() => { | ||
api.open({ | ||
content: ( | ||
<Context.Consumer> | ||
{name => <span className="hook-test-result">{name}</span>} | ||
</Context.Consumer> | ||
), | ||
duration: 0, | ||
}); | ||
}} | ||
/> | ||
{holder} | ||
</Context.Provider> | ||
</ConfigProvider> | ||
); | ||
}; | ||
|
||
const wrapper = mount(<Demo />); | ||
wrapper.find('button').simulate('click'); | ||
expect(document.querySelectorAll('.my-test-message-notice').length).toBe(1); | ||
expect(document.querySelector('.hook-test-result').innerHTML).toEqual('bamboo'); | ||
}); | ||
|
||
it('should work with success', () => { | ||
const Context = React.createContext('light'); | ||
|
||
const Demo = () => { | ||
const [api, holder] = message.useMessage(); | ||
|
||
return ( | ||
<ConfigProvider prefixCls="my-test"> | ||
<Context.Provider value="bamboo"> | ||
<button | ||
type="button" | ||
onClick={() => { | ||
api.success({ | ||
content: ( | ||
<Context.Consumer> | ||
{name => <span className="hook-test-result">{name}</span>} | ||
</Context.Consumer> | ||
), | ||
duration: 0, | ||
}); | ||
}} | ||
/> | ||
{holder} | ||
</Context.Provider> | ||
</ConfigProvider> | ||
); | ||
}; | ||
|
||
const wrapper = mount(<Demo />); | ||
wrapper.find('button').simulate('click'); | ||
expect(document.querySelectorAll('.my-test-message-notice').length).toBe(1); | ||
expect(document.querySelectorAll('.anticon-check-circle').length).toBe(1); | ||
expect(document.querySelector('.hook-test-result').innerHTML).toEqual('bamboo'); | ||
}); | ||
|
||
it('should work with onClose', done => { | ||
// if not use real timer, done won't be called | ||
jest.useRealTimers(); | ||
const Demo = () => { | ||
const [api, holder] = message.useMessage(); | ||
return ( | ||
<> | ||
<button | ||
type="button" | ||
onClick={() => { | ||
api.open({ | ||
content: 'amazing', | ||
duration: 1, | ||
onClose() { | ||
done(); | ||
}, | ||
}); | ||
}} | ||
/> | ||
{holder} | ||
</> | ||
); | ||
}; | ||
|
||
const wrapper = mount(<Demo />); | ||
wrapper.find('button').simulate('click'); | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
it('should work with close promise', done => { | ||
// if not use real timer, done won't be called | ||
jest.useRealTimers(); | ||
const Demo = () => { | ||
const [api, holder] = message.useMessage(); | ||
return ( | ||
<> | ||
<button | ||
type="button" | ||
onClick={() => { | ||
api | ||
.open({ | ||
content: 'good', | ||
duration: 1, | ||
}) | ||
.then(() => { | ||
done(); | ||
}); | ||
}} | ||
/> | ||
{holder} | ||
</> | ||
); | ||
}; | ||
|
||
const wrapper = mount(<Demo />); | ||
wrapper.find('button').simulate('click'); | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
it('should work with hide', () => { | ||
let hide; | ||
const Demo = () => { | ||
const [api, holder] = message.useMessage(); | ||
return ( | ||
<ConfigProvider prefixCls="my-test"> | ||
<button | ||
type="button" | ||
onClick={() => { | ||
hide = api.open({ | ||
content: 'nice', | ||
duration: 0, | ||
}); | ||
}} | ||
/> | ||
{holder} | ||
</ConfigProvider> | ||
); | ||
}; | ||
|
||
const wrapper = mount(<Demo />); | ||
wrapper.find('button').simulate('click'); | ||
jest.runAllTimers(); | ||
expect(document.querySelectorAll('.my-test-message-notice').length).toBe(1); | ||
hide(); | ||
jest.runAllTimers(); | ||
expect(document.querySelectorAll('.my-test-message-notice').length).toBe(0); | ||
}); | ||
|
||
it('should be same hook', () => { | ||
let count = 0; | ||
|
||
const Demo = () => { | ||
const [, forceUpdate] = React.useState({}); | ||
const [api] = message.useMessage(); | ||
|
||
React.useEffect(() => { | ||
count += 1; | ||
expect(count).toEqual(1); | ||
forceUpdate(); | ||
}, [api]); | ||
|
||
return null; | ||
}; | ||
|
||
mount(<Demo />); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
--- | ||
order: 10 | ||
title: | ||
zh-CN: 通过 Hooks 获取上下文(4.5.0+) | ||
en-US: Get context with hooks (4.5.0+) | ||
--- | ||
|
||
## zh-CN | ||
|
||
通过 `message.useMessage` 创建支持读取 context 的 `contextHolder`。 | ||
|
||
## en-US | ||
|
||
Use `message.useMessage` to get `contextHolder` with context accessible issue. | ||
|
||
```jsx | ||
import { message, Button } from 'antd'; | ||
|
||
const Context = React.createContext({ name: 'Default' }); | ||
|
||
function Demo() { | ||
const [messsageApi, contextHolder] = message.useMessage(); | ||
const info = () => { | ||
messsageApi.open({ | ||
type: 'info', | ||
content: <Context.Consumer>{({ name }) => `Hello, ${name}!`}</Context.Consumer>, | ||
duration: 1, | ||
}); | ||
}; | ||
|
||
return ( | ||
<Context.Provider value={{ name: 'Ant Design' }}> | ||
{contextHolder} | ||
<Button type="primary" onClick={info}> | ||
Display normal message | ||
</Button> | ||
</Context.Provider> | ||
); | ||
} | ||
|
||
ReactDOM.render(<Demo />, mountNode); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import * as React from 'react'; | ||
import useRCNotification from 'rc-notification/lib/useNotification'; | ||
import { | ||
NotificationInstance as RCNotificationInstance, | ||
NoticeContent as RCNoticeContent, | ||
HolderReadyCallback as RCHolderReadyCallback, | ||
} from 'rc-notification/lib/Notification'; | ||
import { ConfigConsumer, ConfigConsumerProps } from '../../config-provider'; | ||
import { | ||
MessageInstance, | ||
ArgsProps, | ||
attachTypeApi, | ||
ThenableArgument, | ||
getKeyThenIncreaseKey, | ||
} from '..'; | ||
|
||
export default function createUseMessage( | ||
getRcNotificationInstance: ( | ||
args: ArgsProps, | ||
callback: (info: { prefixCls: string; instance: RCNotificationInstance }) => void, | ||
) => void, | ||
getRCNoticeProps: (args: ArgsProps, prefixCls: string) => RCNoticeContent, | ||
) { | ||
const useMessage = (): [MessageInstance, React.ReactElement] => { | ||
// We can only get content by render | ||
let getPrefixCls: ConfigConsumerProps['getPrefixCls']; | ||
|
||
// We create a proxy to handle delay created instance | ||
let innerInstance: RCNotificationInstance | null = null; | ||
const proxy = { | ||
add: (noticeProps: RCNoticeContent, holderCallback?: RCHolderReadyCallback) => { | ||
innerInstance?.component.add(noticeProps, holderCallback); | ||
}, | ||
} as any; | ||
|
||
const [hookNotify, holder] = useRCNotification(proxy); | ||
|
||
function notify(args: ArgsProps) { | ||
const { prefixCls: customizePrefixCls } = args; | ||
const mergedPrefixCls = getPrefixCls('message', customizePrefixCls); | ||
const target = args.key || getKeyThenIncreaseKey(); | ||
const closePromise = new Promise(resolve => { | ||
const callback = () => { | ||
if (typeof args.onClose === 'function') { | ||
args.onClose(); | ||
} | ||
return resolve(true); | ||
}; | ||
getRcNotificationInstance( | ||
{ | ||
...args, | ||
prefixCls: mergedPrefixCls, | ||
}, | ||
({ prefixCls, instance }) => { | ||
innerInstance = instance; | ||
hookNotify(getRCNoticeProps({ ...args, key: target, onClose: callback }, prefixCls)); | ||
}, | ||
); | ||
}); | ||
const result: any = () => { | ||
if (innerInstance) { | ||
innerInstance.removeNotice(target); | ||
} | ||
}; | ||
result.then = (filled: ThenableArgument, rejected: ThenableArgument) => | ||
closePromise.then(filled, rejected); | ||
result.promise = closePromise; | ||
return result; | ||
} | ||
|
||
// Fill functions | ||
const hookApiRef = React.useRef<any>({}); | ||
|
||
hookApiRef.current.open = notify; | ||
|
||
['success', 'info', 'warning', 'error', 'loading'].forEach(type => | ||
attachTypeApi(hookApiRef.current, type), | ||
); | ||
|
||
return [ | ||
hookApiRef.current, | ||
<ConfigConsumer key="holder"> | ||
{(context: ConfigConsumerProps) => { | ||
({ getPrefixCls } = context); | ||
return holder; | ||
}} | ||
</ConfigConsumer>, | ||
]; | ||
}; | ||
|
||
return useMessage; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.