@romaro

Как создать один компонент из другого и управлять им?

У меня есть кнопка логаута:
631c85adc42c1821801187.jpeg

По клику на ней должен выкатываться оверлей и закатываться обратно, если пользователь нажимает на кнопку отмены:
631c8636867e6186837988.jpeg

Реализовать это на чистом JS совершенно плевое дело, а вот с реактом не совсем ясно.

Напрашивается решение с useContext, но смущает, что у компонентов есть разные точки монтирования (у меня SSR и каркас разметки формируется на сервере). Я попробовал реализовать таким образом, чтобы один компонент рендерил другой и, судя по варнингу реакта, это в корне не верный путь:
Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.
.

На всякий случай приведу здесь код черновика.

Сам оверлей определяет высотку корневого элемента, в который он должен монтироваться и, в зависимости от переданного параметра, смещается вверх:
import { useState, useEffect } from 'react';

export interface PropsOverlay {
    /**
     * Элемент, в который будет монтироваться оверлей.
     * Должен иметь position: relative
     */
    rootElementId: string;
    isHidden: boolean;
    content: JSX.Element;
}

export function Overlay(props: PropsOverlay) {
    const [elHeight, setHeight] = useState<number>();

    useEffect(() => {
        const el = document.getElementById(props.rootElementId);
        if (el) {
            setHeight(el.offsetHeight);
        }
    }, []);

    const hiddenStyle = {
        position: 'absolute',
        height: elHeight + 'px',
        width: '100%',
        top: `-${elHeight}px`,
        backgroundColor: 'aliceblue',
        visibility: 'visible',
    } as React.CSSProperties;

    const shownStyle = {
        ...hiddenStyle,
        top: 0,
    };

    return <div style={props.isHidden ? hiddenStyle : shownStyle}>{props.content}</div>;
}


Затем я написал компонент с кнопкой, по которой оверлей должен закрываться (не закрывается):
interface PropsLogoutOverlayContent {
    chancelButtonClickHandler: () => void;
}

export function LogoutOverlayContent(props: PropsLogoutOverlayContent) {
    return (
        <div className="_content">
            <button onClick={props.chancelButtonClickHandler}>Отмена</button>
            <button>Выйти</button>
        </div>
    );
}


И наконец вызываю все это из кнопки логаута:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import { LogoutOverlayContent } from './LogoutOverlayContent';
import { Overlay } from './Overlay';

export function LogoutInitButton() {
    const [isHidden, setHidden] = useState(true);

    const toggle = () => {
        setHidden(!isHidden);
    };

    useEffect(() => {
        const container = document.getElementById('LogoutOverlay');
        if (container) {
            const root = createRoot(container);
            root.render(
                <Overlay
                    isHidden={isHidden}
                    content={
                        <LogoutOverlayContent
                            // Это не сработает, окно не закроется
                            chancelButtonClickHandler={toggle}
                        />
                    }
                    rootElementId="LogoutOverlay"
                />,
            );
        }
    }, []);

    return (
        <button className="LogoutInitButton" onClick={toggle}>
            X
        </button>
    );
}


То есть получилась жесть, когда компонент перемонтируется при каждом нажатии на логаут, а кнопка закрытия не работает.

Я правильно понимаю, что для подобной задачи вложенный рендеринг это неверный путь и правильным вариантом будет:
1) создать контекст страницы (например, при помощи MobX) и увязать все три компонента на этот контекст (оверлей, контент оверлея и кнопка логаута)
2) дважды вызывать функцию рендеринга, т.к. у меня две точки монтирования: кнопка логаута находится в одном DOM-узле, а оверлей в другом.

UPD. Пока готовил вопрос, пришла мысль, чтобы не городить контекст ради одного параметра isHidden, попробовать вынести состояние в кастомный useOverlayPosition(), который и дергать по onClick. Хук будет позиционировать оверлей через модификацию CSSOM. Опять же не ясно, насколько это согласуется с React best practices.
  • Вопрос задан
  • 182 просмотра
Пригласить эксперта
Ответы на вопрос 1
@limpch
Сложный вопрос, отвечу как понял))
У тебя где-то хранится состояние когда пользователь залогинен, а когда нет? Через что ты контролируешь это?

Если как ты указал выше, mobX, то в стейте создай еще одно поле по типу logoutConfirm и меняй еще состояние. При нажатии на выход, меняй состояние logoutConfirm на pending например, и выводи этот блок с подтверждением. При нажатии меняй logoutConfirm на false например, если нажали нет и ее нужно держать в скрытом состоянии, и на true, если согласились и делай выход.

Я думаю это можно сделать только с помощью mobX, redux или useContext
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы