useSelect в колбэке события – не получает значение вовремя

Вопрос или проблема

Я попытался использовать useSelect в колбэке события, как описано в руководстве блоков, однако я получаю undefined, так как значение получено позже, чем мне нужно. Я полагаю, что мне нужна какая-то дополнительная асинхронная функция, чтобы поймать значение, когда оно будет получено, или существует какой-то другой механизм, который не описан здесь?

Обратите внимание, что я не использую useSelect в классическом режиме с колбэком, потому что значение не может быть получено при обновлении компонента, а только при действии пользователя.

Этот код должен проверять, когда выбран определенный тип публикации, затем получить его выделенное изображение, в противном случае предоставить пользователю возможность выбрать изображение. В настоящее время я только записываю в консоль, потому что отлаживаю это.

SingleImage — это компонент, который позволяет пользователю выбрать и заменить изображение.

import { useBlockProps, URLInput } from '@wordpress/block-editor';
import SingleImage from '../../components/singleImage.jsx'
import { useSelect } from '@wordpress/data';

import './editor.scss';

export default function Edit({ attributes, setAttributes }) {
    const { getEntityRecord } = useSelect('core');
    const urlInputSetter = (url, post) => {
        if (post && ! attributes.imgID) {
            if (post.type === 'virtualne-izlozbe') {
                const record = getEntityRecord('postType', 'virtualne-izlozbe', post.id );
                if (record)
                    console.log(record.featured_media);
                else
                    console.log('empty');
            }
        }
        setAttributes( { link: url, postTitle: (post && post.title) } )
    }

    return (
        <div { ...useBlockProps() }>
            <URLInput
                value={ attributes.link }
                onChange={ urlInputSetter }
                label="Ссылка на публикацию"
                className="post-link"
            />
            { attributes.postTitle && <div className="post-title">Изображение ведет на публикацию { attributes.postTitle }</div> }
            <SingleImage
                imgID={attributes.imgID}
                imgURL={attributes.imgURL}
                alt="Изображение, которое ведет на публикацию"
                setImgIdAndUrl={(imgID, imgURL) => setAttributes({imgID, imgURL})}
                title="Выбрать изображение"
                help="Изображение, которое ведет на публикацию"
            />

        </div>
    );
}

В документации, которую вы опубликовали, сказано:

Не используйте useSelect таким образом, когда вызываете селекторы в функции рендера, потому что ваш компонент не будет перерисовываться при изменении данных.

import { useSelect } from '@wordpress/data';
 
function Paste( { children } ) {
    const { getSettings } = useSelect( 'my-shop' );
    function onPaste() {
        // Сделать что-то с настройками.
        const settings = getSettings();
    }
    return <div onPaste={ onPaste }>{ children }</div>;
}

Что в основном соответствует тому, как вы использовали, и поэтому ваш компонент перерисовывается при изменении.

Вы можете использовать хуки react (useState, useEffect), чтобы решить эту проблему. См. код ниже. Я прокомментировал код, чтобы объяснить изменения. (код не тестировался)

import { useBlockProps, URLInput } from '@wordpress/block-editor';
import SingleImage from '../../components/singleImage.jsx';
import { useSelect } from '@wordpress/data';
const { useState, useEffect } = wp.element;
import './editor.scss';

export default function Edit({ attributes, setAttributes }) {
    // Подготовка состояния
    const [post, setPost] = useState();

    // useSelect с колбэком, обратите внимание на третий параметр
    const record = useSelect(
        (select) => {
            return select('core').getEntityRecords(
                'postType',
                'virtualne-izlozbe',
                post.id
            );
        },
        // Это массив зависимостей, каждый раз, когда состояние
        // "post" изменяется, будет вызван колбэк useSelect.
        [post]
    );

    // Выполнение действий при изменении состояния
    useEffect(() => {
        // изменение значения record, сделать что-то
        if (record) {
            console.log(record.featured_media);
        } else {
            console.log('пусто');
        }
        // Массив зависимостей, каждый раз, когда record изменяется, колбэк useEffect
        // будет вызван
    }, [record]);

    const urlInputSetter = (url, urlPost) => {
        if (urlPost && !attributes.imgID) {
            if (urlPost.type === 'virtualne-izlozbe') {
                // Изменение состояния, чтобы колбэк useSelect был вызван
                setPost(urlPost);
            }
        }
        setAttributes({ link: url, postTitle: post && post.title });
    };

    return (
        <div {...useBlockProps()}>
            <URLInput
                value={attributes.link}
                onChange={urlInputSetter}
                label="Ссылка на публикацию"
                className="post-link"
            />
            {attributes.postTitle && (
                <div className="post-title">
                    Изображение ведет на публикацию {attributes.postTitle}
                </div>
            )}
            <SingleImage
                imgID={attributes.imgID}
                imgURL={attributes.imgURL}
                alt="Изображение, которое ведет на публикацию"
                setImgIdAndUrl={(imgID, imgURL) =>
                    setAttributes({ imgID, imgURL })
                }
                title="Выбрать изображение"
                help="Изображение, которое ведет на публикацию"
            />
        </div>
    );
}

