Form์์๋ ํ๋ ์ด์์ ๋ค์ํ ์ ๋ ฅ์ ๋ฐ๊ธฐ ๋๋ฌธ์ ๋ค์ํ๊ณ ๋ง์ ์์ state๋ฅผ ๋ค๋ฃจ๊ฒ ๋๋ค. ํ๋ ์ด์์ ์ ๋ ฅ ๊ฐ์ด ๋ชจ๋ ์ ํจํ๊ฑฐ๋ ๋ชจ๋ ์ ํจํ์ง ์์ ์ ์๋ค. ๋๋ ์๋ฒ๋ก request๋ฅผ ๋ณด๋ธ ํ์ ํน์ ๊ฐ์ด ์ฌ์ฉ ๊ฐ๋ฅํ์ง ํ์ธํ๋ ๋น๋๊ธฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ด์ฉํ๋ ๊ฒฝ์ฐ์๋ ์ ์ก ๋น์ ์ํ๋ฅผ ์ ์ ์์ ์๋ ์๋ค.
์ด๋ ๊ฒ ์ ํจํ์ง ์์ ๊ฐ์ ์ ์ ๊ฐ ์ ๋ ฅํ ๊ฒฝ์ฐ ์ ํจํ ๊ฐ์ ์ ๋ ฅํ ์ ์๋๋ก ํน์ ํ ์ ๋ ฅ๊ฐ์ ๋ํด์ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ๊ณ ๋ฌธ์ ๊ฐ ๋๋ ์ ๋ ฅ๊ฐ์ ๊ฐ์กฐํด์ผ ํ๋ค. ์ด ๊ฒฝ์ฐ์๋ ์ ๋ง ๋ง์ ์ํ๋ฅผ ๋ค๋ฃจ๊ฒ ๋๋ค. ๋ฐ๋ผ์ ์ด๋ฌํ ์ํ๊ด๋ฆฌ์ ๋ํด ๊ฐ๋ตํ๊ฒ ๋ค๋ค๋ณด๊ณ ์ ํ๋ค.
์ฌ์ฉ์์๊ฒ ์๋ฌ๋ฉ์์ง๋ฅผ ๋์ฐ๋ ์๊ฐ์ ํฌ๊ฒ 3๊ฐ์ง๋ก ๋๋์ด๋ณผ ์ ์๋ค.
- ์ฌ์ฉ์๊ฐ form submit ๋ฒํผ์ ๋๋ฅธ ์๊ฐ
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ๋ form์ input์์ focus๊ฐ ์ ์ธ๋ ์๊ฐ
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ๋ ์๊ฐ์๊ฐ (every keystroke)
1. ์ฌ์ฉ์๊ฐ input์ ๊ฐ์ ์ ๋ ฅํ ๋ ๋ง๋ค state์ ์ ์ฅํ๊ธฐ
- ์ฆ๊ฐ์ ์ธ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ํด ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๊ฐ์ด ํค ์ ๋ ฅ๋ง๋ค ํ์ํ ๊ฒฝ์ฐ
- ์ ๋ ฅ๋ ๊ฐ์ ์ด๊ธฐํํ๊ณ ์ถ์ ๊ฒฝ์ฐ
(ref๋ก๋ ๊ฐ๋ฅํ์ง๋ง, DOM์ ์ง์ ์ ์ผ๋ก ์กฐ์ํ๊ฒ ๋๋ฏ๋ก ์ง์ํด์ผํ๋ค)
import {useRef, useState} from 'react';
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState('');
// input์ ๊ฐ ๋ฐ์์ค๊ธฐ
const nameInputChangeHandler = event => {
setEnteredName(event.target.value);
};
// form submitํ ๋ ๋ก์ง
const formSubmissionHandler = event => {
event.preventDefault();
// ์
๋ ฅ๊ฐ ์ด๊ธฐํ
setEnteredName('');
};
return (
<form onSubmit={formSubmissionHandler}>
<input
type='text'
id='name'
onChange={nameInputChangeHandler}
value= {enteredName}
/>
</form>
);
};
export default SimpleInput;
2. ref๋ฅผ ์ฌ์ฉํด์ ์ฌ์ฉ์์ ์ ๋ ฅ์ด ๋๋ฌ์ ๋ ์ ๋ ฅ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๊ฐ์ด ํผ์ด ์ ์ถ๋์์ ๋ ํ ๋ฒ๋ง ํ์ํ ๊ฒฝ์ฐ
import {useRef, useState} from 'react';
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState('');
const nameInputRef = useRef();
const nameInputChangeHandler = event => {
setEnteredName(event.target.value);
};
const formSubmissionHandler = event => {
event.preventDefault();
// submit ๋ฒํผ์ด ๋๋ ธ์ ๋ ์
๋ ฅ๋ ๊ฐ์ ๊ฐ์ ธ์จ๋ค
const enteredValue = nameInputRef.current.value;
};
return (
<form onSubmit={formSubmissionHandler}>
// ref ์ฐ๊ฒฐํ๊ธฐ
<input ref={nameInputRef} type='text' id='name' onChange={nameInputChangeHandler}/>
</form>
);
};
export default SimpleInput;
import { useState } from 'react';
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState('');
// input ์ ํจ์ฑ ๊ฒ์ฆ state
const [enteredNameIsValid, setEnteredNameIsValid] = useState(true);
const nameInputChangeHandler = (event) => {
setEnteredName(event.target.value);
};
const formSubmissionHandler = (event) => {
event.preventDefault();
// input์ ์๋ฌด๋ฐ ๊ฐ์ด ์
๋ ฅ๋์ง ์์ ๊ฒฝ์ฐ
if (enteredName.trim() === '') {
setEnteredNameIsValid(false);
return;
}
setEnteredNameIsValid(true);
setEnteredName('');
};
// invalidํ ๊ฐ์ผ ๊ฒฝ์ฐ css class ๋์ ์ ์ฉ
const nameInputClasses = enteredNameIsValid ? 'form-control' : 'form-control invalid'
return (
<form onSubmit={formSubmissionHandler}>
<div className={nameInputClasses}>
<label htmlFor="name">Y our Name</label>
<input
type="text"
id="name"
onChange={nameInputChangeHandler}
value={enteredName}
/>
{/* ์ ํจ์ฑ ์ฌ๋ถ์ ๋ฐ๋ผ ์๋ฌ ๋ฉ์์ง ๋์ฐ๊ธฐ */}
{!enteredNameIsValid && <p className='error-text'>Name must not be empty</p>}
</div>
<div className="form-actions">
<button>Submit</button>
</div>
</form>
);
};
export default SimpleInput;
์ด ๋ฐฉ์์ ๋ฌธ์ ์ ์ ์ฒ์๋ถํฐ ์๋ฌ๋ฅผ ๋์ฐ์ง ์๊ธฐ ์ํด์ ์ผ๋จ input์ ์
๋ ฅ๋ ๊ฐ์ด valid๋ผ๊ณ ์ค์ ํ๊ณ ์์ํ๋ค๋ ๊ฒ์ด๋ค. ์ด ๋ฐฉ๋ฒ์ ์ผ์ข
์ ์์์์ด์ ํธ๋ฒ์ธ ๋ฐฉ๋ฒ์ด๋ผ ์ง๊ธ์ ๋ฌธ์ ์์ด ์๋ํ์ง๋ง ๋ง์ฝ useEffect()
๋ฅผ ์ฌ์ฉํด์ ์
๋ ฅ๊ฐ์ด ์ ํจํ ๊ฒฝ์ฐ httpRequest๋ฅผ ๋ณด๋ธ๋ค๋ฉด useEffect()
์ ํน์ฑ์ dependencies์ ์๋ฌด๊ฒ๋ ์๋ค๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ ๋ ๋ฌด์กฐ๊ฑด ์คํ๋๋ฏ๋ก, valid์ ์ด๊ธฐ๊ฐ์ true๋ก ์ค์ ํ๊ธฐ ๋๋ฌธ์ ์์ง ์ฌ์ฉ์๊ฐ input์ ์๋ฌด๊ฒ๋ ์
๋ ฅ ์ํ์์๋ ๋ถ๊ตฌํ๊ณ http request๋ฅผ ๋ณด๋ด๋ฒ๋ฆฌ๋ ์ผ์ด ๋ฐ์ํ๋ค.
๋ฐ๋ผ์ input์ด touch ๋์๋์ง ํ์ธํ๋ state๋ฅผ ํ๋ ๋ ์ ์ธํด์ valid state์ ๊ฐ์ด ์ฌ์ฉํ๋ฉด ๋์ฑ ์ข์ ์ฝ๋๊ฐ ๋ ์ ์๋ค!
import { useState } from 'react';
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState('');
// input ์ ํจ์ฑ ๊ฒ์ฆ state
const [enteredNameIsValid, setEnteredNameIsValid] = useState(false);
// input์ ๊ฐ์ ์
๋ ฅํ๋์ง ํ์ธ
const [enteredNameTouched, setEnteredNameTouched] = useState(false);
const nameInputChangeHandler = (event) => {
setEnteredName(event.target.value);
};
const formSubmissionHandler = (event) => {
event.preventDefault();
// form์ submitํ ๊ฒฝ์ฐ๋ input์ ๋ค touchํ ๊ฒ์ผ๋ก ๊ฐ์ฃผ
setEnteredNameTouched(true);
// input์ ์๋ฌด๋ฐ ๊ฐ์ด ์
๋ ฅ๋์ง ์์ ๊ฒฝ์ฐ
if (enteredName.trim() === '') {
setEnteredNameIsValid(false);
return;
}
setEnteredNameIsValid(true);
setEnteredName('');
};
// invalidํ ๊ฒฝ์ฐ = ์
๋ ฅ๊ฐ์ด ์ ํจํ์ง ์์ + ์
๋ ฅ์ฐฝ์ touchํ ๊ฒฝ์ฐ
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
// invalidํ ๊ฐ์ผ ๊ฒฝ์ฐ css class ๋์ ์ ์ฉ
const nameInputClasses = nameInputIsInvalid ? 'form-control invalid' : 'form-control';
return (
<form onSubmit={formSubmissionHandler}>
<div className={nameInputClasses}>
<label htmlFor="name">Y our Name</label>
<input type="text" id="name" onChange={nameInputChangeHandler} value={enteredName} />
{/* ์ ํจ์ฑ ์ฌ๋ถ์ ๋ฐ๋ผ ์๋ฌ ๋ฉ์์ง ๋์ฐ๊ธฐ */}
{nameInputIsInvalid && <p className="error-text">Name must not be empty</p>}
</div>
<div className="form-actions">
<button>Submit</button>
</div>
</form>
);
};
export default SimpleInput;
์ฝ๋๊ฐ ๋ ๊ธธ์ด์ง๊ธด ํ์ง๋ง ์์์น ๋ชปํ ์๋ฌ๋ฅผ ๋ฐฉ์งํ ์ ์๋ค!
1๋ฒ์ ๋ฐฉ๋ฒ์์๋ submit ๋ฒํผ์ ๋๋ฌ์ submithandler
๊ฐ ๋์ํ ๊ฒฝ์ฐ์๋ง ์
๋ ฅ๊ฐ์ด ์ ํจํ์ง ์๋์ง์ ๋ฐ๋ฅธ ์๋ฌ๋ฉ์์ง๋ฅผ ์ ์ ์๊ฒ ๋ณด์ฌ์ค๋ค. ์ด ๊ฒฝ์ฐ์๋ ์ฌ์ฉ์๊ฐ ๋ง์ฝ ์
๋ ฅ ํ ๋ค ์ง์ฐ๊ณ input ์นธ์ ๋น์๋์ ๊ฒฝ์ฐ submit ๋ฒํผ์ ๋๋ฅด๊ธฐ ์ ๊น์ง๋ ์๋ฌ ๋ฉ์์ง๊ฐ ์ถ๋ ฅ๋์ง ์๋๋ค. ์ด๋ ์ฌ์ฉ์๊ฐ ๊ฒฝํํ๊ธฐ์ ์ต์ ์ ๊ฒฝํ์ด๋ผ๊ณ ํ ์ ์๋ค.
๋ฐ๋ผ์ onblur
๋ฅผ ์ฌ์ฉํ์ฌ input์์ focus๊ฐ ์ฌ๋ผ์ก์ ๊ฒฝ์ฐ ์ ํจ์ฑ์ ๋ฐ์ ธ์ ์ฌ์ฉ์์๊ฒ ์๋ฌ๋ฉ์์ง๋ฅผ ๋์์ฃผ๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ณ๊ฒฝํด๋ณด์.
๊ธฐ์กด์ input์ touchํ๋์ง ์ ํ๋์ง๋ฅผ ๋ฐ์ง๋ ๋ก์ง์ nameInputBlurHandler
์ ์ถ๊ฐํ๋ค.
const SimpleInput = (props) => {
...
// focus out ๋์ ๋ input์ ๊ฐ ํ์ธ
const nameInputBlurHandler = (event) => {
setEnteredNameTouched(true);
if (enteredName.trim() === '') {
setEnteredNameIsValid(false);
return;
}
};
return (
<form>
...
<input
type="text"
id="name"
onChange={nameInputChangeHandler}
// focus out ๋์ ๋!
onBlur={nameInputBlurHandler}
value={enteredName}
/>
</form>
)
}
์ด ๊ฒฝ์ฐ์ ๋ฌธ์ ์ ์ ์ฌ์ฉ์๊ฐ ์๋ฌ๋ฉ์์ง๋ฅผ ๋ณด๊ณ ๋ค์ input์ ๊ฐ์ ์ ๋ ฅํ์ฌ validํ ๊ฐ์ด ๋์ด๋ submit๋ฒํผ์ ๋๋ฅด๊ธฐ ์ ๊น์ง๋ ๋์๋ ์๋ฌ๋ฉ์์ง๊ฐ ์์ด์ง์ง ์๋ ๊ตฌ์กฐ์ด๋ค.
๋ฐ๋ผ์ ๋ง์ง๋ง ์ธ๋ฒ์งธ ๋ฐฉ๋ฒ์ธ ๋ชจ๋ keystroke์ ๋ฐ๋ผ ์ ํจ์ฑ์ ํ๋จํ๋ ๋ฐฉ๋ฒ์ ์์ด์ ์ฌ์ฉํ๋ฉด ์ต๊ณ ์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์๋ค. ๋ง์ฝ ๋จ์ง keystroke์๋ง ์์กดํ์ฌ ์ ํจ์ฑ์ ํ๋จํ๋ค๋ฉด ์ฌ์ฉ์์ ๋ชจ๋ ์ ๋ ฅ์ ๋ฐ๋ผ ๊ฒ์ฆ๋จ๊ณ๋ฅผ ๊ฑฐ์น๊ธฐ ๋๋ฌธ์ ์ข์ง ๋ชปํ ๋ก์ง์ด๋ผ๊ณ ํ ์์๋ค. ํ์ง๋ง, 2๋ฒ๊ณผ ๊ฒฐํฉํด์ focus๋ฅผ ์๊ณ , ๋ค์ focus๋ฅผ ๋ฐ์ ํ์ ๋ชจ๋ keystroke์ ๋ฐ์ง๋ค๋ฉด ๋ณด๋ค ํจ์จ์ ์ธ ์ ํจ์ฑ ๊ฒ์ฆ ๋ฐฉ๋ฒ์ด ๋ ์ ์๋ค.
์ ํจ์ฑ์ ๊ฒ์ฆํ๋ ๋ถ๋ถ์ ๋ชจ๋ ์ฝ๋๊ฐ ๋์ผํ๊ธฐ ๋๋ฌธ์ ๋ชจ๋ handler๋ฅผ ์๋๋ก ๋์ผํ ์ ํจ์ฑ ๊ฒ์ฆ ์ฝ๋๋ฅผ ๋ฃ๋ ๊ฒ ๋ณด๋ค, ์ ํจ์ฑ ๊ฒ์ฆ ๋ถ๋ถ์ ํ๋์ ๋ณ์์ ์ ์ฅํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ๋ณด๊ธฐ ์ข์ ์ฝ๋์ด๋ค.
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState('');
const [enteredNameTouched, setEnteredNameTouched] = useState(false);
// ์ ํจ์ฑ ๊ฒ์ฆ๋ถ๋ถ์ ๋ฐ๋ก ๋ณ์์ ์ ์ฅ
const enteredNameIsValid = enteredName.trim() !== '';
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
// keystroke ๋ง๋ค
const nameInputChangeHandler = (event) => {
setEnteredName(event.target.value);
};
// focus๋ฅผ ์์ ๋
const nameInputBlurHandler = (event) => {
setEnteredNameTouched(true);
};
// submit ๋ฒํผ์ ๋๋ ์ ๋
const formSubmissionHandler = (event) => {
event.preventDefault();
setEnteredNameTouched(true);
// input์ ์๋ฌด๋ฐ ๊ฐ์ด ์
๋ ฅ๋์ง ์์ ๊ฒฝ์ฐ
if (!enteredNameIsValid) {
return;
}
setEnteredName('');
setEnteredNameTouched(false);
};
return (...)
}
ํ์ฌ๋ ํ๋์ input์์ ๋ฐ์์ค๋ ๊ฐ์ ๋ํ ์ ํจ์ฑ๋ง์ ํ์ธํ์ง๋ง, ๋ณดํต์ ๊ฒฝ์ฐ์๋ input์ด ์ฌ๋ฌ๊ฐ ์ฌ์ฉ๋๊ณ , ํด๋น form๋ค์ด ๋ชจ๋ ์ ํจํด์ผํ๋ค. ์ ์ฒด ์์์ด ์ ํจํ์ง ์์๋ณด๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น?
- ์ ์ฒด form์ด ์ ํจํ์ง ์๋์ง ํ์ธํ๋ state ์ถ๊ฐ
- ๊ฐ form์ ์ ํจ์ฑ ์ฌ๋ถ๋ฅผ dependency๋ก ์ถ๊ฐํ useEffectํ์ฉ
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState('');
const [enteredNameTouched, setEnteredNameTouched] = useState(false);
// ์ ์ฒด form์ ์ ํจ์ฑ ๊ฒ์ฌ
const [formIsValid, setFormIsValid] = useState(false);
const enteredNameIsValid = enteredName.trim() !== '';
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
// component๊ฐ ์ฒ์ ์คํ๋ ๋, dependency์ ๊ฐ์ด ๋ฐ๋ ๋ ํจ์๊ฐ ์คํ๋จ
// ๋ชจ๋ form์ด validํ์ง ํ์ธํ๋ คํจ!
useEffect(()=> {
if (enteredNameIsValid) {
setFormIsValid(true);
} else {
setFormIsValid(false);
}
}, [enteredNameIsValid]);
return (...)
}
ํ์ง๋ง ์ฌ์ค useEffect
๋ด๋ถ์์ side effect๋ฅผ ์ฒ๋ฆฌํ๋๊ฒ ์๋๋ฏ๋ก useEffect
๋ฅผ ์ฌ์ฉํ ์ด์ ๊ฐ ์๋ค. ๋ฐ๋ผ์ ๋จ์ํ๊ฒ ์ ์ฒด form์ด validํ์ง๋ฅผ boolean๊ฐ์ผ๋ก ์ ์ฅํ๋ ๋ณ์๋ฅผ ๋๊ณ , ํด๋น ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ๋ ๋ชจ๋ form์ valid์ฌ๋ถ๋ฅผ if๋ฌธ์ผ๋ก ๋์ด์ ์ ์ฒด form์ ์ ํจ์ฑ์ฌ๋ถ๋ฅผ ์
๋ฐ์ดํธํ๋ฉด ๋๋ค!
...
const [enteredName, setEnteredName] = useState('');
const [enteredNameTouched, setEnteredNameTouched] = useState(false);
// ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ๋ form๋ค์ ์ ํจ์ฑ์ฌ๋ถ ํ์ธํ๋ ๋ถ๋ถ
const enteredNameIsValid = enteredName.trim() !== '';
const enteredAgeIsValid = enteredName.trim() !== '';
const enteredEmailIsValid = enteredName.trim() !== '';
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
// ์ ์ฒด form์ valid ์ฌ๋ถ ํ์ธ ๋ณ์
let formIsValid = false;
if (enteredNameIsValid && enteredAgeIsValid && enteredEmailIsValid) {
formIsValid = true;
}
์ ํจ์ฑ์ ๊ฒ์ฆํ๋ ๋ถ๋ถ์ด input ํ๊ทธ๋ง๋ค ๋ก์ง์ด ๊ฐ์ผ๋ ์ด ๋ถ๋ถ์ ๊ณ์ํด์ ์์ฑํ๊ธฐ๋ณด๋ค๋ custom Hook
์ผ๋ก ๋ง๋ค์ด์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.
useInput custom Hook ๋ง๋ค๊ธฐ
import { useState } from 'react';
const useInput = (validateValue) => {
const [enteredValue, setEnteredValue] = useState('');
const [isTouched, setIsTouched] = useState(false);
const valueIsValid = validateValue(enteredValue);
const hasError = !valueIsValid && isTouched;
const valueChangedHandler = (event) => {
setEnteredValue(event.target.value);
};
const inputBlurHandler = (event) => {
setIsTouched(true);
};
const reset = () => {
setEnteredValue('');
setIsTouched(false);
}
return {
value: enteredValue,
isValid: valueIsValid,
hasError,
valueChangedHandler,
inputBlurHandler,
reset
};
};
export default useInput;
์ฌ์ฉํ๊ธฐ
import useInput from '../hooks/use-input';
// alias ์ด์ฉํด์ return ๊ฐ๋ค์ name๊ณผ email์ ๊ฐ๊ฐ ์ฌ์ฉํ๊ธฐ!
const SimpleInput = (props) => {
const {
value: enteredName,
isValid: enteredNameIsValid,
hasError: nameInputHasError,
valueChangedHandler: nameChangedHandler,
inputBlurHandler: nameBlurHandler,
reset: resetNameInput,
} = useInput((value) => value.trim() !== '');
const {
value: enteredEmail,
isValid: enteredEmailIsvalid,
hasError: emailInputHasError,
valueChangedHandler: emailChangeHandler,
inputBlurHandler: emailBlurHandler,
reset: resetEmailInput,
} = useInput((value) => value.includes('@') && value.includes('.'));
let formIsValid = false;
if (enteredNameIsValid && enteredEmailIsvalid) {
formIsValid = true;
}
// form submit!
const formSubmissionHandler = (event) => {
event.preventDefault();
// input์ ์๋ฌด๋ฐ ๊ฐ์ด ์
๋ ฅ๋์ง ์์ ๊ฒฝ์ฐ
if (!enteredNameIsValid || !enteredEmailIsvalid) {
return;
}
resetNameInput();
resetEmailInput();
};
// invalidํ ๊ฐ์ผ ๊ฒฝ์ฐ css class ๋์ ์ ์ฉ
const nameInputClasses = nameInputHasError ? 'form-control invalid' : 'form-control';
const emailInputClasses = emailInputHasError ? 'form-control invalid' : 'form-control';
return (...์๋ต);
};
export default SimpleInput;