Skip to content

Commit 90d38e9

Browse files
feat: updated react and react-hooks sample and docs (#179)
* feat: updated react sample to follow readme * fix: match results class to style guide * feat: add link canonical * fix: Changed Video capture to decode video, and image too * fix: Added comments on dynamsoft.config.ts * fix formatter printWidth to 120 * fix: handle uncaught runtime errors * fix: standarize HelloWorld component on App.tsx * finalized react update * fix: update both react and react-hooks * fix: typo * moved comment * update readme to add customization for ui --------- Co-authored-by: felixindrawan <indrawan.felix123@gmail.com>
1 parent 5bb9c3e commit 90d38e9

28 files changed

+878
-936
lines changed

hello-world/react-hooks/README.md

Lines changed: 225 additions & 309 deletions
Large diffs are not rendered by default.

hello-world/react-hooks/package.json

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
{
22
"name": "dbrjs-react-sample",
3-
"version": "0.0.0",
3+
"version": "0.1.0",
44
"private": true,
5-
"homepage": "./",
65
"dependencies": {
7-
"@testing-library/jest-dom": "^5.16.5",
6+
"@testing-library/jest-dom": "^5.17.0",
87
"@testing-library/react": "^13.4.0",
98
"@testing-library/user-event": "^13.5.0",
109
"@types/jest": "^27.5.2",
11-
"@types/node": "^16.18.12",
12-
"@types/react": "^18.0.27",
13-
"@types/react-dom": "^18.0.10",
10+
"@types/node": "^16.18.99",
11+
"@types/react": "^18.3.3",
12+
"@types/react-dom": "^18.3.0",
1413
"dynamsoft-barcode-reader-bundle": "10.2.1000",
15-
"react": "^18.2.0",
16-
"react-dom": "^18.2.0",
14+
"react": "^18.3.1",
15+
"react-dom": "^18.3.1",
1716
"react-scripts": "5.0.1",
1817
"typescript": "^4.9.5",
1918
"web-vitals": "^2.1.4"
@@ -42,4 +41,4 @@
4241
"last 1 safari version"
4342
]
4443
}
45-
}
44+
}