Ответ или решение

Решение проблемы использования useSelect в колбэке события в React

При работе с хук-формами в среде WordPress, разработчики нередко сталкиваются с проблемой, когда значения, получаемые с помощью useSelect, не обновляются вовремя, особенно в случае обработки событий, таких как ввод пользователя. Рассмотрим ваш случай, в котором вы пытаетесь использовать useSelect для получения записи поста и его актуального значения, что приводит к неопределённому результату. Вот подробный подход к решению этой проблемы.

Контекст задачи

Ваш код использует компонент <URLInput /> для обновления ссылки на пост, который по выбору поста должен извлекать изображение поста, если тип поста 'virtualne-izlozbe'. Однако getEntityRecord из useSelect вызывается внутри колбэка, что будет иметь место только при первом рендере, что приводит к тому, что значение в логах оказывается неопределенным.

Проблемы с текущей реализацией

  1. Использование useSelect вне его контекста рендеринга: useSelect должен вызываться в самом компоненте или в колбэке, который подписан на соответствующие изменения состояния. В вашем случае вы вызываете его в колбэке события, что приводит к тому, что изменений состояния не происходит.

  2. Асинхронный доступ к данным: Из-за того что запрос к данным может занять некоторое время, вызов setState для получения значения может привести к несоответствиям.

Оптимизированное решение

Чтобы правильно реализовать обновление ваших данных и избежать описанных проблем, используйте состояние и эффектные хуки (useState, useEffect).

Пример кода с объяснениями:
import { useBlockProps, URLInput } from '@wordpress/block-editor';
import SingleImage from '../../components/singleImage.jsx';
import { useSelect } from '@wordpress/data';
const { useState, useEffect } = wp.element;
import './editor.scss';

export default function Edit({ attributes, setAttributes }) {
    const [post, setPost] = useState();

    // Используем useSelect для получения записи поста
    const record = useSelect(
        (select) => {
            return select('core').getEntityRecord('postType', 'virtualne-izlozbe', post ? post.id : null);
        },
        [post] // Зависимость от состояния post
    );

    // Используем useEffect для обработки изменений записи
    useEffect(() => {
        if (record) {
            console.log(record.featured_media);
        } else {
            console.log('empty');
        }
    }, [record]); // Срабатывает при изменении record

    const urlInputSetter = (url, urlPost) => {
        if (urlPost && !attributes.imgID) {
            if (urlPost.type === 'virtualne-izlozbe') {
                setPost(urlPost); // Сохраняем пост в состояние
            }
        }
        setAttributes({ link: url, postTitle: urlPost ? urlPost.title : undefined});
    };

    return (
        <div {...useBlockProps()}>
            <URLInput
                value={attributes.link}
                onChange={urlInputSetter}
                label="Link na objavu"
                className="post-link"
            />
            {attributes.postTitle && (
                <div className="post-title">
                    Ссылка ведёт на объект {attributes.postTitle}
                </div>
            )}
            <SingleImage
                imgID={attributes.imgID}
                imgURL={attributes.imgURL}
                alt="Изображение для ссылки"
                setImgIdAndUrl={(imgID, imgURL) => setAttributes({ imgID, imgURL })}
                title="Выберите изображение"
                help="Изображение для ссылки"
            />
        </div>
    );
}

Объяснение изменений:

  1. Использование состояния: Добавление состояния post позволяет вам сохранять данные поста и повторно их использовать при необходимости.

  2. Изменение useSelect: Теперь useSelect следит за изменением состояния post, что позволяет компоненту React повторно рендерить, когда данные о посте обновляются.

  3. Эффекты: useEffect отслеживает изменения в record, позволяя асинхронно обрабатывать данные без блокировки основного потока выполнения.

Заключение

Данное решение устраняет проблемы с асинхронностью и обеспечит корректное получение данных. Использование хуков useState и useEffect в сочетании с useSelect позволяет эффективно управлять данными и состоянием, соответствуя лучшим практикам разработки в React и WordPress.

Оцените материал
Добавить комментарий

Капча загружается...