[ReactJs- Component] Giải pháp cho Form có rất nhiều thẻ input
E đang gặp phải trường hợp Form có nhiều trường có thẻ input
như trong ví dụ dưới đây :
https://codesandbox.io/s/vjwy1q4p25
(e tạm thời ví dụ vậy , thực tế có thể phát sinh thêm nhiều trường hơn)
Với mỗi thẻ input
như vậy e cần gắn sự kiện onChange
để set lại giá trị của trường đó trong State
khi người dùng nhập vào.
Vì có nhiều thẻ input
quá nên việc viết sự kiện này bị lặp lại code (10 thẻ input => 10 cái sự kiện onChange
giống nhau)
Nếu e tách riêng thẻ input thành một Component là InputBox
, rồi gọi lại trong component Form
thì chỉ cần viết sự kiện onchange
một lần, tuy nhiên bên component cha với mỗi component InputBox
em lại phải truyền call back
thì mới lấy được giá trị bên input
kia. Tóm lại là cũng phải viết thêm ngần ấy cái callback.
Nói chung lợi đằng này lại thiệt đằng kia mà xem chừng cách tách ra còn phức tạp hơn.
Nên e muốn hỏi mọi người xemi có cách nào tối ưu công việc này không ạ.
2 ANSWERS
đầu tiên e nên cbi trước các component dạng View trong form ví dụ Input, Textarea, Select, RangeSlider, .... tất cả mọi thứ mà trong form sẽ có. thứ 2 là tạo 1 mảng hoặc mảng này fetch từ server về sẽ có dạng như sau ví dụ:
bây giờ map mảng data trên kia ra để render ra component và sẽ được check theo type của phần tử trong mảng để render ra các Component View tạo trên và khi thực hiện các hàm lấy giá trị của các Component View ( ví dụ onChange,.... ) thì sẽ lưu vào state ví dụ với object là results và các key là name của phần tử trong mảng ví dụ:
Khi này màn hình sẽ hiển thị các component y chang như mảng kia. và nó sẽ là form động cho phép setting từ admin của web và bên ngoài client sẽ hiển thị như vậy luôn
Cám ơn anh nhiều ạ. Cách này thực sự trong sáng và dễ hiểu ạ )
@hpvanlong à anh ơi, anh cho e hỏi thêm câu này nữa với ạ: giả sử ban đầu e khởi tạo state thế này:
state = {
name : "",
age: "",
email: "",
gender : "",
....vv
}
Sau đó em lấy tất cả các giá trị trong state
ra và gán vào thuộc tính value
cho mỗi trường tương ứng.
Khi người dùng thay đổi một một trường bất kỳ => gọi đến sự kiện onChange () => gọi hàm set lại State để cập nhật giá trị mới mà người dùng nhập vào trường đó. Nhưng như vậy khi state được set lại thì code render() lại toàn bộ cho dù các trường kia người dùng không thực hiện thay đổi gì cả. Đây là hệ quả tất yếu, bắt buộc phải vậy hả anh , hay mình có cách khác để khắc phục nó ạ
@wiliamfeng nên nhớ các Component Form View kia e phải để làm PureComponent nhé. chưa biết thì đọc thêm về PureComponent, shouldComponentUpdate, shallow compare trong react nhé. Và nên nhớ lúc đầu e nên tạo giá trị mặc định bằng cách biến đổi cái mảng ví dụ data như của a ở trên ra để setState mặc định nhé
khi componentDidMount chạy results sẽ ra default như sau
cái này e vẫn chưa thật hiểu. Về lifecycle shouldComponentUpdate
thì em biết nó dùng để check xem có nên render()
lại một component
hay không - tuy nhiên trường hợp sử dụng nó là khi một component
cha thay đổi dẫn đến việc render() lại một component
con một cách không cần thiết => khi đó ta mới dùng shouldComponentUpdate
để kiểm tra thông qua cái này :
shouldComponentUpdate(nextProps, nextState) {
return this.state.value != nextState.value;
}
tức là ta kiểm tra state
hiện tại với state
kế tiếp có khác nhau hay không.Nếu khác mới cho render()
lại. Còn về PureComponent
thì e chưa đọc nhiều nhưng hình như nó là dùng để mình bớt phải sử dụng đến shouldComponentUpdate
.
Còn đối với câu hỏi trong trường hợp trên của e thì không có quan hệ cha - con trong component
vì ở đây chỉ có duy nhất một component
Form thôi. Và khi người dùng thay đổi giá trị ở 1 trường nào đó => bắt buộc phải set lại giá trị của trường đó trong state
=> vì vậy state chắc chắn bị thay đổi => nên khó tránh khỏi việc render() lại toàn bộ Form lắm => e không biết là có cách nào hạn chế việc đó ko ?
Thêm một ý nữa là trong ví dụ của anh ở trên là anh thực hiện việc setState
trong componentDidMount()
=> làm cho component render() thêm lần nữa => trong khi việc này đâu cần thiết vì mình có thể khởi tạo giá trị cho các trường trong state
ngay từ đầu cũng được mà ?
E cũng chỉ đưa ra một vài ý kiến trên sự hiểu biết cá nhân của mình thôi. Có chỗ nào sai xót mong anh giảng giải thêm ạ . Tks anh nhiều !
@wiliamfeng nếu Form của e chỉ cần lấy kết quả khi bấm Submit thì có lẽ kết qủa đó e k nên lưu vào state vì trong trường hợp này a nghĩ Form có cần phải render lại để làm gì đâu. Ở đây a vẫn cho tất cả là PureComponent vì trong các component ở đây k có cái nào có tình trạng renderProps nên theo a cứ để PureComponent đi. NẾU RESULT Ở ĐÂY LÀ STATE nó vẫn sẽ render tất cả mặc dù mình đang dùng PureComponent do props của nó là onChange đang reference tới 1 hàm _handleInputTextChange là hàm HOF và nó bị thay đổi điều này khiến InputText vẫn sẽ re-render nên trong trường hợp này có thể để InputText là component rồi trong shouldComponentUpdate so sánh nextState.value !== this.state.value thì mới cho render vì value này cũng trùng với value truyền qua hàm onChange. NHƯNG THEO A TRONG TRƯỜNG HỢP FORM KHÔNG THAY ĐỔI THÌ K NÊN DÙNG STATE
Ở đây a ví dụ có 1 InputText tính toán cái gì đó ở đây a ví dụ đơn giản chỉ trả ra value ở hàm onChange. 1 Form nhận vào data và trả ra result ở hàm onSubmit là kết quả cuối cùng của form này.
Còn trường hợp vì sao a lại lấy default mà không khởi tạo luôn vì a đang ví dụ data đó từ server trả về và e đâu biết có những trường nào đâu mà tự nhập sẵn trước được
Đây là demo e bật console lên xem nhé. khi e thay đổi InputText của cái nào thì cái đó render thôi xong rồi submit thì vẫn ra kết quả nhé
https://codesandbox.io/s/n439n6p7p
Thực ra vẫn còn 1 cách nữa mà vẫn dùng state. nhưng comment dài quá nên thôi
e cám ơn anh nhiều ạ. Anh cho e hỏi , câu này : THEO A TRONG TRƯỜNG HỢP FORM KHÔNG THAY ĐỔI THÌ K NÊN DÙNG STATE
ý anh có phải là với Form
chỉ có chức năng cho người dùng nhập để gửi thông tin đi ( form 1 chiều ) => thì loại này gọi là FORM KHÔNG THAY ĐỔI
.
Còn một loại Form
mà ta có thể chỉnh sửa thông tin của một user nào đó (thường thấy trong mấy trang quản trị) => thì gọi là FORM CÓ THAY ĐỔI
phải không anh ?
Anh có thể cho e một vài suggest
về những trường hợp như nào thì dùng state
và khi nào thì không nên dùng không ạ .
PS: Cách viết code của anh đọc rõ ràng và dễ hiểu lắm. :: [ Ước gì mấy anh trong Công ty e cũng có phong cách viết như vậy thì tốt quá : ))
@wiliamfeng đúng rồi e. Form của e nếu có chức năng sửa ( hoặc form của e có cần sử dụng state để làm việc gì đó ). khi ở ngoài bài viết chẳng hạn e bấm vào edit nó mở lại cái form cũ nhưng đồng thời nhập vào những trường cũ để mình edit lại. lúc này đòi hỏi e phải sử dụng state để render lại form đó. Để làm điều này tốt thì những Component View e xài PureComponent để tránh tình trạng render k cần thiết cũng giống như shouldComponentUpdate thôi và props lúc đó e truyền vào nếu k thay đổi thì nó sẽ k bị re-render. A cũng đang bận nên chỉ làm qua 1 chút cho e hiểu vấn đề thôi. E làm dần rồi sẽ hiểu.
E k nên viết hàm kiểu này như bạn ở dưới cmt nhé <input onChange={value => this.handleChange(value)} /> bởi vì khi e change nó luôn create 1 function mới hoàn toàn performance k tốt và sẽ gây ra re-render kể cả khi input kia là 1 PureComponent. e nên viết ví dụ thế này <input onChange={this.handleChange} /> khi đó sẽ là tham chiếu hàm handleChange = value => {}
PS: Trước a cũng xem khá nhiều tut rồi tổng hợp những cách viết hay và đúng đắn nhất thôi. Cách viết code thì nhiều chỗ xử lý theo a thì nên tách ra thành function programming Pure Function dạng function có đầu vào và đầu ra rõ ràng nhưng ở kia thì a cũng viết luôn kiểu bt cho e dễ hiểu, 2 là nên tách rõ Component UI riêng ra, và có thể chia component theo atomic design
@hpvanlong Vâng anh. Cám ơn anh nhiều ạ )
Đây là cách mà mình vẫn thường dùng trong thực tế. Bạn tham khảo thử xem, nếu ai có cách hay hơn mình cũng muốn tham khảo
- Ở component Form, đặt state cho các input này là một array, mỗi phần tử của nó là một object có 2 trường name, value
- Ở form viết một hàm callback update state dùng truyền vào từng input, hàm này cần nhận một tham số là name để update state cho đúng giá trị tương ứng với input đó, ngoài ra còn một tham số nữa là giá trị cần update
- Với mỗi input, đơn giản thì không cần viết component riêng làm gì, trong onChange của input đó, cần truyền thêm name tương ứng khi gọi callback, để update state đúng value tương ứng với input đó mà thôi. Như vậy, chúng ta có thể chỉ cần viết 1 callback nhưng có thể truyền cho nhiều input
a cho e hỏi như cách này a bày thì mình cũng phải gắn 10 cái sự kiện onchange={this.handleChange}
vào 10 cái input
phải không ạ. Nếu như vậy thì giống như cách e làm ở link
e dẫn ở trong câu hỏi à?
@wiliamfeng Đương nhiên là mỗi thẻ input phải có onChange mới được vì không có cách nào lưu lại giá trị của nó khác. Tuy nhiên, chúng ta có thể dùng vòng lặp để giảm bớt code, kiểu thế này
this.state = {
inputs: [{name: 'input1', value: null}, {name: 'input2', value: null}, ...]
}
this.state.inputs.forEach(input => {
return (
<input onChange={value => this.handleChange(input.name, value)} />
)
})
@ahjhj hehe cách này em cũng nghĩ đến đầu tiên nhưng ngặt nỗi không áp dụng vào thực tế của e được vì thiết kế nó không theo quy luật , như ví dụ e đưa ra thì các thẻ input xuất hiện liên tiếp nhưng trong thực tế nó không như vậy , thỉnh thoảng nó lại có trường select
hoặc checkbox
hay textarea
chêm vào giữa cơ.
Nên không áp dụng đc vòng lặp ạ
@wiliamfeng các trường điều có onChange mà bạn.
@won.baria nhưng mà nếu tách input
thành một component
thì chỉ phải khái báo sự kiện onChange
một lần thôi b