输入框失去焦点,陷入重新渲染循环

Input box losing focus, caught in re-render loop

我已经阅读了以前关于类似问题的帖子,并且我已经根据建议(键等,onFocus(仅适用于单个输入))尽可能多地添加,不幸的是,我的反应知识不足以解决问题。

输入单个字符时输入框失去焦点,正在重新渲染组件(我认为)我可以继续在框中输入(每次单击输入框 calculator working) 结果和总和都按预期工作。

非常感谢任何帮助 谢谢

import { Table, THead, Tr, Th, TBody, Td } from '@twilio-paste/core/table'
import { Text } from '@twilio-paste/text';
import { Input } from '@twilio-paste/core/input';
import { Label } from '@twilio-paste/core/label';
import { styled } from '@twilio-paste/styling-library';
import { Box } from '@twilio-paste/core/box'

const StyledCheckboxContainer = styled(Box)`
  > * {
    width: 50%;
    margin-bottom: 8px;}`
const StyledColumn = styled.div`
  display: flex;
  flex-direction: column;
  flex-basis: 100%;
  flex: 1;
  margin-left: 8px;
  margin-right: 8px; `
const StyledText = styled.div`
  font-size: 12px;
  color: #606B85;`
const StyledInputContainer = styled.div`
  margin-bottom: 24px;`


export default function BudgetCalculator() {
  const [monthlyAmount, setMonthlyAmount] = useState(0);
  const [interestRate, setInterestRate] = useState(0);
  const [deposit, setDeposit] = useState(0);
  const [feeAtStart, setFeeAtStart] = useState(0);
  const [feeAtEnd, setFeeAtEnd] = useState(0);

  var decimalInterest = 1 + (interestRate / 100)
  var monthlyAPR = ((decimalInterest) ** (1 / 12)) - 1
  function annuityCalculation(monthDynamicArr: number) {
    var total = monthlyAmount * ((1 - (1 + monthlyAPR) ** -(monthDynamicArr))) / monthlyAPR
    return total
  }
  let monthsArr = [12, 24, 30, 36, 42, 48, 54, 60]
  let monthDynamicArr = monthsArr.map(month => {
    return annuityCalculation(month)
  })

  let totalWithFee = monthDynamicArr.map(month => (month + +feeAtEnd) - feeAtStart)
  let totalWithDeposit = totalWithFee.map(month => month + +deposit)

  const InputBoxes = () => {
    return (
      <StyledColumn>
        <StyledInputContainer>
          <Label required htmlFor="monthly_budget">Monthly Budget</Label>
          <Input
            key="monthly_budget"
            type="number"
            value={`${monthlyAmount}`}
            onChange={(e) => setMonthlyAmount(parseInt(e.target.value) || 0)}
            placeholder="0"
            insertBefore={<Text as="span" fontWeight="fontWeightSemibold">£</Text>}
          />
        </StyledInputContainer>
        <StyledInputContainer>
          <Label required htmlFor="apr">APR</Label>
          <Input
            key="apr"
            type="number"
            value={`${interestRate}`}
            onChange={(e) => setInterestRate(parseInt(e.target.value) || 0)}
            placeholder="0"
            insertAfter={<Text as="span" fontWeight="fontWeightSemibold">%</Text>}
          />
        </StyledInputContainer>
        <StyledInputContainer>
          <Label htmlFor="deposit">Deposit</Label>
          <Input type="number"
            key="deposit"
            value={`${deposit}`}
            onChange={(e) => setDeposit(parseInt(e.target.value) || 0)}
            placeholder="0"
            insertBefore={<Text as="span" fontWeight="fontWeightSemibold">£</Text>}
          />
        </StyledInputContainer>
        <StyledInputContainer>
          <Label htmlFor="fee_at_start">Fee at start</Label>
          <Input
            key="fee_at_start"
            type="number"
            value={`${feeAtStart}`}
            onChange={(e) => setFeeAtStart(parseInt(e.target.value) || 0)}
            placeholder="0"
            insertBefore={<Text as="span" fontWeight="fontWeightSemibold">£</Text>}
          />
        </StyledInputContainer>
        <StyledInputContainer>
          <Label htmlFor="fee_at_end">Fee at end</Label>
          <Input key="fee_at_end"
            type="number"
            value={`${feeAtEnd}`}
            onChange={(e) => setFeeAtEnd(parseInt(e.target.value) || 0)}
            placeholder="0"
            insertBefore={<Text as="span" fontWeight="fontWeightSemibold">£</Text>}
          />
        </StyledInputContainer>
      </StyledColumn>
    );
  }
  const tableMonths = monthsArr.map((month) => {
    return (
      <Tr>
        <Td key={"months"}>
          {month} months
        </Td>
      </Tr>
    )
  })

  const tableLoan = totalWithFee.map((money) => {
    return (
      <Tr>
        <Td key={"fee"}>
          £ {money.toFixed(2)}
        </Td>
      </Tr>
    )
  })
  const tableDeposit = totalWithDeposit.map((money) => {

    return (
      <Tr>
        <Td key={"totalDeposit"}>
          £ {money.toFixed(2)}
        </Td>
      </Tr>
    )
  })

  const TableExample = () => {
    return (
      <StyledColumn>
        <Table>
          <THead>
            <Tr>
              <Th>Term</Th>
              <Th>Loan</Th>
              <Th>Total</Th>
            </Tr>
          </THead>
          <TBody>
            <Td>
              <Tr>
                {tableMonths}
              </Tr>
            </Td>
            <Td>
              <Tr>
                {tableLoan}
              </Tr>
            </Td>
            <Td>
              <Tr>
                {tableDeposit}
              </Tr>
            </Td>
          </TBody>
        </Table>
      </StyledColumn >
    )
  }

  return (
    <>
      <StyledCheckboxContainer
        display="flex"
        flexWrap="wrap"
      >
        <InputBoxes />
        <TableExample />
      </StyledCheckboxContainer>
      <StyledText>
        Approximate
      </StyledText>
    </>
  )
}

