3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【kintone】kintone-ui-component v0→v1でreact非対応に困った際の対処方法

Last updated at Posted at 2024-02-28

お疲れ様です。

今回は、kintoneプラグインの開発時に問題になった「kintone-ui-component v1 が React 非対応」という点について対処した方法について書いてみます。

背景

どういった背景かというと

kintone-ui-component v0 を使ってプラグインを開発していた

プラグインの内部では、react をつかっていた

kintone-ui-component v0 が2023/12/31 でサポート終了

じゃあkintone-ui-component v1 に置き換えよう

『kintone-ui-component v1 は react 非対応です』

 

『kintone-ui-component v1 は react 非対応です』

https://cybozu.dev/ja/kintone/sdk/library/kintone-ui-component-v1/
image.png

 

Oh...

困りごと

kintoneプラグインは react で実装されている

kintone-ui-component v1 を使いたい

kintone-ui-component v1 は react 非対応

v1 に置き換えてみるとビルドエラー
image.png

↑こんな感じで「Its instance type [component名] is not a valid JSX component.」というエラーになる

分析

原因も何もJSX component ではないものをJSX component 内に含めることはできないという理由だ

無理やりkintone-ui-component v1 を JSX component に変換してみようと React.createElementを使って書いてみてもビルドエラーか実行時エラーになる

これはもうreactを諦めて素のTypeScriptに戻すしかないのか
もしくはkintone-ui-component のようなものを自作するか

諦めかけていたがふとした拍子に光明が見えた

解決方法 → @lit/react

kintone-ui-component v1 のソースコードを見ていたところ、
各種Component は KucBase というクラスを拡張していた

KucBase は、LitElement というクラスを拡張していた

LitElement というやつは、ReactiveComponent を拡張していた

ReactiveComponent というのを見て

「Reactと似たような名前なのにReactに対応してないんだな」

「props とかあるのにReactComponentじゃないのか」

と思っていた

LitElement ってなんだろうと思い調べてみると

LitというのはGoogle社製のOSS
https://lit.dev/

シンプルで早くWebComponentを開発できるとのこと

LitとReactの親和性を調べてみると、

「@lit/react」というOSSがあることが判明

@lit/react
https://www.npmjs.com/package/@lit/react

こちらのOSSを使ってラップしてみたところ、Reactで書かれたソースコードからkintone-ui-component v1 を呼び出すことに成功

各種イベントハンドリングもつなげることが可能

Button.tsx

// KUC V1とReactをつなぐために@lit/reactを使ってLitElementをReactComponentに変換するラッパーを用意した

import React from 'react';
import { createComponent } from '@lit/react';
import { Button as KucButton } from 'kintone-ui-component/lib';

export const Button = createComponent({
    tagName: 'kuc-button-1-15-0',
    elementClass: KucButton,
    react: React,
    events: {},
});

kintone-ui-component v1 のボタンをラップしたButton.tsxの実装をおいておきます

tagName のところにkintone-ui-component のバージョンまで入れないとElementをうまく認識してくれずでした
kintone-ui-component のバージョンを上げるとラッパー側のコードも直さなきゃいけないのは微妙...
ここはうまいことバージョンを動的に入れられればいいけどひとまず決め打ち

events は空ですが、デフォルトでonClick などは備えているので呼び出し元からこんな風に書ける

MinusButton.tsx

import { Button } from '../atoms/kintone/Button';
import React, { memo, VFC } from 'react';

type Props = {
    onClick: () => void;
    isDisabled?: boolean;
};

const MinusButton: VFC<Props> = memo(({ onClick, isDisabled = false }) => {
    return (
        <div className="icon-button">
            <Button text="-" onClick={onClick} disabled={isDisabled} />
        </div>
    );
});

export default MinusButton;

他にもラジオボタンのラッパーを実装していたところ
onchange はデフォルトで生えてないようだったので、カスタムイベントとして登録した

RadioButton.tsx

// KUC V1とReactをつなぐために@lit/reactを使ってLitElementをReactComponentに変換するラッパーを用意した

import React from 'react';
import { createComponent } from '@lit/react';
import { RadioButton as KucRadioButton } from 'kintone-ui-component/lib';

export const RadioButton = createComponent({
    tagName: 'kuc-radio-button-1-15-0',
    elementClass: KucRadioButton,
    react: React,
    events: {
        onchange: 'change',
    },
});

events に onchange:'change' と記載する

ラジオボタンの呼び出し元ではちょっと工夫して書く必要あり

RadioButtonField.tsx

import React, { FormEvent, memo, useCallback, useEffect, useReducer, useState, VFC } from 'react';
import { RadioButton } from '../atoms/kintone/RadioButton';

type RadioOption = {
    value: string;
    label: string;
};

type Props = {
    id?: string;
    name: string;
    items: RadioOption[];
    onChange: (name: string, value: string) => void;
    value: string;
    label: string;
    isRequired?: boolean;
    isDisabled?: boolean;
};

export const RadioButtonField: VFC<Props> = memo(
    ({ name, items, onChange, label, value, id, isRequired = false, isDisabled = false }) => {
        const [radioValue, setRadioValue] = useState<string>('');

        useEffect(() => {
            setRadioValue(value);
        }, [value]);

        const handleChange = (event: Event) => {
            const customEvent = event as CustomEvent;
            if (!customEvent.detail) {
                return;
            }
            change(customEvent.detail.value);
        };

        const change = useCallback(
            async (newValue: string) => {
                if (newValue !== value) {
                        setRadioValue(newValue);
                        onChange(name, newValue);
                }
            },
            [name, onChange]
        );

        return (
            <div id={id} className="row-item">
                <div className="label">
                    <div>{label}<span className='required'>{isRequired ? "*" : " "}</span></div>
                </div>
                <RadioButton
                    items={items}
                    value={radioValue}
                    borderVisible={false}
                    itemLayout='vertical'
                    onchange={(event) => {
                        handleChange(event);
                    }}
                    disabled={isDisabled}></RadioButton>
            </div>
        );
    }
);

export default RadioButtonField;

だいぶ雑な感じですが、ひとまずこれでonchange も動きました

こんな風にラッパーを用意することで一通りのComponentをReact上で動作させることに成功

注意点としては、

@lit/react が今後kintone-ui-component v1 で動作するかは保証されていないのでなんらかの拍子に動かなくなる可能性はある

締め

なかば諦めかけていたReact上でのkintone-ui-component v1 の動作ですが、なんとかなりました

ネット上での情報はなかったので同じようなお困りごとの方の助けになればと思いこちらに残しておきます

こちらの記事が役に立った/参考になった/面白かったというかたは👍を押して言っていただけると励みになります!

ここまで読んでいただきありがとうございました

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?