Skip to content

Instantly share code, notes, and snippets.

@kawaz
Last active February 10, 2025 03:37
Show Gist options
  • Save kawaz/ec938f21d9b937c09900c48b4400ab48 to your computer and use it in GitHub Desktop.
Save kawaz/ec938f21d9b937c09900c48b4400ab48 to your computer and use it in GitHub Desktop.
React等が利用されているとinputやtextareaのvalueに値をセットしてもWEBアプリ側に変更が伝わらないことがあるが、それを上手くやる。
/**
* React等が利用されているサイトでは value プロパティに値をセットしてもWEBアプリに変更が伝わらないことがあるが、それを上手くやる。
* valueやcheckedプロパティを変更する際はフレームワーク側で差し替えられてるケースの対応として、ネイティブパーツのsetterを直接使って値を書き換えるのが確実
*
* @param {string | Element | Array<string | Element>} target - 値を設定する対象の要素。文字列(セレクタ)、Element、またはそれらの配列
* @param {string | boolean} value - 設定する値。input/textareaの場合は文字列、checkboxの場合は真偽値でもOK
* @param {boolean} [immediate=false] - trueの場合は即座に値を更新、falseの場合は次のイベントループで更新(特別な理由がない限りはfalseでOK)
*/
function setValue(
target,
value,
immediate = false
) {
if (Array.isArray(target)) {
for (const t of target) {
setValue(t, value, immediate)
}
return
}
if (typeof target === 'string') {
setValue([...document.querySelectorAll(target)], value, immediate)
return
}
if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) {
return
}
const f = () => {
let changed = false
switch (target.type) {
case 'radio':
case 'checkbox': {
if(! (target instanceof HTMLInputElement)) {
return
}
const newCheckedValue = (typeof value === 'boolean') ? value : target.value == value
// 現在の checked 状態と異なる場合のみ更新
if (target.checked !== newCheckedValue) {
const checkedDescriptor = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(target),
"checked"
)
if (checkedDescriptor?.set) {
checkedDescriptor.set.call(target, newCheckedValue)
} else {
target.checked = newCheckedValue
}
changed = true
}
break
}
default: {
// 現在の value と異なる場合のみ更新
if (target.value !== value) {
const valueDescriptor = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(target),
"value"
)
if (valueDescriptor?.set) {
valueDescriptor.set.call(target, value)
} else {
target.value = value
}
changed = true
}
}
}
// 値が実際に変更された場合のみイベントを発火
if (changed) {
target.dispatchEvent(Object.assign(new Event('input', { bubbles: true }), { simulated: true }))
target.dispatchEvent(Object.assign(new Event('change', { bubbles: true }), { simulated: true }))
target.dispatchEvent(Object.assign(new Event('blur', { bubbles: true }), { simulated: true }))
}
}
// イベントリスナ内から入れ子でイベントを発生さてしまうことを避けるため、
// 特別理由がなければ次のイベントループで実行させる。
if (immediate) {
f()
} else {
setTimeout(f, 0)
}
}
/**
* React等が利用されているサイトでは value プロパティに値をセットしてもWEBアプリに変更が伝わらないことがあるが、それを上手くやる。
* valueやcheckedプロパティを変更する際はフレームワーク側で差し替えられてるケースの対応として、ネイティブパーツのsetterを直接使って値を書き換えるのが確実
*
* @param {string | Element | Array<string | Element>} target - 値を設定する対象の要素。文字列(セレクタ)、Element、またはそれらの配列
* @param {string | boolean} value - 設定する値。input/textareaの場合は文字列、checkboxの場合は真偽値でもOK
* @param {boolean} [immediate=false] - trueの場合は即座に値を更新、falseの場合は次のイベントループで更新(特別な理由がない限りはfalseでOK)
*/
export function setValue(
target: string | Element | Array<string | Element>,
value: string | boolean,
immediate = false
): void {
if (Array.isArray(target)) {
for (const t of target) {
setValue(t, value, immediate)
}
return
}
if (typeof target === 'string') {
setValue([...document.querySelectorAll(target)], value, immediate)
return
}
if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) {
return
}
const f = () => {
let changed = false
switch (target.type) {
case 'radio':
case 'checkbox': {
if(! (target instanceof HTMLInputElement)) {
return
}
const newCheckedValue = (typeof value === 'boolean') ? value : target.value == value
// 現在の checked 状態と異なる場合のみ更新
if (target.checked !== newCheckedValue) {
const checkedDescriptor = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(target),
"checked"
)
if (checkedDescriptor?.set) {
checkedDescriptor.set.call(target, newCheckedValue)
} else {
target.checked = newCheckedValue
}
changed = true
}
break
}
default: {
// 現在の value と異なる場合のみ更新
if (target.value !== value) {
const valueDescriptor = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(target),
"value"
)
if (valueDescriptor?.set) {
valueDescriptor.set.call(target, value)
} else {
target.value = value as string
}
changed = true
}
}
}
// 値が実際に変更された場合のみイベントを発火
if (changed) {
target.dispatchEvent(Object.assign(new Event('input', { bubbles: true }), { simulated: true }))
target.dispatchEvent(Object.assign(new Event('change', { bubbles: true }), { simulated: true }))
target.dispatchEvent(Object.assign(new Event('blur', { bubbles: true }), { simulated: true }))
}
}
// イベントリスナ内から入れ子でイベントを発生さてしまうことを避けるため、
// 特別理由がなければ次のイベントループで実行させる。
if (immediate) {
f()
} else {
setTimeout(f, 0)
}
}
// ネイティブセッターを使う方法で問題なければそちらの方が良いが一応メモとして別のやり方も残しておく
// Reactの内部実装スペシャル対応の謎更新手順
if(target._valueTracker && target._valueTracker.setValue) {
const oldValue = target.value
target.value = value
target._valueTracker.setValue(oldValue)
} else {
target.value = value
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment