final-form / react-final-form

🏁 High performance subscription-based form state management for React
https://final-form.org/react
MIT License
7.38k stars 480 forks source link

Suggestion: Create `useCalculatedFieldValue` to avoid unnecessary re-renders for calculated fields #643

Open NateRadebaugh opened 5 years ago

NateRadebaugh commented 5 years ago

Are you submitting a bug report or a feature request?

Feature request

What is the current behavior?

Currently, if we want a calculated value from a field name, the useField will trigger any time the field changes. In cases where we useField on an array and may only want to retrieve calculated count or specific filtered indices, these extra rerenders are detrimental and also cause fields to lose focus.

What is the expected behavior?

Expose a new hook to only trigger a new render when the calculated value changes instead of every time any inner value changes.

Sandbox

Here is a code sandbox of a workaround my team is using:

https://codesandbox.io/s/kind-jang-m62pd

function SyncCalculatedValue({ calculate, name, onChange }) {
  const { input } = useField(name);
  const { value } = input;
  const calculatedValue = calculate(value);
  const prev = usePrevious(calculatedValue);

  useEffect(() => {
    if (calculatedValue !== prev) {
      onChange(calculatedValue);
    }
  }, [calculatedValue, prev, onChange]);

  return null;
}
function Count2() {
  const [numFields, setNumFields] = useState(undefined);

  console.log("rendering Count2");

  return (
    <>
      <SyncCalculatedValue
        name="test.testFields"
        calculate={testFields => {
          if (!Array.isArray(testFields)) {
            return 0;
          }

          return testFields.length;
        }}
        onChange={setNumFields}
      />
      <strong>Count2:</strong> {numFields}
    </>
  );
}
abrad45 commented 4 years ago

This looks like something I'm running into right now. I created a hook called useFieldState:

const useFieldValue = fieldName => {
  const {
    input: { value },
  } = useField(fieldName);

  return value;
};

...but since this is called every time any aspect of FieldRenderProps changes (including meta.active), it's far noisier than I need it to be. Something like what either of us described would be really useful.