(Something to fix beforehand)
An error that Egoing didn't hit but I did. It says it doesn't match JSX syntax... but so what's actually wrong? I looked and...
It was the error that says you can't use ' _ ' when writing a <custom_tag>. I only noticed while creating the <Update_Contents> component when a single line got added. I guess I'd been too busy just following along. Anyway, once I deleted all the ' _ ' from every component I had written, it cleaned right up.~
(Now let's go go~ not much left~)
https://opentutorials.org/module/4058/24861
Implementing Update & Delete — React
Lesson intro: let's implement a web app's edit feature in React. Lesson - update implementation, code changes. Lesson - update implementation: form work, code changes. Lesson - update implementation: state changes, code changes. Lesson - delete implementation code changes
opentutorials.org
1. Update
1) Add a component and wire it up
UpdateContents.js
In UpdateContents.js, create the <UpdateContents>component and wire it up in app.js.
import React, {Component} from 'react';
class UpdateContent extends Component{
render(){
console.log('Update render');
return(
<article>
<h2>Update</h2>
<form action="/create_process" method="post"
onSubmit={function(e){
e.preventDefault();
this.props.onSubmit(e.target.title.value, e.target.desc.value);
}.bind(this)}
>
<p><input type="text" name="title" placeholder="title"></input></p>
<p><textarea name="desc" placeholder="description"></textarea></p>
<p><input type="submit"></input></p>
</form>
</article>
);
}
}
export default UpdateContent;
app.js
When mode is update, write an else if{ ... } inside render(){} so that the <UpdateContents>component is rendered.
(초략)
import Control from "./component/Control"
(중략)
}else if(this.state.mode === 'update'){
_content = <UpdateContent onSubmit={function(_title, _desc){
var _new_content = this.state.content.concat({
id:this.count_contents_id, title:_title, desc:_desc
});
this.setState({
content: _new_content
});
}.bind(this)}></UpdateContent>
}
(대략 ㅋ)
2) Refactoring
(1) render(){ ..the code inside has gotten messy.. }: extract it into a separate function
What the code does is check the mode value through conditionals and render the right content for each mode in the content area.
app.js - pull the content-rendering conditional out into a getContent(){} function, placed before render()
import React, {Component} from 'react';
import './App.css';
import TOC from "./component/TOC"
...
class App extends Component{
...
}
getContents(){
console.log('- getContents()');
var _title, _desc,_content = null;
if(this.state.mode === 'welcome'){
_title=this.state.welcome.title;
_desc=this.state.welcome.desc;
_content = <ReadContent title={_title} desc={_desc}></ReadContent>
}else if(this.state.mode === 'read'){
var i =0;
while(i<this.state.content.length){
var data = this.state.content[i];
if(data.id === this.state.selected_content_id){
_title=data.title;
_desc=data.desc;
break;
}
i++;
}
_content = <ReadContent title={_title} desc={_desc}></ReadContent>
}else if(this.state.mode === 'create'){
_content = <CreateContent onSubmit={function(_title, _desc){
var _new_content = this.state.content.concat({
id:this.count_contents_id, title:_title, desc:_desc
});
this.setState({
content: _new_content
});
}.bind(this)}></CreateContent>
}else if(this.state.mode === 'update'){
_content = <UpdateContent onSubmit={function(_title, _desc){
var _new_content = this.state.content.concat({
id:this.count_contents_id, title:_title, desc:_desc
});
this.setState({
content: _new_content
});
}.bind(this)}></UpdateContent>
}
return _content; // +'return' <- 새로 생성한 getContents의 역할
}
render(){
console.log('APP render');
return (
<div className="App">
<Subject ... ></Subject>
<TOC ... ></TOC>
<Control ... </Control>
{/* {_content} */} // <- 안에 있을때는 바로 썼지만 밖으로 뺏으니까, 다시 받는다.
{this.getContents()} // this. 으로 다시 받는다.
</div>
);
}
}
export default App;
(2) Show the title and description of the clicked item from the content array
app.js
Take the 'show the title and description of the clicked item...' code that was inside render(){ ... } and pull it out into a getReadContent(){ ...here... } so this function can be used for both read and update.
#above getCotent
getReadContent(){
console.log('- getReadContent(선택한 콘텐츠 출력)');
var i =0;
while(i<this.state.content.length){
var data = this.state.content[i];
if(data.id === this.state.selected_content_id){
return data;
// _title=data.title; // <- _title는 이제 사용하지 않는다.
// _desc=data.desc;
//break;
}
i++;
}
//return data;
}
#Fix the content inside the conditional
#결과적으로?, title={_cdata.title}를 하기위한 코드
else if(this.state.mode === 'read'){
var _cdata = this.getReadContent();
//*** 함수로 바뀐 부분!의 리턴(data)값을 담기위해 변수를 선언한다.
//debugger;
_content = <ReadContent title={_cdata.title} desc={_cdata.desc}></ReadContent>
//_cdata._title가 아니다! ㅋ
}
(중략)
#결과적으로?, data={_cdata}를 하기위한 코드
else if(this.state.mode === 'update'){
_cdata = this.getReadContent();
//*** 함수 재활용, 여기서 변수(_data)는 다시 선언할 필요가 없네?! 가 아니라 var만 뺐네..!
_content = <UpdateContent
data={_cdata}
onSubmit={function(_title, _desc){
var _new_content = this.state.content.concat({
id:this.count_contents_id, title:_title, desc:_desc
});
this.setState({
content: _new_content
});
}.bind(this)}></UpdateContent>
}
In UpdateContents.js, add console.log(this.props.data); to check that the value is being passed through correctly.
3) Form work inside the UpdateContents component
https://reactjs.org/docs/forms.html
Forms – React
A JavaScript library for building user interfaces
reactjs.org
(1) Putting data into the form, attempt 1
1) Not value={this.props.title} but value={this.props.data.title}
2) Inside the form, React steps in to keep this.props.data from being changed.
3) In UpdateContent, create a constructor(props){re-set the state} and receive the values inside the form with value={this.state.title}
UpdateContents.js
import React, {Component} from 'react';
class UpdateContent extends Component{
constructor(props){
super(props);
this.state={
title : this.props.data.title,
desc : this.props.data.desc
}
}
render(){
console.log(this.props.data);
console.log('Update render');
return(
<article>
<h2>Update</h2>
<form action="/create_process" method="post"
onSubmit={function(e){
e.preventDefault();
this.props.onSubmit(
e.target.title.value,
e.target.desc.value
);
}.bind(this)}
>
<p><input type="text" name="title" placeholder="title" value={this.state.title}></input></p>
<p><textarea name="desc" placeholder="description" value={this.state.desc}></textarea></p>
<p><input type="submit"></input></p>
</form>
</article>
);
}
}
export default UpdateContent;
(2) Putting data into the form, attempt 2
Attach onChange={ function(e){...}.bind(this) }! <- Connect state and <input> so we escape the readOnly state
If you check the value logged through the console...
console.log(e.target.value)
It's connected, but you'll notice that only one character at a time shows up in the console as you press keys. Of course, the reason nothing shows on the screen is that state hasn't been updated.
This feels similar to an issue I ran into back when I was learning Java on my final project — when I was integrating an external editor.js library. At the time I was building something with WebSockets so multiple people could edit a document in real time... I remember making it so every character typed got shipped over to be reflected on screen. The Opentutorials example works similarly — reflecting each character into state as it's typed.
And don't forget! state sync is always setState({a:'b'}).
<input
type="text"
name="title"
placeholder="title"
value={this.state.title} //*연결은 되었지만 갱신은 안된다.
onChange={function(e){ //*갱신하기 위한 함수안에서
//** 변경되는 value를 반영하는 함수안에서 로그를 찍어보자.
// console.log(e.target.value)
//** 키보드를 누를때마다 state에 반영하여 화면에 출력되도록 한다.
this.setState({
title:e.target.value
});
}.bind(this)}
></input>
For reference, the red warning... is because I set two values in state inside the constructor (title and desc) but only applied onChange() to title. If you add onChange() to the textarea below, it goes away.
import React, {Component} from 'react';
class UpdateContent extends Component{
constructor(props){
super(props);
this.state={
title:this.props.data.title,
desc:this.props.data.desc
}
}
render(){
console.log(this.props.data);
console.log('Update render');
return(
<article>
<h2>Update</h2>
<form action="/create_process" method="post"
onSubmit={function(e){
e.preventDefault();
this.props.onSubmit(
e.target.title.value,
e.target.desc.value
);
}.bind(this)}
>
<p><input
type="text"
name="title"
placeholder="title"
value={this.state.title} //연결은 되었지만 갱신은 안된다.
onChange={function(e){ //갱신하기 위한 함수안에서
// console.log(e.target.value) // 변경되는 value를 반영하는 함수안에서 로그를 찍어보자.
// 키보드를 누를때마다 state에 반영하여 화면에 출력되도록 한다.
this.setState({
title:e.target.value
});
}.bind(this)}
></input></p>
<p><textarea
name="desc"
placeholder="description"
value={this.state.desc}
onChange={function(e){
this.setState({
desc:e.target.value
});
}.bind(this)}
></textarea></p>
<p><input type="submit"></input></p>
</form>
</article>
);
}
}
export default UpdateContent;
(3) Refactoring using modern JavaScript []
- onChange()={ ...this code!... }
import React, {Component} from 'react';
class UpdateContent extends Component{
constructor(props){
super(props);
this.state={
title:this.props.data.title,
desc:this.props.data.desc
}
}
inputFormHandler(e){
this.setState({
//title:e.target.value
[e.target.name]:e.target.value
});
}
render(){
console.log(this.props.data);
console.log('Update render');
return(
<article>
<h2>Update</h2>
<form action="/create_process" method="post"
onSubmit={function(e){
e.preventDefault();
this.props.onSubmit(
e.target.title.value,
e.target.desc.value
);
}.bind(this)}
>
<p><input
type="text"
name="title"
placeholder="title"
value={this.state.title}
//onChange={function(e){this.inputFormHandler}.bind(this)} //no b!
onChange={this.inputFormHandler.bind(this)}
></input></p>
<p><textarea
name="desc"
placeholder="description"
value={this.state.desc}
onChange={this.inputFormHandler.bind(this)}
></textarea></p>
<p><input type="submit"></input></p>
</form>
</article>
);
}
}
export default UpdateContent;
Result is the same ~
- This time with .bind(this)
import React, {Component} from 'react';
class UpdateContent extends Component{
constructor(props){
super(props);
this.state={
title:this.props.data.title,
desc:this.props.data.desc
}
this.inputFormHandler = this.inputFormHandler.bind(this);
}
inputFormHandler(e){
this.setState({
//title:e.target.value
[e.target.name]:e.target.value
});
}
render(){
console.log(this.props.data);
console.log('Update render');
return(
<article>
<h2>Update</h2>
<form action="/create_process" method="post"
onSubmit={function(e){
e.preventDefault();
this.props.onSubmit(
e.target.title.value,
e.target.desc.value
);
}.bind(this)}
>
<p><input
type="text"
name="title"
placeholder="title"
value={this.state.title}
onChange={this.inputFormHandler}
></input></p>
<p><textarea
name="desc"
placeholder="description"
value={this.state.desc}
onChange={this.inputFormHandler}
></textarea></p>
<p><input type="submit"></input></p>
</form>
</article>
);
}
}
export default UpdateContent;
Result is the same ~
(4) To tell state.content which record to update — i.e. to pass along which id to target — create a hidden form field
UpdateContent.js
- Add id to state
- In onSubmit(), add id and switch from e.target to this.state
import React, {Component} from 'react';
class UpdateContent extends Component{
constructor(props){
super(props);
this.state={
id:this.props.data.id,
title:this.props.data.title,
desc:this.props.data.desc
}
this.inputFormHandler = this.inputFormHandler.bind(this);
}
inputFormHandler(e){
this.setState({
//title:e.target.value
[e.target.name]:e.target.value
});
}
render(){
console.log(this.props.data);
console.log('Update render');
return(
<article>
<h2>Update</h2>
<form action="/create_process" method="post"
onSubmit={function(e){
e.preventDefault();
this.props.onSubmit(
// e.target.title.value,
// e.target.desc.value
this.state.id,
this.state.title,
this.state.desc
);
}.bind(this)}
>
{/* */}
<input type="hidden" name="id" value={this.state.id}></input>
<p><input
type="text"
name="title"
placeholder="title"
value={this.state.title}
onChange={this.inputFormHandler}
></input></p>
<p><textarea
name="desc"
placeholder="description"
value={this.state.desc}
onChange={this.inputFormHandler}
></textarea></p>
<p><input type="submit"></input></p>
</form>
</article>
);
}
}
export default UpdateContent;
app.js
(5) After the update finishes, switch mode from update to read / apply the same for create
- Inside else if(this.state.mode === 'update'){ ... },
*1) onSubmit( function( ..add id to the arguments here.. ){...} )
*2) remove the id+1 code that's not needed for update
*3-2) find the content[id] at the same address as the selected content's id and overwrite it
import React, {Component} from 'react';
import './App.css';
import TOC from "./component/TOC"
import ReadContent from "./component/Read_Content"
import CreateContent from "./component/Create_Content"
import UpdateContent from "./component/Update_Content"
import Subject from "./component/Subject"
import Control from "./component/Control"
class App extends Component{
constructor(props){
super(props);
this.count_contents_id=3;
this.state={
mode :"create",
welcome : {title:'welcome', desc:'hello react'},
selected_content_id : 1,
subject:{title:'WEB', sub:'world wide web !'},
content:[
{id:1, title:"HTML", desc:"HTML is HyperText "},
{id:2, title:"CSS", desc:"Css is for design "},
{id:3, title:"JS", desc:"javascript is for intrective "}
]
}
}
getReadContent(){
console.log('- getReadContent(선택한 콘텐츠 출력)');
var i =0;
while(i<this.state.content.length){
var data = this.state.content[i];
if(data.id === this.state.selected_content_id){
return data;
// _title=data.title; // <- _title는 이제 사용하지 않는다.
// _desc=data.desc;
//break;
}
i++;
}
//return data;
}
getContents(){
console.log('- getContents()');
var _title, _desc,_content = null;
if(this.state.mode === 'welcome'){
_title=this.state.welcome.title;
_desc=this.state.welcome.desc;
_content = <ReadContent title={_title} desc={_desc}></ReadContent>
}else if(this.state.mode === 'read'){
var _cdata = this.getReadContent(); // 함수로 바뀐 부분!의 리턴(data)값을 담기위해 변수를 선언한다.
//debugger;
_content = <ReadContent title={_cdata.title} desc={_cdata.desc}></ReadContent> //_cdata._title가 아니다! ㅋ
}else if(this.state.mode === 'create'){
_content = <CreateContent onSubmit={function(_title, _desc){
var _new_content = this.state.content.concat({
id:this.count_contents_id, title:_title, desc:_desc
});
this.setState({
content: _new_content
});
}.bind(this)}></CreateContent>
}else if(this.state.mode === 'update'){
_cdata = this.getReadContent();
_content = <UpdateContent
data={_cdata}
onSubmit={function(_id, _title, _desc){ //1) id추가 받아 온 값 !
// 2) id+1 제거
// 3-1) ?? 왜 바꿔야하지? 그냥 쓰면 안되나?
// var _new_content = this.state.content.concat({
// id:this.count_contents_id, title:_title, desc:_desc
// });
// -> 안돼지.. concat({})여기 안에, ()나, {} 어디에도.. 반복문을 넣을 방법이 없다.
// 3-2) 불변성을 위해 복사한 후 덥어쓰기
var _ucontents = Array.from(this.state.content);
var i =0;
while(i<_ucontents.length){
// OMG!!! '==='과 '='가, 다른 결과가 나온다! *조심조심*
if(_ucontents[i].id ===_id){
_ucontents[i]={id:_id, title:_title, desc:_desc};
break;
}
i++;
}
this.setState({
content: _ucontents
});
}.bind(this)}></UpdateContent>
}
return _content;
}
render(){
console.log('APP render');
return (
<div className="App">
<Subject
title={this.state.subject.title}
sub={this.state.subject.sub}
onChangePage={function(){
this.setState({
mode:'welcome'
});
}.bind(this)}
>
</Subject>
<TOC
data={this.state.content}
onChangePage={function(id){
this.setState({
mode : 'read',
selected_content_id : Number(id)
})
}.bind(this)}
></TOC>
<Control onChangeMode={function(_mode){
this.setState({
mode:_mode
})
}.bind(this)}></Control>
{/* {_content} */}
{this.getContents()}
</div>
);
}
}
export default App;
Update done. But remember:
for create we used var _A = this...concat({...}), and for update we used var _B = Array.from(this...).
(+) If you add the following to create and update, mode switches to 'read', and the result changes as shown below.
this.setState({
content: _ucontents,
mode:"read" // <-- 이거 추가 ~
});
2. Delete
1) First, go to the component the delete button points to.
<li>
<input type="button" onClick={function(e){
e.preventDefault();
this.props.onChangeMode("delete");
}.bind(this)} value="delete"></input>
</li>
2) Go to where the onChangeMode("delete") function is declared.
<Control onChangeMode={function(_mode){
this.setState({
mode:_mode
});
}.bind(this)}></Control>
3) Add a conditional to the code that's been used for Create and Update so it can also be used for delete
<Control onChangeMode={function(_mode){
if(this.state.mode==="delete"){ //* D를 위한 코드
if(window.conform("really?")){ //삭제여부 재확인
//** 삭제를 위한 코드
}
}else{ //* C, U를 위한 코드
this.setState({
mode:_mode
});
}
}.bind(this)}></Control>
4) Code for the deletion
//1. 지우는 콘텐츠가 몇번방인지 알아야 한다
var _contents = Array.from(this.state.content); //대상 배열을 복재한다.
i=0;
while(i<_contents.lenth){ //복재한 배열의 방을 하나씩 열람한다
if(_contents.id=this.state.selected_content_id){ //while로 열람한 방과 사용자가 선택한 방이 같다면
_contents.splite(i,1); //타깃을 찾았으니, 삭제한다.
break; // 볼일 봤으니 while밖으로 나간다
}
i++;
}
this.setState({
content : _contents, // 원본에 복재품을 덮어쓴다.
mode : "welcome" // 할일을 마쳤으니 기본 모드로 전환한다
})
app.js
import React, {Component} from 'react';
import './App.css';
import TOC from "./component/TOC"
import ReadContent from "./component/Read_Content"
import CreateContent from "./component/Create_Content"
import UpdateContent from "./component/Update_Content"
import Subject from "./component/Subject"
import Control from "./component/Control"
class App extends Component{
constructor(props){
super(props);
this.count_contents_id=3;
this.state={
mode :"welcome",
welcome : {title:'welcome', desc:'hello react'},
selected_content_id : 1,
subject:{title:'WEB', sub:'world wide web !'},
content:[
{id:1, title:"HTML", desc:"HTML is HyperText "},
{id:2, title:"CSS", desc:"Css is for design "},
{id:3, title:"JS", desc:"javascript is for intrective "}
]
}
}
getReadContent(){
console.log('- getReadContent(선택한 콘텐츠 출력)');
var i =0;
while(i<this.state.content.length){
var data = this.state.content[i];
if(data.id === this.state.selected_content_id){
return data;
// _title=data.title; // <- _title는 이제 사용하지 않는다.
// _desc=data.desc;
//break;
}
i++;
}
//return data;
}
getContents(){
console.log('- getContents()');
var _title, _desc,_content = null;
if(this.state.mode === 'welcome'){
_title=this.state.welcome.title;
_desc=this.state.welcome.desc;
_content = <ReadContent title={_title} desc={_desc}></ReadContent>
}else if(this.state.mode === 'read'){
var _cdata = this.getReadContent(); // 함수로 바뀐 부분!의 리턴(data)값을 담기위해 변수를 선언한다.
//debugger;
_content = <ReadContent title={_cdata.title} desc={_cdata.desc}></ReadContent> //_cdata._title가 아니다! ㅋ
}else if(this.state.mode === 'create'){
_content = <CreateContent onSubmit={function(_title, _desc){
var _new_content = this.state.content.concat({
id:this.count_contents_id, title:_title, desc:_desc
});
this.setState({
content: _new_content,
mode:"read"
});
}.bind(this)}></CreateContent>
}else if(this.state.mode === 'update'){
_cdata = this.getReadContent();
_content = <UpdateContent
data={_cdata}
onSubmit={function(_id, _title, _desc){
var _ucontents = Array.from(this.state.content);
var i =0;
while(i<_ucontents.length){
if(_ucontents[i].id ===_id){
_ucontents[i]={id:_id, title:_title, desc:_desc};
break;
}
i++;
}
this.setState({
content: _ucontents,
mode:"read"
});
}.bind(this)}></UpdateContent>
}
return _content;
}
render(){
console.log('APP render');
return (
<div className="App">
<Subject
title={this.state.subject.title}
sub={this.state.subject.sub}
onChangePage={function(){
this.setState({
mode:'welcome'
});
}.bind(this)}
>
</Subject>
<TOC
data={this.state.content}
onChangePage={function(id){
this.setState({
mode : 'read',
selected_content_id : Number(id)
})
}.bind(this)}
></TOC>
<Control onChangeMode={function(_mode){
if(_mode==='delete'){
if(window.confirm('really?')){
var _dcontent = Array.from(this.state.content);
var i =0;
while(i<_dcontent.length){
if(_dcontent[i].id === this.state.selected_content_id){
_dcontent.splice(i,1);
break;
}
i++;
}
this.setState({
mode:"welcome",
content:_dcontent
})
}
}else{
this.setState({
mode:_mode
});
}
}.bind(this)}></Control>
{/* {_content} */}
{this.getContents()}
</div>
);
}
}
export default App;
Done~ And,
Final.GitHub code link, next time I'm starting from GitHub straight away... this time I ended up using it like cloud storage, heh
https://github.com/normalstory/space-r1
normalstory/space-r1
first 'hello world' of react . Contribute to normalstory/space-r1 development by creating an account on GitHub.
github.com