hello-world/react-hooks/public/index.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@
77
<meta name="theme-color" content="#000000" />
88
<meta
99
name="description"
10-
content="Web site created using create-react-app"
10+
content="Dynamsoft Barcode Reader in a React Application, helps read barcodes from camera or images."
1111
/>
12+
<meta name="keywords" content="barcodes, camera, images, React" />
13+
<link rel="canonical" href="https://demo.dynamsoft.com/samples/dbr/js/hello-world/react-hooks/build/" />
1214
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
15+
<!--
16+
manifest.json provides metadata used when your web app is installed on a
17+
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
18+
-->
19+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
1320
<!--
1421
Notice the use of %PUBLIC_URL% in the tags above.
1522
It will be replaced with the URL of the `public` folder during the build.
@@ -19,7 +26,7 @@
1926
work correctly both with client-side routing and a non-root public URL.
2027
Learn how to configure a non-root public URL by running `npm run build`.
2128
-->
22-
<title>read-video-react</title>
29+
<title>Hello World for React - Dynamsoft Barcode Reader Sample</title>
2330
</head>
2431
<body>
2532
<noscript>You need to enable JavaScript to run this app.</noscript>
5.22 KB
Loading
9.44 KB
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"short_name": "React App",
3+
"name": "Create React App Sample",
4+
"icons": [
5+
{
6+
"src": "favicon.ico",
7+
"sizes": "64x64 32x32 24x24 16x16",
8+
"type": "image/x-icon"
9+
},
10+
{
11+
"src": "logo192.png",
12+
"type": "image/png",
13+
"sizes": "192x192"
14+
},
15+
{
16+
"src": "logo512.png",
17+
"type": "image/png",
18+
"sizes": "512x512"
19+
}
20+
],
21+
"start_url": ".",
22+
"display": "standalone",
23+
"theme_color": "#000000",
24+
"background_color": "#ffffff"
25+
}
Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.hello-world-page {
2+
text-align: center;
3+
}
14
.title {
25
display: flex;
36
justify-content: center;
@@ -9,39 +12,39 @@
912
height: 60px;
1013
animation: retate 5s infinite linear;
1114
}
12-
.top-btns {
13-
width: 30%;
15+
.buttons-container {
16+
text-align: center;
1417
margin: 20px auto;
1518
}
16-
.top-btns button {
19+
.buttons-container button {
1720
display: inline-block;
1821
border: 1px solid black;
1922
padding: 5px 15px;
2023
background-color: transparent;
2124
cursor: pointer;
2225
}
23-
.top-btns button:first-child {
26+
.buttons-container button:first-child {
2427
border-top-left-radius: 10px;
2528
border-bottom-left-radius: 10px;
2629
border-right: transparent;
2730
}
28-
.top-btns button:nth-child(2) {
31+
.buttons-container button:nth-child(2) {
2932
border-top-right-radius: 10px;
3033
border-bottom-right-radius: 10px;
3134
border-left: transparent;
3235
}
3336

34-
@media screen and (max-width: 500px) {
35-
.top-btns {
36-
width: 70%;
37+
@media screen and (max-width: 800px) {
38+
.buttons-container {
39+
width: 70%;
3740
}
3841
}
3942

4043
@keyframes retate {
4144
from {
42-
transform: rotate(0deg);
45+
transform: rotate(0deg);
4346
}
4447
to {
45-
transform: rotate(360deg);
48+
transform: rotate(360deg);
4649
}
47-
}
50+
}

hello-world/react-hooks/src/App.tsx

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,46 @@
1-
import { useState } from 'react';
2-
import './App.css';
3-
import reactLogo from './assets/logo.svg';
4-
import VideoCapture from './components/VideoCapture/VideoCapture';
5-
import ImageCapture from './components/ImageCapture/ImageCapture';
1+
import { useState } from "react";
2+
import reactLogo from "./assets/logo.svg";
3+
import VideoCapture from "./components/VideoCapture/VideoCapture";
4+
import ImageCapture from "./components/ImageCapture/ImageCapture";
5+
import "./App.css";
6+
7+
enum Modes {
8+
VIDEO_CAPTURE = "video",
9+
IMAGE_CAPTURE = "image",
10+
}
611

712
function App() {
8-
const [mode, setMode] = useState("video");
13+
const [mode, setMode] = useState(Modes.VIDEO_CAPTURE);
14+
15+
const showVideoCapture = () => setMode(Modes.VIDEO_CAPTURE);
16+
17+
const showImageCapture = () => setMode(Modes.IMAGE_CAPTURE);
18+
919
return (
10-
<div className='App'>
11-
<div className='title'>
12-
<h2 className='title-text'>Hello World for React</h2>
13-
<img className='title-logo' src={reactLogo} alt="logo"></img>
20+
<div className="hello-world-page">
21+
<div className="title">
22+
<h2 className="title-text">Hello World for React</h2>
23+
<img className="title-logo" src={reactLogo} alt="logo"></img>
1424
</div>
15-
<div className='top-btns'>
16-
<button onClick={()=>{setMode("video")}} style={{backgroundColor: mode === "video" ? "rgb(255, 174, 55)" : "#fff"}}>Video Capture</button>
17-
<button onClick={()=>{setMode("image")}} style={{backgroundColor: mode === "image" ? "rgb(255, 174, 55)" : "#fff"}}>Image Capture</button>
25+
<div className="buttons-container">
26+
<button
27+
style={{
28+
backgroundColor: mode === Modes.VIDEO_CAPTURE ? "rgb(255,174,55)" : "white",
29+
}}
30+
onClick={showVideoCapture}
31+
>
32+
Decode Video
33+
</button>
34+
<button
35+
style={{
36+
backgroundColor: mode === Modes.IMAGE_CAPTURE ? "rgb(255,174,55)" : "white",
37+
}}
38+
onClick={showImageCapture}
39+
>
40+
Decode Image
41+
</button>
1842
</div>
19-
{ mode === "video" ? <VideoCapture /> : <ImageCapture /> }
43+
<div className="container">{mode === Modes.VIDEO_CAPTURE ? <VideoCapture /> : <ImageCapture />}</div>
2044
</div>
2145
);
2246
}
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
.capture-img {
1+
.image-capture-container {
22
width: 100%;
33
height: 100%;
4-
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
4+
font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono,
5+
Courier New, monospace;
56
}
67

7-
.capture-img .img-ipt {
8+
.image-capture-container .input-container {
89
width: 80%;
910
height: 100%;
1011
display: flex;
@@ -13,6 +14,7 @@
1314
margin: 0 auto;
1415
}
1516

16-
.capture-img .result-area {
17+
.image-capture-container .results {
1718
margin-top: 20px;
18-
}
19+
height: 100%;
20+
}
Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,45 @@
1-
import { useEffect, useRef, MutableRefObject, useCallback, ChangeEvent } from "react";
2-
import "../../dynamsoft.config";
1+
import React, { useRef, useEffect, MutableRefObject, useCallback } from "react";
2+
import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on.
33
import { EnumCapturedResultItemType } from "dynamsoft-core";
4-
import type { BarcodeResultItem } from "dynamsoft-barcode-reader";
4+
import { BarcodeResultItem } from "dynamsoft-barcode-reader";
55
import { CaptureVisionRouter } from "dynamsoft-capture-vision-router";
66
import "./ImageCapture.css";
77

8-
export default () => {
9-
const resDiv: MutableRefObject<HTMLDivElement | null> = useRef(null);
8+
function ImageCapture() {
9+
const resultsContainer: MutableRefObject<HTMLDivElement | null> = useRef(null);
1010

11-
const pCvRouter: MutableRefObject<Promise<CaptureVisionRouter> | null> = useRef(null);
12-
const bDestoried = useRef(false);
11+
let pCvRouter: MutableRefObject<Promise<CaptureVisionRouter> | null> = useRef(null);
12+
let isDestroyed = useRef(false);
1313

14-
const captureImage = useCallback(async(e: ChangeEvent<HTMLInputElement>)=>{
14+
const captureImage = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
1515
let files = [...(e.target.files as any as File[])];
16-
e.target.value = '';
17-
resDiv.current!.innerText = "";
16+
e.target.value = ""; // reset input
17+
resultsContainer.current!.innerText = "";
18+
1819
try {
20+
// ensure cvRouter is created only once
1921
const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance());
20-
if (bDestoried.current) return;
21-
22-
for(let file of files){
22+
if (isDestroyed.current) return;
23+
24+
for (let file of files) {
2325
// Decode selected image with 'ReadBarcodes_SpeedFirst' template.
2426
const result = await cvRouter.capture(file, "ReadBarcodes_SpeedFirst");
25-
if (bDestoried.current) return;
26-
27-
if(files.length > 1){
28-
resDiv.current!.innerText += `\n${file.name}:\n`;
27+
if (isDestroyed.current) return;
28+
29+
// Print file name if there's multiple files
30+
if (files.length > 1) {
31+
resultsContainer.current!.innerText += `\n${file.name}:\n`;
2932
}
3033
for (let _item of result.items) {
31-
if(_item.type !== EnumCapturedResultItemType.CRIT_BARCODE) { continue; }
34+
if (_item.type !== EnumCapturedResultItemType.CRIT_BARCODE) {
35+
continue; // check if captured result item is a barcode
36+
}
3237
let item = _item as BarcodeResultItem;
33-
resDiv.current!.innerText += item.text + "\n";
38+
resultsContainer.current!.innerText += item.text + "\n"; // output the decoded barcode text
3439
console.log(item.text);
3540
}
36-
if (!result.items.length) resDiv.current!.innerText += 'No barcode found\n';
41+
// If no items are found, display that no barcode was detected
42+
if (!result.items.length) resultsContainer.current!.innerText += "No barcode found";
3743
}
3844
} catch (ex: any) {
3945
let errMsg = ex.message || ex;
@@ -43,25 +49,28 @@ export default () => {
4349
}, []);
4450

4551
useEffect((): any => {
46-
// reset value so works in React.StrictMode
47-
bDestoried.current = false;
48-
// onBeforeUnmount
52+
// In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode.
53+
isDestroyed.current = false;
54+
55+
// componentWillUnmount. dispose cvRouter when it's no longer needed
4956
return async () => {
50-
bDestoried.current = true;
51-
if(pCvRouter.current){
52-
try{
57+
isDestroyed.current = true;
58+
if (pCvRouter.current) {
59+
try {
5360
(await pCvRouter.current).dispose();
54-
}catch(_){}
61+
} catch (_) {}
5562
}
56-
}
63+
};
5764
}, []);
5865

5966
return (
60-
<div className="capture-img">
61-
<div className="img-ipt">
62-
<input type="file" multiple onChange={captureImage} accept=".jpg,.jpeg,.icon,.gif,.svg,.webp,.png,.bmp"/>
67+
<div className="image-capture-container">
68+
<div className="input-container">
69+
<input type="file" multiple accept=".jpg,.jpeg,.icon,.gif,.svg,.webp,.png,.bmp" onChange={captureImage} />
6370
</div>
64-
<div className="result-area" ref={resDiv}></div>
71+
<div className="results" ref={resultsContainer}></div>
6572
</div>
66-
)
67-
};
73+
);
74+
}
75+
76+
export default ImageCapture;

0 commit comments

Comments
 (0)