React输入框每次按键后都失去焦点。

6

我在导航组件上有一个搜索栏。每次输入后,输入框都会失去焦点,需要重新点击才能输入下一个字符。

这是输入框的代码:

<input
    type="text"
    name="search"
    placeholder="Search"
    value={search}
    onChange={handleInputChange}
/>

这是handleInputChange函数:

function handleInputChange(event) {
   event.preventDefault();
   let value = event.target.value;
   setSearch(value);
}

这里是设置搜索的钩子:
const [search, setSearch] = useState("");

我尝试为输入框添加key,但没有生效。当我将搜索输入框移至新组件时,也没有生效。
以下是完整代码:
import React, { useEffect, useState, useCallback } from "react";
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import { Row, Col } from '../Grid';
import IconButton from '@material-ui/core/IconButton';
import SearchIcon from '@material-ui/icons/Search';
import ShoppingCartOutlinedIcon from '@material-ui/icons/ShoppingCartOutlined';
import MenuIcon from '@material-ui/icons/Menu';
import Badge from '@material-ui/core/Badge';
import useScrollTrigger from '@material-ui/core/useScrollTrigger';
import Slide from '@material-ui/core/Slide';
import SideMenu from '../SideMenu';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { css } from 'glamor';
import "./style.css";


function Navbar(props) {
    const cart = useSelector(state => state.cart);
    const [cartTotal, setCartTotal] = useState(0);
    const [loggedIn, setLoggedIn] = useState(false);
    const [search, setSearch] = useState("");
    const [isOpen, setIsOpen] = useState(false);
    const [renderMiddleCol, setMiddleCol] = useState(true);
    const [menuClass, setMenuClass] = useState("no-menu");

    useEffect(() => {
        if (cart[0]) {
            setCartTotal(cart[0].line_items.length)
        }
    }, [cart[0]]);
    useEffect(() => {
        if (window.sessionStorage.id) {
            setLoggedIn(true);
        } else {
            setLoggedIn(false);
        }
    }, [loggedIn]);
    useEffect(() => {
        if (window.innerWidth < 450) {
            setMiddleCol(false);
        } else {
            setMiddleCol(true);
        }
    }, [window.innerWidth]);
    function HideOnScroll(props) {
        const { children, window } = props;
        const trigger = useScrollTrigger({ target: window ? window() : undefined });
        return (
            <Slide appear={false} direction="down" in={!trigger}>
                {children}
            </Slide>
        );
    }
    HideOnScroll.propTypes = {
        children: PropTypes.element.isRequired,
        window: PropTypes.func,
    };
    function CheckCart() {
        if (window.sessionStorage.id) {
            window.location.href = "/cart";
        } else {
            toast("Please login to view your cart", {
                className: css({
                    background: '#3E0768',
                    boxShadow: '2px 2px 20px 2px rgba(0,0,0,0.3)',
                    borderRadius: '17px'
                }),
                bodyClassName: css({
                    fontSize: '20px',
                    color: 'white'
                }),
                progressClassName: css({
                    background: "linear-gradient(90deg, rgba(0,0,0,1) 0%, rgba(62,7,104,1) 80%)"
                })
            });
        }
    }
    function Search() {
        if (search) {
            sessionStorage.setItem("search", search);
            window.location.href = "/search";
        } else {
            toast("Search field cannot be empty", {
                className: css({
                    background: '#3E0768',
                    boxShadow: '2px 2px 20px 2px rgba(0,0,0,0.3)',
                    borderRadius: '17px'
                }),
                bodyClassName: css({
                    fontSize: '20px',
                    color: 'white'
                }),
                progressClassName: css({
                    background: "linear-gradient(90deg, rgba(0,0,0,1) 0%, rgba(62,7,104,1) 80%)"
                })
            });
        }
    }
    function logOut(event) {
        event.preventDefault();
        setIsOpen(false);
        sessionStorage.clear();
        window.location.href = "/login";
    }
    function handleInputChange(event) {
        event.preventDefault();
        let value = event.target.value;
        setSearch(value);
    }
    function toggleMenu(event) {
        event.preventDefault();
        setIsOpen(!isOpen);
        if (menuClass === "no-menu") {
            setMenuClass("menu-background");
        } else {
            setMenuClass("no-menu");
        }
    }
    const theme = createMuiTheme({
        palette: {
            primary: {
                main: '#000000',
                contrastText: '#ffffff',
            },
            secondary: {
                light: '#3E0768',
                main: '#3E0768',
                contrastText: '#ffffff',
            },
            tertiary: {
                main: '#ffffff',
            }
        },
    });

    return (
        <MuiThemeProvider theme={theme}>
            <HideOnScroll {...props}>
                <AppBar position="fixed" color="primary">
                    <Toolbar>
                        <Col size="md-1">
                            <IconButton
                                onClick={toggleMenu}
                                aria-label="Menu"
                            >
                                <MenuIcon
                                    fontSize="large"
                                    className="white"
                                />
                            </IconButton>
                        </Col>
                        <Col size="md-2">
                            <a href="/" className="white "><h6>Demo Company</h6></a>
                        </Col>
                        {renderMiddleCol ? (
                            <Col size="lg-6 md-5 sm-3" />
                        ) : (<div />)}
                        <Col size="md-2 4">
                            <Row no-gutters>
                                <div className="search-box">
                                    <Col size="md-2 1">
                                        <IconButton onClick={Search} aria-label="search" >
                                            <SearchIcon className="white" />
                                        </IconButton>
                                    </Col>
                                    <Col size="md-8 9">
                                        {/* <SearchForm
                                            value={search}
                                            onChange={handleInputChange}
                                        /> */}
                                        <input
                                            className="search-field white"
                                            type="text"
                                            name="search"
                                            placeholder="Search"
                                            value={search}
                                            onChange={handleInputChange}
                                        />
                                    </Col>
                                </div>
                            </Row>
                        </Col>
                        <Col size="md-1">
                            <IconButton
                                onClick={CheckCart}
                                aria-label="Go to cart"
                            >
                                <MuiThemeProvider theme={theme}>
                                    <Badge
                                        badgeContent={cartTotal}
                                        color="secondary"
                                    >
                                        <ShoppingCartOutlinedIcon className="white" />
                                    </Badge>
                                </MuiThemeProvider>
                            </IconButton>
                        </Col>
                    </Toolbar>
                </AppBar>
            </HideOnScroll>
            <SideMenu
                isOpen={isOpen}
                menuClass={menuClass}
                toggleMenu={toggleMenu}
                loggedIn={loggedIn}
                logOut={logOut}
            />
        </MuiThemeProvider>
    );
}