这里的问题是您在每次渲染时创建 InputBoxes 组件。由于这是一个新的组件引用,它会卸载任何先前存在的版本并安装新版本。这就是输入焦点丢失的原因。

不要在另一个 React 组件的函数组件体内声明 React 组件。

阻力最小的路径是将 InputBoxes 重命名为 inputBoxes 并将其声明为 JSX 文字而不是 React 组件。

const inputBoxes = (
  <StyledColumn>
    <StyledInputContainer>
      <Label required htmlFor="monthly_budget">
        Monthly Budget
      </Label>
      <Input
        ...
      />
    </StyledInputContainer>
    <StyledInputContainer>
      <Label required htmlFor="apr">
        APR
      </Label>
      <Input
        ...
      />
    </StyledInputContainer>
    <StyledInputContainer>
      <Label htmlFor="deposit">Deposit</Label>
      <Input
        ...
      />
    </StyledInputContainer>
    <StyledInputContainer>
      <Label htmlFor="fee_at_start">Fee at start</Label>
      <Input
        ...
      />
    </StyledInputContainer>
    <StyledInputContainer>
      <Label htmlFor="fee_at_end">Fee at end</Label>
      <Input
        ...
      />
    </StyledInputContainer>
  </StyledColumn>
);

...

return (
  <>
    <StyledCheckboxContainer display="flex" flexWrap="wrap">
      {inputBoxes}
      <TableExample />
    </StyledCheckboxContainer>
    <StyledText>Approximate</StyledText>
  </>
);

另一种方法是保留 InputBoxes 一个组件并将其声明在 外部 BudgetCalculator 并通过所有values/callbacks 作为道具。

示例:

const InputBoxes = ({
  monthlyAmount,
  setMonthlyAmount,
  interestRate,
  setInterestRate,
  deposit,
  setDeposit,
  feeAtStart,
  setFeeAtStart,
  feeAtEnd,
  setFeeAtEnd
}) => {
  return (
    <StyledColumn>
      <StyledInputContainer>
        <Label required htmlFor="monthly_budget">
          Monthly Budget
        </Label>
        <Input
          ...
        />
      </StyledInputContainer>
      <StyledInputContainer>
        <Label required htmlFor="apr">
          APR
        </Label>
        <Input
          ...
        />
      </StyledInputContainer>
      <StyledInputContainer>
        <Label htmlFor="deposit">Deposit</Label>
        <Input
          ...
        />
      </StyledInputContainer>
      <StyledInputContainer>
        <Label htmlFor="fee_at_start">Fee at start</Label>
        <Input
          ...
        />
      </StyledInputContainer>
      <StyledInputContainer>
        ...
        />
      </StyledInputContainer>
    </StyledColumn>
  );
};

function BudgetCalculator() {
  ...

  return (
    <>
      <StyledCheckboxContainer display="flex" flexWrap="wrap">
        <InputBoxes
          {...{
            monthlyAmount,
            setMonthlyAmount,
            interestRate,
            setInterestRate,
            deposit,
            setDeposit,
            feeAtStart,
            setFeeAtStart,
            feeAtEnd,
            setFeeAtEnd
          }}
        />
        <TableExample />
      </StyledCheckboxContainer>
      <StyledText>Approximate</StyledText>
    </>
  );
}