most react hooks updated

This commit is contained in:
Michael Peters 2022-02-07 01:09:38 -06:00
parent 434bc6bfe5
commit e002b7092c
16 changed files with 36 additions and 28 deletions

View File

@ -6,12 +6,13 @@ module.exports = {
plugins: [ plugins: [
'@typescript-eslint', '@typescript-eslint',
'unused-imports', 'unused-imports',
'react',
'react-hooks',
], ],
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:react/recommended', 'plugin:react/recommended',
'plugin:react-hooks/recommended',
], ],
settings: { settings: {
react: { react: {
@ -159,6 +160,9 @@ module.exports = {
'react/jsx-props-no-multi-spaces': 'warn', 'react/jsx-props-no-multi-spaces': 'warn',
'react/jsx-props-no-spreading': 'error', 'react/jsx-props-no-spreading': 'error',
'react/jsx-tag-spacing': [ 'warn', { closingSlash: 'never', beforeSelfClosing: 'always', afterOpening: 'never', beforeClosing: 'never' } ], 'react/jsx-tag-spacing': [ 'warn', { closingSlash: 'never', beforeSelfClosing: 'always', afterOpening: 'never', beforeClosing: 'never' } ],
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
}, },
}; };

View File

@ -102,7 +102,7 @@ const DownloadButton: FC<DownloadButtonProps> = (props: DownloadButtonProps) =>
setButtonText('Reveal in Explorer'); setButtonText('Reveal in Explorer');
setDownloading(false); setDownloading(false);
}, [ downloading, buttonShaking, downloadPath, downloadBuff ]); }, [ guild, resourceId, resourceName, downloading, buttonShaking, downloadPath, downloadBuff ]);
return <Button shaking={buttonShaking} onClick={downloadListener}>{buttonText}</Button>; return <Button shaking={buttonShaking} onClick={downloadListener}>{buttonText}</Button>;
}; };

View File

@ -18,7 +18,7 @@ const ChoicesControl: FC<ChoicesControlProps> = (props: ChoicesControlProps) =>
> >
{choice.display} {choice.display}
</div> </div>
)), [ choices, selectedId ]); )), [ choices, selectedId, setSelectedId ]);
return ( return (
<div className="choices-react"> <div className="choices-react">

View File

@ -45,11 +45,11 @@ const Display: FC<DisplayProps> = (props: DisplayProps) => {
} }
}, [ saving, saveFailed ]); }, [ saving, saveFailed ]);
const dismissInfoMessage = () => {
setDismissedInfoMessage(infoMessage);
};
const popup = useMemo(() => { const popup = useMemo(() => {
const dismissInfoMessage = () => {
setDismissedInfoMessage(infoMessage);
};
if (errorMessage) { if (errorMessage) {
return ( return (
<DisplayPopup tip={errorMessage}> <DisplayPopup tip={errorMessage}>
@ -72,7 +72,7 @@ const Display: FC<DisplayProps> = (props: DisplayProps) => {
} else { } else {
return null; return null;
} }
}, [ errorMessage, changes, resetChanges, saveChanges ]); }, [ errorMessage, infoMessage, dismissedInfoMessage, changes, resetChanges, saveChanges, saveButtonShaking, changesButtonText ]);
return ( return (
<div className="display"> <div className="display">

View File

@ -30,7 +30,7 @@ const DropdownInput: FC<DropdownInputProps> = (props: DropdownInputProps) => {
useEffect(() => { useEffect(() => {
// only do this expensive version once // only do this expensive version once
setDisplay(options.find(option => option.value === value)?.display ?? ''); setDisplay(options.find(option => option.value === value)?.display ?? '');
}, []); }, [ options, value ]);
const optionElements = useMemo(() => options.map(option => { const optionElements = useMemo(() => options.map(option => {
const className = option.value === value ? 'option selected' : 'option'; const className = option.value === value ? 'option selected' : 'option';
@ -40,7 +40,7 @@ const DropdownInput: FC<DropdownInputProps> = (props: DropdownInputProps) => {
setOptionsOpen(false); setOptionsOpen(false);
}; };
return <div className={className} key={option.value} onClick={onClick}>{option.display}</div>; return <div className={className} key={option.value} onClick={onClick}>{option.display}</div>;
}), [ options ]); }), [ options, setValue, value ]);
const labelElement = useMemo(() => label && <div className="label">{label}</div>, [ label ]); const labelElement = useMemo(() => label && <div className="label">{label}</div>, [ label ]);

View File

@ -40,7 +40,7 @@ const TextInput: FC<TextInputProps> = React.forwardRef(function TextInput(props:
} }
setValid(true); setValid(true);
setMessage(null); setMessage(null);
}, [ value ]); }, [ allowEmpty, label, maxLength, setMessage, setValid, value ]);
const labelElement = useMemo(() => label && <div className="label">{label}</div>, [ label ]); const labelElement = useMemo(() => label && <div className="label">{label}</div>, [ label ]);

View File

@ -18,6 +18,8 @@ const Overlay: FC<OverlayProps> = (props: OverlayProps) => {
const setOverlay = useSetRecoilState<ReactNode>(overlayState); const setOverlay = useSetRecoilState<ReactNode>(overlayState);
if (childRootRef) { if (childRootRef) {
// this is alright to do since childRootRef (the ref itself) should never change for each component using this element
// eslint-disable-next-line react-hooks/rules-of-hooks
useActionWhenEscapeOrClickedOrContextOutsideEffect(childRootRef, () => setOverlay(null)); useActionWhenEscapeOrClickedOrContextOutsideEffect(childRootRef, () => setOverlay(null));
} }

View File

@ -30,12 +30,12 @@ const ConnectionInfoContextMenu: FC<ConnectionInfoContextMenuProps> = (props: Co
<div className="status-circle" /> <div className="status-circle" />
<div className="status-text">{status}</div> <div className="status-text">{status}</div>
</div> </div>
)), [ setSelfStatus ]); )), [ setSelfStatus, close ]);
const openPersonalize = useCallback(() => { const openPersonalize = useCallback(() => {
close(); close();
setOverlay(<PersonalizeOverlay guild={guild} selfMember={selfMember} />); setOverlay(<PersonalizeOverlay guild={guild} selfMember={selfMember} />);
}, [ close ]); }, [ close, guild, selfMember, setOverlay ]);
const alignment = useMemo(() => ({ bottom: 'top', centerX: 'centerX' }), []); const alignment = useMemo(() => ({ bottom: 'top', centerX: 'centerX' }), []);
@ -54,3 +54,4 @@ const ConnectionInfoContextMenu: FC<ConnectionInfoContextMenuProps> = (props: Co
}; };
export default ConnectionInfoContextMenu; export default ConnectionInfoContextMenu;

