I want to combine the inline toolbar with the example from draft.js to insert a link into the editor.
Thanks to the draft.js plugin FAQ I am able to add a decorator to the draft-js-plugin editor which inserts a link on a button click.
Now I want to put this button inside the inline-toolbar from draft-js-plugins. That doesn't seem to work. This is what I've done so far:
Editor.jsx
...
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: {buttonStyles, toolbarStyles},
structure: [..., LinkButton]
});
const {InlineToolbar} = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin];
class RMSEditor extends Component {
...
render() {
return (
<div>
<div className={editorStyles.editor}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
decorators={this.decorator}
ref={(element) => {
this.editor = element;
}}
readOnly={this.state.readOnly}
/>
<InlineToolbar />
</div>
</div>
);
}
LinkButton.jsx
import classNames from "classnames/bind";
import React from "react";
import {Glyphicon} from "react-bootstrap";
import styles from "./buttonStyles.less";
const cx = classNames.bind(styles);
const LinkButton = () => {
return (
<div className={cx('buttonWrapper')} onClick={(e) => {
console.log("Div", e);
}}>
<button className={cx('button')} onClick={(e) => {
console.log("Button", e);
}}><Glyphicon glyph="link"/></button>
</div>
)
};
export default LinkButton;
This way, I have managed to get a Button that shows up in the inline toolbar. But when I clicked on that button, nothing happens. I expacted that one of the onClick handlers will fire but that is not the case.
Full source code
Here you can find my full sourcecode as I don't want to put only the relevant parts directly under the questions. Please notice that the code will not work in the run snipped thingy as I have no clue how to get it to work there without setting up the hole wabpack thing.
MentionsEditor.jsx
import {CompositeDecorator, EditorState, RichUtils} from "draft-js";
import {BoldButton, CodeButton, ItalicButton, UnderlineButton, UnorderedListButton} from "draft-js-buttons";
import createInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import {defaultSuggestionsFilter} from "draft-js-mention-plugin"; // eslint-disable-line import/no-unresolved
import Editor from "draft-js-plugins-editor"; // eslint-disable-line import/no-unresolved
import React, {Component, PropTypes} from "react";
import {Button} from "react-bootstrap";
import buttonStyles from "./buttonStyles";
import editorStyles from "./editorStyles";
import LinkButton from "./InsertLinkButton";
import toolbarStyles from "./toolbarStyles";
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: {buttonStyles, toolbarStyles},
structure: [BoldButton, ItalicButton, UnderlineButton, CodeButton, UnorderedListButton, LinkButton]
});
const {InlineToolbar} = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin];
class RMSEditor extends Component {
constructor(props) {
super(props);
}
decorator = [
{
strategy: function findLinkEntities(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
contentState.getEntity(entityKey).getType() === 'LINK'
);
},
callback
);
},
component: function (props) {
const {url} = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url}>
{props.children}
</a>
);
}
}
];
state = {
editorState: EditorState.createEmpty(),
};
onChange = (editorState) => {
this.setState({
editorState,
});
};
editorLink = function (props) {
const {url} = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url} style={{color: "blue"}}>
{props.children}
</a>
);
};
focus = () => {
this.editor.focus();
};
insertLink = (e) => {
e.preventDefault();
const {editorState} = this.state;
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
'LINK',
'MUTABLE',
{url: "https://example.com"}
);
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newEditorState = EditorState.set(editorState, {currentContent: contentStateWithEntity});
this.setState({
editorState: RichUtils.toggleLink(
newEditorState,
newEditorState.getSelection(),
entityKey
)
}, () => {
setTimeout(() => {
this.focus()
}, 0);
});
};
render() {
return (
<div>
<Button onClick={this.insertLink}>insert URL</Button>
<div className={editorStyles.editor}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
decorators={this.decorator}
ref={(element) => {
this.editor = element;
}}
readOnly={this.state.readOnly}
/>
<InlineToolbar />
</div>
</div>
);
}
}
RMSEditor.propTypes = {
stakeholder: PropTypes.object.isRequired
};
export default RMSEditor;
LinkButton.jsx
import classNames from "classnames/bind";
import React from "react";
import {Glyphicon} from "react-bootstrap";
import styles from "./buttonStyles.less";
const cx = classNames.bind(styles);
const LinkButton = () => {
return (
<div className={cx('buttonWrapper')} onClick={(e) => {
console.log("Div", e);
}}>
<button className={cx('button')} onClick={(e) => {
console.log("Button", e);
}}><Glyphicon glyph="link"/></button>
</div>
)
};
export default LinkButton;
This works for me:
const MyButton = (props) => {
return (
<div className={props.className} onMouseDown={(e)=>e.preventDefault()}>
<button onClick={() => props.createComment('Hello')}>
Comment
</button>
</div>
)
}
const inlineToolbarPlugin = createInlineToolbarPlugin({
structure: [props => <MyButton {...props} createComment={(text) => alert(text)} />]
});
const { InlineToolbar } = inlineToolbarPlugin;
export default class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
const { editorState } = this.state;
return (
<div className="editor">
<Editor
editorState={editorState}
onChange={this.onChange}
plugins={[inlineToolbarPlugin]}
ref={(element) => { this.editor = element; }}
/>
<InlineToolbar />
</div>
)
}
}
Basically, this is what you need to do:
1) import the inline toolbar plugin (bonus there is a separator in there)
import createInlineToolbarPlugin, {Separator} from 'draft-js-inline-toolbar-plugin'
2) instantciate the plugin like this:
const inlineToolbarPlugin = createInlineToolbarPlugin({
structure: [
BoldButton,
ItalicButton,
UnderlineButton,
CodeButton,
Separator,
],
})
2.1) you can get those components and more from draft-js-buttons
3) add to the array more components (let's add a video button component)
AddVideoButton
this is what it looks like
import React, { Component, PropTypes } from 'react'
import classnames from 'classnames'
import { AtomicBlockUtils, RichUtils } from 'draft-js'
import createVideoPlugin from 'draft-js-video-plugin'
const {types} = createVideoPlugin()
export default class AddVideoButton extends Component {
static propTypes = {
getEditorState: PropTypes.func,
setEditorState: PropTypes.func,
style: PropTypes.shape(),
theme: PropTypes.shape(),
}
// toggleStyle(event){
// const { setEditorState, getEditorState, style } = this.props
//
// event.preventDefault()
// setEditorState(
// RichUtils.toggleInlineStyle(
// getEditorState(),
// style
// )
// )
// }
toggleVideo(event){
const { setEditorState, getEditorState } = this.props
event.preventDefault()
const style = { width: 100}
const editorState = getEditorState()
const currentContent = editorState.getCurrentContent()
const selectionState = editorState.getSelection()
const anchorKey = selectionState.getAnchorKey()
const currentContentBlock = currentContent.getBlockForKey(anchorKey)
const start = selectionState.getStartOffset()
const end = selectionState.getEndOffset()
const src = currentContentBlock.getText().slice(start, end)
if (RichUtils.getCurrentBlockType(editorState) === types.ATOMIC) {
return editorState
}
const contentState = editorState.getCurrentContent()
const contentStateWithEntity = contentState.createEntity(
types.VIDEOTYPE,
'IMMUTABLE',
{ src, style }
)
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
const newEditorState = AtomicBlockUtils.insertAtomicBlock(editorState, entityKey, ' ')
return setEditorState(newEditorState)
}
render(){
const { theme, style, getEditorState } = this.props
const styleIsActive = () => getEditorState().getCurrentInlineStyle().has(style)
return (
<div
className={theme.buttonWrapper}
onMouseDown={(e)=>e.preventDefault()}
>
<button
className={classnames({
[theme.active]:styleIsActive(),
[theme.button]:true,
})}
onClick={::this.toggleVideo}
>
<svg height="24" viewBox="0 0 120 100" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M111.374,20.922c-3.151-3.159-7.557-5.128-12.374-5.125H29c-4.817-0.002-9.222,1.966-12.375,5.125 c-3.159,3.152-5.128,7.557-5.125,12.375v40.038c-0.002,4.818,1.966,9.223,5.125,12.375c3.152,3.158,7.557,5.129,12.375,5.125h70 c4.817,0.004,9.224-1.967,12.375-5.125c3.159-3.152,5.128-7.557,5.125-12.375V33.296C116.503,28.479,114.534,24.074,111.374,20.922z M104.624,78.959c-1.454,1.447-3.413,2.328-5.624,2.33H29c-2.211-0.002-4.17-0.883-5.625-2.33c-1.447-1.455-2.328-3.414-2.33-5.625 V33.296c0.002-2.211,0.883-4.17,2.33-5.625c1.455-1.447,3.413-2.328,5.625-2.33h70c2.211,0.002,4.17,0.883,5.625,2.33 c1.447,1.455,2.327,3.413,2.329,5.625v40.038C106.952,75.545,106.072,77.504,104.624,78.959z" fill="#232323"/><path d="M77.519,50.744L57.45,39.161c-0.46-0.266-0.971-0.397-1.483-0.397c-0.513,0-1.023,0.131-1.484,0.397 c-0.918,0.528-1.483,1.509-1.483,2.569v23.171c0,1.061,0.565,2.04,1.483,2.57c0.46,0.267,0.971,0.396,1.484,0.396 c0.513,0,1.023-0.13,1.483-0.396l20.069-11.586c0.918-0.531,1.482-1.51,1.482-2.571C79.001,52.253,78.437,51.274,77.519,50.744z" fill="#232323"/>
<path d="M0 0h24v24H0z" fill="none" />
</svg>
</button>
</div>
)
}
}
or
you can imitate anything out of the draft-js-buttons
Let me know if I helped or if you have any other questions
Related
I painstakingly put together a mui-datatable that displays the content of a CSV file.
Now I want to read another CSV file when a button is clicked.
Unfortunately, I'm a complete beginner - and I don't really know how to implement it.
I tried the following - but unfortunately it doesn't work that way.
Can someone help me with that?
As a 2nd step, I want to show a mui-alert for 3 seconds when I click on a cell. At the same time, a filter with the content of the cell is set. I have already successfully implemented this functionality using the setTimeout function. Strangely enough, only if I remove a filter within the 3 seconds "setTimeout" and then add a new filter, both filters (the one already removed and the new filter) are added again. Somehow the "setTimeout" function seems to be blocking further input in the mui-datatable....
Here is my code:
import {
Typography,
AppBar,
Button,
CssBaseline,
Toolbar,
Container,
Grid,
} from "#mui/material";
import React, { useState} from "react";
import { BackupTable, Refresh } from "#mui/icons-material";
import useStyles from "./styles";
//Tabelle
import MUIDataTable, { ExpandButton } from "mui-datatables";
import { TableRow, TableCell } from "#mui/material";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import total from "./test.csv";
import lost1 from "./test.csv";
import lost2 from "./test.csv";
//const App = () => {
function App() {
const { classes } = useStyles();
const [file, setFile] = useState();
const [array, setArray] = useState([]);
const csvFileToArray = (string) => {
const csvHeader = string.slice(0, string.indexOf("\n")).split(",");
const csvRows = string.slice(string.indexOf("\n") + 1).split("\n");
const array = csvRows.map((i) => {
const values = i.split(",");
const obj = csvHeader.reduce((object, header, index) => {
object[header] = values[index];
return object;
}, {});
return obj;
});
setArray(array);
setCols(initFilter(Object.keys(Object.assign({}, ...array))));
};
const headerKeys = Object.keys(Object.assign({}, ...array));
let [selectedFilter, setSelectedFilter] = useState([new Array()]);
let [cols, setCols] = useState();
//Tabelle
const components = {
ExpandButton: function (props) {
return <ExpandButton {...props} />;
},
};
var removedFilterList=[];
const options = {
filter: true,
filterType: "multiselect",
onFilterChange: (changedColumn, filterList) => {
cols = modifyFilter(cols, filterList);
},
onFilterChipClose: (index, removedFilter, filterList) => {
removedFilterList = filterList;
selectedFilter = filterList;
},
responsive: "standard",
selectableRowsOnClick: false,
rowHover: true,
expandableRows: false,
expandableRowsHeader: false,
expandableRowsOnClick: true,
selectableRows: "none",
rowsPerPage: 100,
filterList: [],
selectableRowsHideCheckboxes: false,
onCellClick: (rowData, rowMeta) => {
onFilter(rowData, rowMeta);
},
};
const theme = createTheme({
overrides: {
MUIDataTableSelectCell: {
expandDisabled: {
// Soft hide the button.
visibility: "hidden",
},
},
MUIDataTableBodyCell: {
styleOverrides: {
root: {
backgroundColor: "#FF0000",
},
},
},
},
});
const initFilter = (cols) => {
//add options to columns
for (let i = 0; i < cols.length; i++) {
cols[i] = {
name: cols[i],
options: {
filterList: []
}
}
}
return cols;
}
let modifyFilter = (colss, newFilterArray) => {
for (let i = 0; i < colss.length; i++) {
colss[i].options.filterList = newFilterArray[i];
}
return colss;
}
const onFilter = (value, rowMeta) => {
let filteredCols = [...cols];
if (value !== "All") {
if (selectedFilter.length < rowMeta.colIndex + 1) {
for (let i = selectedFilter.length; i < rowMeta.colIndex + 1; i++) {
selectedFilter.push(new Array());
}
}
let schonVorhanden = selectedFilter[rowMeta.colIndex].indexOf(value);
if (schonVorhanden == "-1") {
selectedFilter[rowMeta.colIndex].push(value);
}
else {
console.log(value, "schon vorhanden");
}
}
filteredCols[rowMeta.colIndex].options.filterList = selectedFilter[rowMeta.colIndex];
setCols(filteredCols);
};
//---------------------------
//TEST
const ImportSamples = () => {
let input = total;
if(file == "lost1"){
input=lost1;
}
if (array.length == 0) {
fetch(input)
.then((r) => r.text())
.then((text) => {
csvFileToArray(text);
});
}
};
const doSetFile = (wert) => {
setFile(wert);
ImportSamples();
Refresh();
}
ImportSamples();
return (
<>
<CssBaseline />
<AppBar position="relative">
<Toolbar>
<BackupTable className={classes.icon} />
<Typography variant="h6">SampleProject Tabelle</Typography>
</Toolbar>
</AppBar>
<main>
<div className={classes.container}>
<Container maxWidth="sm">
<Typography
variant="h2"
align="center"
color="textPrimary"
gutterBottom
>
SampleProject
</Typography>
<Typography
variant="h5"
align="center"
color="textSecondary"
paragraph
>
Viewer
</Typography>
<div className={classes.button}>
<Grid container spacing={2} justifyContent="center">
<Grid item>
<Button variant="contained" color="primary" onClick={() => doSetFile('lost1')}>
Erste Aktion
</Button>
</Grid>
<Grid item>
<Button variant="outlined" color="primary">
Zweite Aktion
</Button>
</Grid>
</Grid>
</div>
</Container>
</div>
<div className={classes.container}>
<Container maxWidth="false">
<ThemeProvider theme={theme}>
<MUIDataTable
title={"SampleProject"}
data={array}
columns={cols}
components={components}
options={options}
/>
</ThemeProvider>
</Container>
</div>
</main>
<footer className={classes.footer}>
<Typography variant="h6" align="center" gutterBottom>
Footer
</Typography>
<Typography variant="subtitle1" align="center" color="textSecondary">
Something here to give the footer a purpose!
</Typography>
</footer>
</>
);
}
export default App;
I'm struggling to create Checkbox filtering in React. I want my products to be filtered by brand,I have created checkboxes dynamically from database. So each checkbox corresponds to the brand. The quantity of checkboxes equals to quantity of brands.
The problem is when I click on one of the checkboxes, the other products are disappearing from the screen, and this is what I want, but at the same time, the other checkboxes is disappearing as well, and I see only the clicked one.
Also, when I click in that checkbox again I want the products of the brand to be back on the screen.
Any ideas how to do it?
Category.js component:
/** #format */
import { useState, useEffect, createContext } from "react";
import { Link, useParams } from "react-router-dom";
import Header from "../components/Header";
import React from "react";
// Importing styles
import "./styles/Category.css";
import Footer from "../components/Footer";
import Filter from "./Filter";
export const CatgContext = createContext();
export const Category = ({ onAdd }) => {
const [products, setProducts] = useState([]);
const params = useParams();
const getProducts = async () => {
try {
const res = await fetch(`/api/products/${params.type}`);
const data = await res.json();
setProducts(data);
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getProducts();
}, []);
return (
<>
<Header />
<h1>{params.type}</h1>
<aside className="catalogue-aside">
<CatgContext.Provider value={{ setProducts, products }}>
<Filter products={products} />
</CatgContext.Provider>
</aside>
<div className="category-wrapper">
{products.map((product) => {
return (
<div
className="product-card"
key={product.product_id}
id={product.product_id}
>
<Link to={`/product/${product.product_id}`}>
<img className="img-card" src={product.product_image} />
<h3 className="title-card">{product.product_type}</h3>
</Link>
<p>{product.product_brand}</p>
<p>{product.product_name}</p>
<p>{product.product_description}</p>
<p>${product.product_price}</p>
<button onClick={() => onAdd(product)}>Add to Cart</button>
</div>
);
})}
</div>
<Footer />
</>
);
};
Filter.js component:
import { useState, useContext, useEffect } from "react";
import { CatgContext } from "./Category";
const Filter = (props) => {
const { products, setProducts } = useContext(CatgContext);
const handleChange = (e, value) => {
const filteredByBrand = products.filter((product) => {
return product.product_brand === value;
});
setProducts(filteredByBrand);
};
const renderCheckboxLists = () =>
products.map((product) => (
<div className="checkbox-wrapper" key={product.product_id}>
<input
onChange={(e) => handleChange(e, product.product_brand)}
type="checkbox"
/>
<span>{product.product_brand}</span>
</div>
));
return <>{renderCheckboxLists()}</>;
};
export default Filter;
Screenshot 1
Screenshot 2
I want to use react-native-camera in expo but I get this error.-> Possible Unhandled Promise Rejection (id: 0) Here is code.
import {StatusBar} from 'expo-status-bar';
import React from "react";
import { StyleSheet, Modal, View, Pressable, Text, SafeAreaView, Button, Image,
PushNotificationIOS } from "react-native";
import { useEffect, useRef, useState } from 'react';
import Icon from "react-native-vector-icons/MaterialIcons";
import {shareAsync} from 'expo-sharing';
import * as MediaLibrary from 'expo-media-library';
import { Camera } from 'expo-camera';
export default function App() {
let cameraRef = useRef();
const [hasCameraPermission, setHasCameraPermission] = useState();
const [hasMediaLibraryPermission, setHasMediaLibraryPermission] = useState();
const [photo, setPhoto] = useState();
useEffect(()=>{
(async ()=>{
const cameraPermission = await Camera.requestCameraPermissionsAsync();
constMediaLibraryPermission = await MediaLibrary.requestMediaLibraryPermissionsAsync();
setHasCameraPermission(cameraPermission.status === "granted");
setHasMediaLibraryPermission(MediaLibraryPermission.status === "granted");
})();
},[]);
if (hasCameraPermission === undefined){
return <Text> Requesting permissions...</Text>
} else if (!hasCameraPermission){
return <Text> Permission for camera not granted. Please change this in settinr.</Text>
}
let takePic = async () => {
let options = {
quality :1,
base64: true,
exif : false
};
let newPhoto = await cameraRef.current.takePictureAsync(options);
setPhoto(newPhoto);
};
if(photo){
let sharePic = () => {
shareAsync(photo.uri).then(() => {
setPhoto(undefined);
});
};
let savePhoto = () => {
MediaLibrary.saveToLibraryAsync(photo.uri).then(() =>{
setPhoto(undefined);
});
};
return (
<SafeAreaView style = {styles.container}>
<Image style = {styles.preview} source = {{uri: "data:image/jpg;base64," +
photo.base64}}/>
<Button title = "share" onPress = {sharePic}/>
{hasMediaLibraryPermission ? <Button title = "save" onPress = {savePhoto}/>:undefined}
<Button title = "Discard" onPress = {()=> setPhoto(undefined)}/>
</SafeAreaView>
)
}
return (
<Camera style={styles.container} ref = {cameraRef}>
<View style = {styles.buttonContainer}>
<Button title = "Takc Pic" onPress = {takePic} />
</View>
<StatusBar style="auto" />
</Camera>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
buttonContainer:{
backgroundColor :'#fff',
alignSelf:'flex-end'
},
preview:{
alignSelf:'stretch',
flex:1
}
});
So I try 'catch-throw', but I didn't solve this problem. Because 'try' is necessary.
I checked other questions and answers, but I didn't find a solution.
How can I solve this error?
I want to submit a form when I stop type, I'm using react hook form and call the handleSubmit in a function and it doesn't work, please help me, this is my code, I'm doing a map of this code, I've tried with ref but it doesn't work. This is my component.
import React, {useRef} from 'react';
import {View, Text, TextInput, TouchableOpacity} from 'react-native';
import tailwind from 'tailwind-rn';
import {useDebounce} from 'use-debounce';
import {useForm, Controller} from 'react-hook-form';
import {MAIN_COLOR, GRAY} from '../../constants/theme';
function RegisterFormHook({data, onSubmit, control, errors}) {
const [underlineColor, setUnderlineColor] = React.useState(GRAY);
const [field, setField] = React.useState('');
const [value] = useDebounce(field, 1000);
const inputElement = useRef();
const {handleSubmit} = useForm();
React.useEffect(() => {
if (value.length !== 0) {
handleSubmit(onSubmit({[data.name]: value}));
}
console.log(errors);
}, [value]);
const onFocus = () => {
setUnderlineColor(MAIN_COLOR);
};
const onBlur = () => {
setUnderlineColor(GRAY);
};
return (
<View style={tailwind('px-8')}>
<Controller
control={control}
name={data.name}
rules={data.rules}
render={({field: {onChange, value}}) => (
<TextInput
placeholder={data.placeholder}
ref={inputElement}
style={[
tailwind('text-black p-0 text-xl pb-1 flex-row'),
{
borderBottomWidth: 1,
borderBottomColor: underlineColor,
},
]}
onChangeText={value => {
onChange(value);
setField(value);
}}
onBlur={onBlur}
value={value}
onFocus={() => onFocus()}
/>
)}
/>
<Text>{errors[data.name]?.message}</Text>
</View>
);
}
export default RegisterFormHook;
I want to submit in the useEffect but it just works when I press a button.
Here is a sample how you could implement scheduled action after input has been completed:
import * as React from 'react';
import { TextInput, View, StyleSheet, Alert } from 'react-native';
import Constants from 'expo-constants';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default function App() {
let timer = React.useRef();
const [text, setText] = React.useState('');
React.useEffect(() => {
if (text) {
timer = setTimeout(() => {
Alert.alert('TEXT : ' + text);
}, 3000);
}
}, [text]);
return (
<View style={styles.container}>
<Card>
<TextInput
placeholder={'Enter the tetxt'}
onChange={(e) => {
clearTimeout(timer);
setText(e.nativeEvent.text);
}}
/>
</Card>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
In the Zxing library, I want to close the camera when the user clicks cancel. So I used a button and add onclick event to it. it is calling resetReader method. I called this method after gets a barcode value or in the cancel button onclick event.If it is getting barcode values, this resetReader method works perfectly. if we cancel, the camera doesn't stop. Am I missing something?
const codeReader = new BrowserMultiFormatReader(hints);
const resetReader = () => {
codeReader.reset();
codeReader.stopContinuousDecode();
};
for those who haven't figured it out yet? I have found a solution to this problem. Harendrra's solution didn't work for me, but this one did in combination with usestate. For my project the code uses Bootstrap. So when I click on a button the Modal appears. The camera loads. When I click on the Close button the camera disappears. Hope this is a solutions for everyone, enjoy ;-)
export default function Example(props) {
// codeReader
const [codeReader, setReader] = useState(new BrowserMultiFormatReader());
const [videoInputDevices, setVideoInputDevices] = useState([]);
const [selectedVideoDevice, selectVideoDevice] = useState('');
useEffect(() => {
(async () => {
const videoInputDeviceList = await codeReader.listVideoInputDevices();
setVideoInputDevices(videoInputDeviceList);
if (videoInputDeviceList.length > 0 && selectedVideoDevice == null) {
selectVideoDevice(videoInputDeviceList[0].deviceId);
}
})();
}, [codeReader, selectedVideoDevice]);
const handleShow = () => {
setBrand('');
// Open modal.
setShow(true);
codeReader.decodeFromVideoDevice(selectedVideoDevice, 'videoElement', (res) => {
setCanClose(true);
if (res) {
const rawText = res.getText();
axios
.get(`https://world.openfoodfacts.org/api/v0/product/${rawText}.json`)
.then((result) => {
// set data
setBrand(result.data.product.brands);
// close modal
setShow(false);
// codeReader reset
codeReader.reset();
})
.catch((err) => console.log('error', err));
}
});
};
const handleClose = () => {
// codeReader reset.
setReader(codeReader.reset());
// Close modal
setShow(false);
// Set new codeReader.
// The solution for the error messages after the codeReader reset.
// This will build the codeReader for the next time.
setReader(new BrowserMultiFormatReader(hints));
};
return (
<Fragment>
<div className='py-2'>
<div>Brand: {brand}</div>
<Button variant='primary' onClick={handleShow}>
Launch static backdrop modal
</Button>
<Modal show={show} onHide={handleClose} backdrop='static' keyboard={false} centered id='scanProductModal'>
<Modal.Body>
<div
onChange={(event) => {
const deviceId = event.target.value;
selectVideoDevice(deviceId);
}}
>
<div className='button-group-top'>
<select className='form-select form-select-sm' aria-label='Default select example'>
{videoInputDevices &&
videoInputDevices.map((inputDevice, index) => {
return (
<option value={inputDevice.deviceId} key={index}>
{inputDevice.label || inputDevice.deviceId}
</option>
);
})}
</select>
</div>
<video id='videoElement' width='600' height='400' />
<Button className='btn btn-danger' onClick={handleClose}>
Close
</Button>
</div>
</Modal.Body>
</Modal>
</Fragment>
);
}
Yes, I resolved. You have to create codeReader object at the top of the Class. Try this code.
import "../App.css";
import { BrowserBarcodeReader } from "#zxing/library";
class Barcode extends React.Component {
codeReader = new BrowserBarcodeReader();
constructor(props) {
super(props);
this.state = { reader: {}, selectedDevice: "" };
this.startButton = this.startButton.bind(this);
this.resetButton = this.resetButton.bind(this);
this.getBarcode = this.getBarcode.bind(this);
}
componentDidMount() {
this.getBarcode();
}
startButton() {
console.log("start", this.codeReader);
this.codeReader
.decodeOnceFromVideoDevice(this.state.selectedDevice, "video")
.then(result => {
document.getElementById("result").textContent = result.text;
})
.catch(err => {
console.error(err.toString());
document.getElementById("result").textContent = err;
});
console.log(
`Started continous decode from camera with id ${this.state.selectedDevice}`
);
}
resetButton() {
this.codeReader && this.codeReader.reset();
document.getElementById("result").textContent = "";
}
getBarcode() {
let selectedDeviceId;
return this.codeReader.getVideoInputDevices().then(videoInputDevices => {
const sourceSelect = document.getElementById("sourceSelect");
selectedDeviceId = videoInputDevices[0].deviceId;
if (videoInputDevices.length > 1) {
videoInputDevices.forEach(element => {
const sourceOption = document.createElement("option");
sourceOption.text = element.label;
sourceOption.value = element.deviceId;
sourceSelect.appendChild(sourceOption);
});
sourceSelect.onchange = () => {
selectedDeviceId = sourceSelect.value;
};
const sourceSelectPanel = document.getElementById(
"sourceSelectPanel"
);
sourceSelectPanel.style.display = "block";
}
this.setState({
selectedDevice: selectedDeviceId
});
})
.catch(err => {
alert(err);
});
}
render() {
return (
<div>
<h2>Barcode</h2>
{Object.keys(this.codeReader).length > 0 && (
<div>
<div>
<button
className="button"
id="startButton"
onClick={this.startButton}
>
Start
</button>
<button
className="button"
id="resetButton"
onClick={this.resetButton}
>
Reset
</button>
</div>
<div>
<video
id="video"
width="600"
height="400"
style={{ border: "1px solid gray" }}
></video>
</div>
<label>Result:</label>
<pre>
<code id="result"></code>
</pre>
</div>
)}
</div>
);
}
}
export default Barcode; ```