In part 2 , we have completed the server part. And the last step we will also write the interface part with ReactJS.
Set up
Here I use nodejs 12.16. Setup steps include:
cd $PROJECT
: go to the project directory addressyarn create react-app frontend
: from creating reactfrontend
foldercd frontend && yarn ađ antd
: add antd library to yarn- Write Dockerfile with the following content:
1 2 3 4 5 6 7 8 9 10 11 | # base image FROM node:12.16.1-alpine WORKDIR /app/frontend # install and cache app dependencies COPY package*.json ./ RUN yarn COPY . . # start app CMD ["yarn", "start"] |
- Now, can you run
yarn start
in the frontend ordocker-compose up
folder outside the main directory and also create the 3000 port interface that has the image below? - Note, if you use docker-compose, you have to fix the reactjs proxy of reactjs to
http://flask:3500
and for local to run it will behttp://127.0.0.1:3500
inpackage.json
.If you have no problems go to the next step.
Coding
In here we will write 3 components:
- 1 component displays the district name
- 1 component is the form to send photos and wards
- 1 component displays the result
Display district name
- Create the file
Title.js
. - Add the following content to.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import React, { useState, useEffect } from "react"; function Title() { const [title, setTitle] = useState(); useEffect(() => { fetch("/district") .then((response) => response.json()) .then((data) => { console.log("Title: " + data.district); setTitle(data.district); }); }, []); return <div><h1>{title}</h1></div>; } export default Title; |
- Here is a basic hook component with title is the name of the district we need.
- We implement GET protocol with api
/district
, Reactjs will automatically map them with proxy inpackage.json
- At the App Component return:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class App extends React.Component { constructor() { super(); } render() { const result = <Result permission={this.state.permission} />; return ( <div className="App" > <Title /> </div> ); } } |
- We get the screen:
Create a Photo submission form
- Create ImageUpload.js file
- We will use the form component of antd.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | class ImageUpload extends React.Component { constructor(props) { super(props); this.formItemLayout = { labelCol: { span: 6, }, wrapperCol: { span: 14, }, }; this.state = { image: { url: null, file: null, }, wards: [], }; } render() { return ( <Form name="validate_other" {...this.formItemLayout} onFinish={} > <Form.Item name="ward" label="Phường" hasFeedback rules={[ { required: true, message: "Xin hãy chọn phường của công dân", }, ]} > <Select placeholder="Chọn phường"> {/* TODO: make gen function */} </Select> </Form.Item> <Form.Item name="image" label="Ảnh" > <input type="file" onChange={} /> <br /> <img className="Image" src={} height="400" width="400" /> </Form.Item> <Form.Item wrapperCol={{ span: 12, offset: 6, }} > <br /> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); } } |
Here we have the screen of a form to upload photos and select districts with Select but it still has no Data.
- We will get the district data via api
/wards
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | componentDidMount() { axios.get("/wards") .then((response) => response.data) .then((json_data) => this.setState({ wards: json_data.wards })); } renderWardOpt() { const { Option } = Select; let data = this.state.wards; let wards = data.map((item, idx) => ( <Option value={item}> {item} </Option> )); return wards; } render() { const wards = this.renderWardOpt(); ... <Select placeholder="Chọn phường"> {/* TODO: make gen function */} {wards} </Select> ... |
- Here after the react is initialized, call
render()
and it will mount again and callcomponentDidMount()
and will changethis.state.wards
with the district values from the API. AndrenderWardOpt
called in render will create CardOption
. - But now the image selection is not displayed, so we have to write a function to turn the upload file into
ObjectURL
- And we will have to handle the event press the submit button. The
antd
form allows us to handle this event with theonFinish
assignment in the Form card.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | ... handleImage = event => { this.setState({ image: { url: URL.createObjectURL(event.target.files[0]), file: event.target.files[0], } }); // console.log("URL: ", URL.createObjectURL(event.target.files[0])); } onFinish = values => { let image = this.state.image.file; let ward = values.ward; const config = { headers: { "content-type": "multipart/form-data" } } var formData = new FormData(); formData.append("ward", ward); formData.append("image", image); axios .post("/face", formData, config) .then(res => { this.props.returnResult(res); }) .catch(err => console.warn(err)); }; render() { const wards = this.renderWardOpt(); return ( <Form name="validate_other" {...this.formItemLayout} onFinish={this.onFinish} > ... <input type="file" onChange={this.handleImage} /> <br /> <img className="Image" src={this.state.image.url} height="400" width="400" /> ... |
- We send a POST Request to
/face
with the data variableFormData()
. Here, we also havethis.props.returnResult(res)
to return the results to the Parent component that the App handles. - Finally, we have a satisfactory form:
Create the result returned
- Create
Result.js
file and write the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function Result(props) { const [permission, setPermission] = useState(); useEffect(() => { if (props.permission === true) { console.log("Ok"); setPermission("Mời bạn ra lấy gạo"); } else if (props.permission === false) { console.log("Ko ok"); setPermission("Hôm nay bạn đã lấy gạo rồi mà"); } else { setPermission("") } }, [props.permission]); return (<div> <h2>{permission}</h2> </div>); } |
- This component receives the value sent from the Component App as permission, which is the result returned from the POST request above.
useEffect
here changes whenprops.permission
changes.- Revise the App to the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class App extends React.Component { constructor() { super(); this.state = { permission: null } } getResult = (res) => { let data = res.data this.setState({ permission: data.permission, }); console.log(res); } render() { const result = <Result permission={this.state.permission} />; return ( <div className="App" > <Title /> <ImageUpload returnResult={this.getResult} /> <br /> <Result permission={this.state.permission}/> </div> ); } } |
- We pass into the ImageUpload component the getResult function to get the server’s response to the App, changing the value of
this.state.permission
assigned in the Result component.
Result:
Here you will have a screen like the one below: Or
We have now completed a simple AI project. Any questions or comments please leave below. Thank you all so much for watching here ^ _ ^.