Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add async validate email #175

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function Nav(_props: Props) {
{ label: `Basic`, key: '/' },
{ label: `CustomizeStyle`, key: '/customizeStyle' },
{ label: `DisableOnBlurValidation`, key: '/disableOnBlurValidation' },
{ label: `AsyncValidate`, key: '/asyncValidate' },
]}
onTabClick={async activeKey => {
await router.push(activeKey);
Expand Down
79 changes: 79 additions & 0 deletions examples/AsyncValidateExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as React from 'react';
import styled from '@emotion/styled';
import { isEmail, ReactMultiEmail } from '../react-multi-email';
import { Button } from 'antd';

interface Props {}

export const delay = (ms: number) => new Promise(res => setTimeout(() => res(undefined), ms));

function AsyncValidateExample(_props: Props) {
const [emails, setEmails] = React.useState<string[]>([]);
const [focused, setFocused] = React.useState(false);

return (
<Container>
<form>
<h3>Email</h3>
<ReactMultiEmail
placeholder='Input your email'
emails={emails}
onChange={(_emails: string[]) => {
setEmails(_emails);
}}
autoFocus={true}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
onKeyDown={evt => {
console.log(evt);
}}
onKeyUp={evt => {
console.log(evt);
}}
getLabel={(email, index, removeEmail) => {
return (
<div data-tag key={index}>
<div data-tag-item>{email}</div>
<span data-tag-handle onClick={() => removeEmail(index)}>
×
</span>
</div>
);
}}
onChangeInput={value => {
console.log(value);
}}
validateEmail={async email => {
await delay(100);
return isEmail(email);
}}
spinner={() => <Spinner>validating...</Spinner>}
/>
<br />
<h4>react-multi-email value</h4>
<h3>Focused: {focused ? 'true' : 'false'}</h3>
<p>{emails.join(', ') || 'empty'}</p>

<Button onClick={() => setEmails(['test', 'tt', '[email protected]'])}>
{`setEmails("['test', 'tt', '[email protected]']")`}
</Button>
<Button onClick={() => setEmails([])}>{`setEmails("[]")`}</Button>
</form>
</Container>
);
}

const Container = styled.div`
font-size: 13px;
`;
const Spinner = styled.div`
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.8);
`;

export default AsyncValidateExample;
2 changes: 1 addition & 1 deletion examples/BasicExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Button } from 'antd';

interface Props {}

function BasicExample(props: Props) {
function BasicExample(_props: Props) {
const [emails, setEmails] = React.useState<string[]>([]);
const [focused, setFocused] = React.useState(false);

Expand Down
23 changes: 23 additions & 0 deletions pages/asyncValidate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import type { NextPage } from 'next';
import { Container } from '../components/Layouts';
import styled from '@emotion/styled';
import BodyRoot from '../components/BodyRoot';
import AsyncValidateExample from '../examples/AsyncValidateExample';

const Page: NextPage = () => {
return (
<PageContainer>
<Container>
<div>
<h2>AsyncValidateExample</h2>
<AsyncValidateExample />
</div>
</Container>
</PageContainer>
);
};

const PageContainer = styled(BodyRoot)``;

export default Page;
37 changes: 26 additions & 11 deletions react-multi-email/ReactMultiEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export interface IReactMultiEmailProps {
allowDuplicate?: boolean;
}


export function ReactMultiEmail(props: IReactMultiEmailProps) {
const {
id,
Expand Down Expand Up @@ -68,16 +67,9 @@ export function ReactMultiEmail(props: IReactMultiEmailProps) {

const [focused, setFocused] = React.useState(false);
const [emails, setEmails] = React.useState<string[]>([]);
const [inputValue, setInputValue] = React.useState(initialInputValue);
const [inputValue, setInputValue] = React.useState('');
const [spinning, setSpinning] = React.useState(false);

const initialEmailAddress = (emails?: string[]) => {
if (typeof emails === 'undefined') return [];

const validEmails = emails.filter(email => validateEmail ? validateEmail(email) : isEmailFn(email));
return validEmails;
};

const findEmailAddress = React.useCallback(
async (value: string, isEnter?: boolean) => {
const validEmails: string[] = [];
Expand Down Expand Up @@ -280,8 +272,31 @@ export function ReactMultiEmail(props: IReactMultiEmailProps) {
}, [onFocus]);

React.useEffect(() => {
setEmails(initialEmailAddress(props.emails));
}, [props.emails]);
setInputValue(initialInputValue);
}, [initialInputValue]);

React.useEffect(() => {
if (validateEmail) {
(async () => {
setSpinning(true);

const validEmails: string[] = [];
for await (const email of props.emails ?? []) {
if (await validateEmail(email)) {
validEmails.push(email);
}
}
setEmails(validEmails);

setSpinning(false);
})();
} else {
const validEmails = props.emails?.filter(email => {
return isEmailFn(email);
});
setEmails(validEmails ?? []);
}
}, [props.emails, validateEmail]);

return (
<div
Expand Down
39 changes: 20 additions & 19 deletions test/emails.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { cleanup, render } from '@testing-library/react';
import { ReactMultiEmail } from '../react-multi-email';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { sleep } from './utils/sleep';

afterEach(cleanup);

Expand Down Expand Up @@ -75,28 +77,27 @@ describe('ReactMultEmail emails TEST', () => {
});
});


it('Emails with custom validation', async () => {

const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]/;

const mockValidateEmailFunc = jest.fn().mockImplementation((email) => regex.test(email));

render(
<ReactMultiEmail
validateEmail={(mockValidateEmailFunc)}
emails={['abc@gmail','abc','def', '[email protected]']}
getLabel={(email, index) => {
return (
<div data-tag key={index}>
<div data-tag-item>{email}</div>
</div>
);
}}
/>,
);

await act(async () => {
render(
<ReactMultiEmail
validateEmail={email => regex.test(email)}
emails={['abc@gmail', 'abc', 'def', '[email protected]']}
getLabel={(email, index) => {
return (
<div data-tag key={index}>
<div data-tag-item>{email}</div>
</div>
);
}}
/>,
);
await sleep(100);
});

const emailsWrapper = document.querySelector('.data-labels');
// 4 emails are passed to the component, but only 2 are valid based on the custom validation.
expect(emailsWrapper?.childElementCount).toEqual(2);
});
});
5 changes: 5 additions & 0 deletions test/utils/sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function sleep(period: number) {
return new Promise((resolve, _reject) => {
setTimeout(resolve, period);
});
}
Loading