Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom field component value not retained across renders #624

Closed
GeorgeyB opened this issue Jun 1, 2022 · 11 comments
Closed

Custom field component value not retained across renders #624

GeorgeyB opened this issue Jun 1, 2022 · 11 comments

Comments

@GeorgeyB
Copy link
Contributor

GeorgeyB commented Jun 1, 2022

Hi guys, I'm trying to swap out a field component, but the value doesn't seem to be retained between renders. Any ideas? Here's a component I'm using in its simplest form:

import React from 'react';
import { withCondition, useField } from 'payload/components/forms';
import { TextField } from 'payload/dist/fields/config/types';

function TestField({ path }: TextField & { path?: string }) {
    const { value, setValue } = useField<string>({
        path: path || ''
    });

    return (
        <input
            className="border py-1 px-4"
            type="text"
            value={value}
            onChange={({ target: { value } }) => setValue(value)}
        />
    );
}

export default withCondition(TestField);

As soon as a rerender occurs (in this case, my cursor leaves the parent array field) the value is lost.

@GeorgeyB GeorgeyB added the bug label Jun 1, 2022
@jmikrut
Copy link
Member

jmikrut commented Jun 1, 2022

Hey @GeorgeyB — this is strange. Do you have any other fields that may be impacting the data of this specific field's path? At first glance, it doesn't seem like there's anything wrong with your custom field.

We will test around with custom fields within array fields, but we've got a lot of these in use across many projects and haven't seen this specific issue. If you use this exact field at the top level of the document, does the problem persist? What if you use it within a group? Does the problem only present itself when you use this component within an array field?

Let us report back shortly.

@GeorgeyB
Copy link
Contributor Author

GeorgeyB commented Jun 1, 2022

@jmikrut Good catch. The issue doesn't seem to occur when the component is for a top-level field - though even at the top level, the value seems to clear after an arbitary amount of time and then never again. Here's a screen recording to try and demonstrate:

firefox_K8wH2ea5kN.mp4

@DanRibbens
Copy link
Contributor

I was able to reproduce the issue. The bug was introduced when we changed withCondition to no longer be something that is imported from Payload and added to project code, only to be used again when Payload builds.

In your code @GeorgeyB export default withCondition(TestField); can be simplified to export default TestField; and it will be added in to Payload at build time so developers don't have to think about it.

As an aside, having path: path || '' instead of path. I was seeing a warning from react in the console when that was changing.

@GeorgeyB
Copy link
Contributor Author

GeorgeyB commented Jun 4, 2022

Thanks @DanRibbens!

I can confirm that this works as expected with your fix.

@GeorgeyB GeorgeyB closed this as completed Jun 4, 2022
@GeorgeyB
Copy link
Contributor Author

GeorgeyB commented Jun 4, 2022

Hi @DanRibbens,

this change seems to prevent any native React hooks from functioning correctly. Using this minimal example:

import React, { ChangeEventHandler, useCallback } from 'react';
import { useField } from 'payload/components/forms';

function TestField({ path, name }: any) {
    const { value, setValue } = useField<string>({
        path: path || name
    });

    const onChange: ChangeEventHandler<HTMLInputElement> = useCallback(
        ({ target: { value } }) => setValue(value),
        [setValue]
    );

    return (
        <input
            className="border py-1 px-4"
            type="text"
            value={value || ''}
            onChange={onChange}
        />
    );
}

export default TestField;

Throws a dispatcher is null error on the client:

image

The same error occurs with other native hooks too due to React's resolveDispatcher returning null.

@GeorgeyB GeorgeyB reopened this Jun 4, 2022
@DanRibbens
Copy link
Contributor

DanRibbens commented Jun 6, 2022

Hey @GeorgeyB I can't reproduce this in demo using your exact component on a conditional field in a collection.

I suspect that your package.json has an issue with 1. You might have mismatching versions of React and the renderer
We upgraded Payload to use React 18 recently so any other dependencies you have need to be on 18.

Would you mind sharing a repo that recreates this or a branch from Payload that shows it in demo for me to see?

edit: you also can run into the issue when you link payload on a project as you end up with more than one react in node_modules. You can add an alias for react in webpack as a workaround but it is annoying to deal with.

@GeorgeyB
Copy link
Contributor Author

Hi @DanRibbens, apologies for the delay coming back to you. React hooks are working as expected, but I'm still experiencing the initial issue whereby values are being lost between renders. I've been able to reproduce this in a completely fresh Payload install:

  1. Initialise a new install with the npx create-payload-app command
  2. Uncomment the "Examples" collection import and configuration item in payload.config.ts
  3. Use the following config for the Examples collection (I've added the testFields array field and a testField sub field inside it):
import { CollectionConfig } from 'payload/types';

import CustomTextField from './CustomTextField';

// Example Collection - For reference only, this must be added to payload.config.ts to be used.
const Examples: CollectionConfig = {
    slug: 'examples',
    admin: {
        useAsTitle: 'someField'
    },
    fields: [
        {
            name: 'someField',
            type: 'text'
        },
        {
            name: 'testFields',
            type: 'array',
            fields: [
                {
                    name: 'testField',
                    type: 'text',
                    admin: {
                        components: {
                            Field: CustomTextField
                        }
                    }
                }
            ]
        }
    ]
};

export default Examples;

Where CustomTextField is essentially the example provided in the Payload documentation:

import { useField } from 'payload/components/forms';

const CustomTextField = ({ path }) => {
  const { value, setValue } = useField({ path });

  return (
    <input
      onChange={(e) => setValue(e.target.value)}
      value={value || ''}
    />
  )
}

The value shows in the input until the parent array field rerenders, then it is lost.

It's worth noting that even if testField is a top-level field, the value is still lost after some arbitary time/event, but only once (as shown in the video in this comment)

@DanRibbens
Copy link
Contributor

Hey @GeorgeyB I haven't published the fix yet in the latest version yet. There were some other big changes merged at the same time and we wanted to do some additional QA on it first. I'll update again when its released.

@jmikrut
Copy link
Member

jmikrut commented Jun 14, 2022

@GeorgeyB — the fix is now deployed in 0.18.0.

Will you give it a shot, and if it resolves the problem you're facing, will you close this issue?

@GeorgeyB
Copy link
Contributor Author

@jmikrut, @DanRibbens.

Looking good now, thanks guys.

Copy link

github-actions bot commented Sep 8, 2024

This issue has been automatically locked.
Please open a new issue if this issue persists with any additional detail.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 8, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants