React error fixes (#435)

* Use keys for FormatDiffText
* Fix React errors due to unused useCombobox props
* Use <select value> instead of <option selected>
This commit is contained in:
Simon Lindholm
2022-04-12 14:03:55 +02:00
committed by GitHub
parent f7982e0c90
commit 15205aa183
7 changed files with 106 additions and 91 deletions
+1
View File
@@ -12,6 +12,7 @@ sandbox/
.DS_Store
.env.*
*.log
/frontend/*.tsbuildinfo
/frontend/.next
/frontend/.cache
/frontend/cache
+4 -4
View File
@@ -21,13 +21,13 @@ const SelectedSourceLineContext = createContext<number | null>(null)
function FormatDiffText({ texts }: { texts: api.DiffText[] }) {
return <> {
texts.map(t => {
texts.map((t, i) => {
if (t.format == "rotation") {
return <span className={styles[`rotation${t.index % 9}`]}>{t.text}</span>
return <span key={i} className={styles[`rotation${t.index % 9}`]}>{t.text}</span>
} else if (t.format) {
return <span className={styles[t.format]}>{t.text}</span>
return <span key={i} className={styles[t.format]}>{t.text}</span>
} else {
return <span>{t.text}</span>
return <span key={i}>{t.text}</span>
}
})
} </>
@@ -85,6 +85,18 @@
border-top-left-radius: 0;
border-top-right-radius: 0;
opacity: 0;
transform: scaleY(0.75);
visibility: hidden;
transition-duration: 0.125s;
transition-property: opacity, transform, visibility;
}
.results.isOpen {
opacity: 0.9999;
transform: none;
visibility: visible;
}
.item {
+54 -45
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react"
import { useEffect, useRef, useState } from "react"
import Image from "next/image"
import { useRouter } from "next/router"
@@ -6,7 +6,6 @@ import { useRouter } from "next/router"
import { SearchIcon } from "@primer/octicons-react"
import classNames from "classnames"
import { useCombobox } from "downshift"
import { motion, AnimatePresence } from "framer-motion"
import { usePlausible } from "next-plausible"
import { useLayer } from "react-laag"
import useSWR from "swr"
@@ -27,7 +26,7 @@ function useRecentScratches(): api.TerseScratch[] {
return data?.results || []
}
export default function Search({ className }: { className?: string }) {
function MountedSearch({ className }: { className?: string }) {
const [query, setQuery] = useState("")
const [isFocused, setIsFocused] = useState(false)
const [isLoading, setIsLoading] = useState(false)
@@ -102,21 +101,11 @@ export default function Search({ className }: { className?: string }) {
},
})
const [isMounted, setIsMounted] = useState(false)
useEffect(() => setIsMounted(true), [])
const router = useRouter()
if (!isMounted) {
return <div className={classNames(styles.container, className)}>
<SearchIcon className={styles.icon} />
<input
className={styles.input}
type="text"
placeholder="Search decomp.me..."
spellCheck={false}
/>
</div>
const lastWidthRef = useRef(0)
if (triggerBounds) {
lastWidthRef.current = triggerBounds.width
}
return <div
@@ -149,33 +138,32 @@ export default function Search({ className }: { className?: string }) {
onClick={() => setIsFocused(true)}
/>
{isLoading && isFocused && <LoadingSpinner className={styles.loadingIcon} />}
{isMounted && renderLayer(
<AnimatePresence>
{isOpen && <motion.ul
{...getMenuProps(layerProps)}
className={classNames(verticalMenuStyles.menu, styles.results)}
style={{
width: triggerBounds.width,
...layerProps.style,
}}
initial={{ opacity: 0, scaleY: 0.75 }}
animate={{ opacity: 1, scaleY: 1 }}
exit={{ opacity: 0, scaleY: 0.75 }}
transition={{ duration: 0.125 }}
>
{items.map((scratch, index) => {
const props = getItemProps({ item: scratch, index })
const oldOnClick = props.onClick
props.onClick = evt => {
evt.preventDefault() // Don't visit the link
return oldOnClick(evt)
}
{renderLayer(
<ul
{...getMenuProps(layerProps)}
className={classNames(verticalMenuStyles.menu, styles.results, {
[styles.isOpen]: isOpen,
})}
style={{
width: lastWidthRef.current,
...layerProps.style,
}}
>
{items.map((scratch, index) => {
const props = getItemProps({ item: scratch, index })
const oldOnClick = props.onClick
props.onClick = evt => {
evt.preventDefault() // Don't visit the link
return oldOnClick(evt)
}
return <a
key={scratch.url}
return <li
key={scratch.url}
{...props}
>
<a
href={scratch.html_url}
className={classNames(verticalMenuStyles.item, styles.item)}
{...props}
>
<ScratchIcon scratch={scratch} size={16} />
<span className={styles.itemName}>
@@ -189,12 +177,33 @@ export default function Search({ className }: { className?: string }) {
className={styles.scratchOwnerAvatar}
/>}
</a>
})}
{items.length === 0 && <div className={classNames(verticalMenuStyles.item, styles.noResults)}>
</li>
})}
{items.length === 0 && <li>
<div className={classNames(verticalMenuStyles.item, styles.noResults)}>
No search results
</div>}
</motion.ul>}
</AnimatePresence>
</div>
</li>}
</ul>
)}
</div>
}
export default function Search({ className }: { className?: string }) {
const [isMounted, setIsMounted] = useState(false)
useEffect(() => setIsMounted(true), [])
if (!isMounted) {
return <div className={classNames(styles.container, className)}>
<SearchIcon className={styles.icon} />
<input
className={styles.input}
type="text"
placeholder="Search decomp.me..."
spellCheck={false}
/>
</div>
}
return <MountedSearch className={className} />
}
+3 -2
View File
@@ -8,11 +8,12 @@ export type Props = {
className?: string
onChange: ChangeEventHandler<HTMLSelectElement>
children: ReactNode
value?: string
}
export default function Select({ onChange, children, className }: Props) {
export default function Select({ onChange, children, className, value }: Props) {
return <div className={`${styles.group} ${className}`}>
<select onChange={onChange}>
<select onChange={onChange} value={value}>
{children}
</select>
@@ -8,9 +8,10 @@ import Select from "../Select"
import styles from "./CompilerOpts.module.css"
import { useCompilersForPlatform } from "./compilers"
import Flags, { NO_TRANSLATION } from "./Flags"
import PresetSelect from "./PresetSelect"
const NO_TRANSLATION = "NO_TRANSLATION"
interface IOptsContext {
checkFlag(flag: string): boolean
setFlag(flag: string, value: boolean): void
@@ -18,7 +19,7 @@ interface IOptsContext {
const OptsContext = createContext<IOptsContext>(undefined)
export function Checkbox({ flag, description }) {
function Checkbox({ flag, description }) {
const { checkFlag, setFlag } = useContext(OptsContext)
const isChecked = checkFlag(flag)
@@ -30,7 +31,7 @@ export function Checkbox({ flag, description }) {
</div>
}
export function FlagSet({ name, children }) {
function FlagSet({ name, children, value }) {
const { setFlag } = useContext(OptsContext)
return <div className={styles.flagSet}>
@@ -43,23 +44,43 @@ export function FlagSet({ name, children }) {
setFlag((event.target as HTMLSelectElement).value, true)
}}
value={value}
>
{children}
</Select>
</div>
}
export function FlagOption({ flag, description }: { flag: string, description?: string }) {
const { checkFlag } = useContext(OptsContext)
return <option
value={flag}
selected={checkFlag(flag)}
>
function FlagOption({ flag, description }: { flag: string, description?: string }) {
return <option value={flag}>
{flag} {description && description !== NO_TRANSLATION && `(${description})`}
</option>
}
interface FlagsProps {
schema: api.Flag[]
}
function Flags({ schema }: FlagsProps) {
const compilersTranslation = useTranslation("compilers")
const { checkFlag } = useContext(OptsContext)
return <>
{schema.map(flag => {
if (flag.type === "checkbox") {
return <Checkbox key={flag.id} flag={flag.flag} description={compilersTranslation.t(flag.id)} />
} else if (flag.type === "flagset") {
const selectedFlag = flag.flags.filter(checkFlag)[0] || flag.flags[0]
return <FlagSet key={flag.id} name={compilersTranslation.t(flag.id)} value={selectedFlag}>
{flag.flags.map(f => <FlagOption key={f} flag={f} description={
compilersTranslation.t(flag.id + "." + f, null, { default: NO_TRANSLATION })
} />)}
</FlagSet>
}
})}
</>
}
export type CompilerOptsT = {
compiler: string
compiler_flags: string
@@ -188,11 +209,11 @@ export function OptsEditor({ platform, compiler: compilerId, setCompiler, opts,
<Select
className={styles.compilerSelect}
onChange={e => setCompiler((e.target as HTMLSelectElement).value)}
value={compilerId}
>
{Object.keys(compilers).map(id => <option
key={id}
value={id}
selected={id === compilerId}
>
{compilersTranslation.t(id)}
</option>)}
@@ -1,29 +0,0 @@
import useTranslation from "next-translate/useTranslation"
import * as api from "../../lib/api"
import { Checkbox, FlagSet, FlagOption } from "./CompilerOpts"
export const NO_TRANSLATION = "NO_TRANSLATION"
export interface Props {
schema: api.Flag[]
}
export default function Flags({ schema }: Props) {
const compilersTranslation = useTranslation("compilers")
return <>
{schema.map(flag => {
if (flag.type === "checkbox") {
return <Checkbox key={flag.id} flag={flag.flag} description={compilersTranslation.t(flag.id)} />
} else if (flag.type === "flagset") {
return <FlagSet key={flag.id} name={compilersTranslation.t(flag.id)}>
{flag.flags.map(f => <FlagOption key={f} flag={f} description={
compilersTranslation.t(flag.id + "." + f, null, { default: NO_TRANSLATION })
} />)}
</FlagSet>
}
})}
</>
}