export default Navbar;

你的组件在每次按键时都重新渲染了吗?因为它正在设置状态。 - ProblemsEverywhere
请发布更多的代码。 - Chase
这个 CodeSandbox 上的代码与您发布的代码完全相同,似乎没有问题。可能需要更多上下文。https://codesandbox.io/s/hopeful-cerf-iyt16 - Mathieu
我已经编辑了问题,并附上了完整的代码。 - CB721
4个回答

5
我最终使用了输入引用并在每次重新渲染时将其设置为焦点。以下是修复它的代码。
const [search, setSearch] = useState("");
const searchInput = React.useRef(null);
useEffect(() => {
  searchInput.current.focus();
}, [search]);

以下是输入内容:

<input
  ref={searchInput}
  className="search-field white"
  type="text"
  name="search"
  placeholder="Search"
  value={search}
  onChange={handleInputChange}
/>

解决方案的来源:React:如何在componentDidMount上使用hooks设置焦点?


4
这是一篇我觉得很有帮助的详细说明:https://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/ 总结一下:如果没有一个不变的key,React在状态改变时会丢弃你控制的输入框的先前实例,并在其位置创建一个新的。当值改变后,具有焦点的输入框立即被移除。
请确保:
- 你所控制的输入框有一个“key”属性 - “key”属性的值不能以任何方式从输入框的“value”派生,因为你不希望在值改变时更改键。

6
因为我没有绘制多个元素,所以不需要使用键。而当我添加键时,它仍然失去了焦点。 - CB721
使用useState钩子似乎对我不起作用。我尝试过映射和不映射我的元素。 - artworkjpm
救命稻草!我的元素键每次都在变,因此重新渲染时失去了焦点。要不是这个问题,我可能要很久才能意识到... - Luis Antonio Canettoli Ordoñez

2
这个问题的解决方法是不使用内部组件,例如:
const NewComponent = (props: {text: string}) => <div>{text}</div>;
return <div><NewComponent text="Text" /><div>;

当我以这种方式声明输入组件时,它会在每次按键时引起重新渲染。解决方法是将组件提取到另一个文件中,或者将整个组件放入JSX中,而不使用上述方法来清理代码。
这与React无法知道何时重新渲染有关。

0

我不确定为什么,但我的注意力问题通过更改这段代码得到了解决:

import { Route } from 'react-router-dom'
<Route path='xxx' component={() => <TheComponent... />}

其中TheComponent包含失去焦点的输入元素,而输入时键入,转换成此代码:

<Route path='xxx'>
  <TheComponent... />
</Route>

请参见我的SO问题,希望很快有人能够解释这个是如何工作的。

@AsafAviv 所以如果我需要 props,在原始代码中就不能使用 input。我也试着添加了 idkey。但是,这里的主要问题是,如果原帖发布者没有机会研究整个代码,他/她能否通过我的提示解决问题。(哦哦,AsafAviv 删除了他的评论) - Roland
发生的情况是,由于您将内联函数传递给“component”属性,导致重新渲染整个内容,从而破坏了焦点。 - Gianluca Pietrobon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接