View File

@ -32,12 +32,12 @@ const GuildTitleContextMenu: FC<GuildTitleContextMenuProps> = (props: GuildTitle
return; return;
} }
setOverlay(<GuildSettingsOverlay guild={guild} />); setOverlay(<GuildSettingsOverlay guild={guild} />);
}, [ close ]); }, [ close, guild, setOverlay ]);
const openCreateChannel = useCallback(() => { const openCreateChannel = useCallback(() => {
close(); close();
setOverlay(<ChannelOverlay />); setOverlay(<ChannelOverlay />);
}, [ close ]); }, [ close, setOverlay ]);
const guildSettingsElement = useMemo(() => { const guildSettingsElement = useMemo(() => {
if (!isLoaded(selfMember)) return null; if (!isLoaded(selfMember)) return null;
@ -61,10 +61,11 @@ const GuildTitleContextMenu: FC<GuildTitleContextMenuProps> = (props: GuildTitle
); );
}, [ selfMember, openCreateChannel ]); }, [ selfMember, openCreateChannel ]);
if (guildSettingsElement === null && createChannelElement === null) return null;
const alignment = useMemo(() => ({ top: 'bottom', centerX: 'centerX' }), []); const alignment = useMemo(() => ({ top: 'bottom', centerX: 'centerX' }), []);
if (guildSettingsElement === null && createChannelElement === null) return null;
return ( return (
<ContextMenu alignment={alignment} close={close} relativeToRef={relativeToRef} realignDeps={[ selfMember ]}> <ContextMenu alignment={alignment} close={close} relativeToRef={relativeToRef} realignDeps={[ selfMember ]}>
<div className="guild-title-context-menu"> <div className="guild-title-context-menu">

View File

@ -43,14 +43,14 @@ const GuildOverviewDisplay: FC<GuildOverviewDisplayProps> = (props: GuildOvervie
if (!isLoaded(guildMeta)) return; if (!isLoaded(guildMeta)) return;
if (name === savedName) setName(guildMeta.value.name); if (name === savedName) setName(guildMeta.value.name);
setSavedName(guildMeta.value.name); setSavedName(guildMeta.value.name);
}, [ guildMeta ]); }, [ guildMeta, name, savedName ]);
useEffect(() => { useEffect(() => {
if (isLoaded(iconResource)) { if (isLoaded(iconResource)) {
if (iconBuff === savedIconBuff) setIconBuff(iconResource.value.data); if (iconBuff === savedIconBuff) setIconBuff(iconResource.value.data);
setSavedIconBuff(iconResource.value.data); setSavedIconBuff(iconResource.value.data);
} }
}, [ iconResource ]); }, [ iconBuff, iconResource, savedIconBuff ]);
const changes = useMemo( const changes = useMemo(
() => name !== savedName || iconBuff?.toString('hex') !== savedIconBuff?.toString('hex'), () => name !== savedName || iconBuff?.toString('hex') !== savedIconBuff?.toString('hex'),
@ -110,7 +110,7 @@ const GuildOverviewDisplay: FC<GuildOverviewDisplayProps> = (props: GuildOvervie
} }
setSaving(false); setSaving(false);
}, [ name, iconBuff, errorMessage, saving, guild, iconBuff, savedIconBuff ]); }, [ name, iconBuff, errorMessage, saving, savedName, savedIconBuff, guild ]);
return ( return (
<Display <Display

View File

@ -50,7 +50,7 @@ const ChannelElement: FC<ChannelElementProps> = (props: ChannelElementProps) =>
const launchModify = useCallback(() => { const launchModify = useCallback(() => {
setOverlay(<ChannelOverlay channel={channel} />); setOverlay(<ChannelOverlay channel={channel} />);
}, [ guild, channel ]); }, [ setOverlay, channel ]);
return ( return (
<div className={baseClassName} onClick={setSelfActiveChannel}> <div className={baseClassName} onClick={setSelfActiveChannel}>

View File

@ -47,7 +47,7 @@ const MessageList: FC = () => {
result.push(<MessageElement key={guild.id + 'm#' + message.id} guild={guild} message={message} prevMessage={prevMessage} />); result.push(<MessageElement key={guild.id + 'm#' + message.id} guild={guild} message={message} prevMessage={prevMessage} />);
} }
return result; return result;
}, [ messages ]); }, [ guild, messages ]);
return ( return (
<div className="message-list"> <div className="message-list">

View File

@ -86,7 +86,7 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
if (exampleAvatarBuff) { if (exampleAvatarBuff) {
if (avatarBuff === null) setAvatarBuff(exampleAvatarBuff); if (avatarBuff === null) setAvatarBuff(exampleAvatarBuff);
} }
}, [ exampleAvatarBuff ]); }, [ avatarBuff, exampleAvatarBuff ]);
const validationErrorMessage = useMemo(() => { const validationErrorMessage = useMemo(() => {
if (exampleAvatarBuffError && !avatarBuff) return 'Unable to load example avatar'; if (exampleAvatarBuffError && !avatarBuff) return 'Unable to load example avatar';

View File

@ -49,14 +49,14 @@ const ChannelOverlay: FC<ChannelOverlayProps> = (props: ChannelOverlayProps) =>
} else { } else {
setEdited(name.length > 0 && flavorText.length > 0); setEdited(name.length > 0 && flavorText.length > 0);
} }
}, [ name, flavorText ]); }, [ name, flavorText, channel ]);
const validationErrorMessage = useMemo(() => { const validationErrorMessage = useMemo(() => {
if (!edited) return null; if (!edited) return null;
if (!nameInputValid && nameInputMessage) return nameInputMessage; if (!nameInputValid && nameInputMessage) return nameInputMessage;
if (!flavorTextInputValid && flavorTextInputMessage) return flavorTextInputMessage; if (!flavorTextInputValid && flavorTextInputMessage) return flavorTextInputMessage;
return null; return null;
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage ]); }, [ edited, nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage ]);
const infoMessage = useMemo(() => { const infoMessage = useMemo(() => {
if (nameInputValid && nameInputMessage) return nameInputMessage; if (nameInputValid && nameInputMessage) return nameInputMessage;

View File

@ -48,11 +48,11 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
if (avatarBuff === savedAvatarBuff) setAvatarBuff(avatarResource.value.data); if (avatarBuff === savedAvatarBuff) setAvatarBuff(avatarResource.value.data);
setSavedAvatarBuff(avatarResource.value.data); setSavedAvatarBuff(avatarResource.value.data);
} }
}, [ avatarResource ]); }, [ avatarBuff, avatarResource, savedAvatarBuff ]);
useEffect(() => { useEffect(() => {
displayNameInputRef.current?.focus(); displayNameInputRef.current?.focus();
}, []); }, [ displayNameInputRef ]);
const validationErrorMessage = useMemo(() => { const validationErrorMessage = useMemo(() => {
if (isFailed(avatarResource)) return 'Unable to load avatar'; if (isFailed(avatarResource)) return 'Unable to load avatar';

View File

@ -109,7 +109,7 @@ const SendMessage: FC<SendMessageProps> = (props: SendMessageProps) => {
return; return;
} }
} }
}, []); }, [ isMounted ]);
const removeAttachment = useCallback(() => { const removeAttachment = useCallback(() => {
setAttachmentBuff(null); setAttachmentBuff(null);
@ -121,7 +121,7 @@ const SendMessage: FC<SendMessageProps> = (props: SendMessageProps) => {
const attachmentPreview = useMemo(() => { const attachmentPreview = useMemo(() => {
if (!attachmentBuff || !attachmentName) return null; if (!attachmentBuff || !attachmentName) return null;
return <AttachmentPreview attachmentBuff={attachmentBuff} attachmentName={attachmentName} remove={removeAttachment} />; return <AttachmentPreview attachmentBuff={attachmentBuff} attachmentName={attachmentName} remove={removeAttachment} />;
}, [ attachmentBuff, attachmentName ]); }, [ attachmentBuff, attachmentName, removeAttachment ]);
// WARNING: The types on this are funky because of react's lack of explicit support for 'plaintext-only' // WARNING: The types on this are funky because of react's lack of explicit support for 'plaintext-only'
const contentEditableType = useMemo(() => { const contentEditableType = useMemo(() => {