Register React-Quill custom module - javascript

I am currently working with the react-quill module and it works very nicely. My App.js looks like
import { useState } from "react";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
const modules = {
toolbar: [
[{ font: [] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
["bold", "italic", "underline", "strike"],
[{ color: [] }, { background: [] }],
[{ script: "sub" }, { script: "super" }],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }, { align: [] }],
["link", "image", "video"],
["clean"],
]
};
const App = () => {
const [value, setValue] = useState("");
console.log(value);
return <ReactQuill modules={modules} theme="snow" onChange={setValue} placeholder="Content goes here..." />;
};
export default App;
and my index.js looks like
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
However, I would like to embed this module for autocompletion.
They say, that one would have to embed it as follows:
import getPlaceholderModule from 'quill-placeholder-module';
import getAutocompleteModule from 'quill-placeholder-autocomplete-module';
Quill.register('modules/placeholder', getPlaceholderModule(Quill, {
className: 'ql-placeholder-content', // default
}));
Quill.register('modules/autocomplete', getAutocompleteModule(Quill));
const placeholders = [
{id: 'foo', label: 'Foo'},
{id: 'required', label: 'Required', required: true}
]
var quill = new Quill('#editor', {
modules: {
toolbar: {container: `#toolbar`},
placeholder: {
delimiters: ['{', '}'], // default
placeholders
},
autocomplete: {
getPlaceholders: () => placeholders // factory
container: '#completions', // can also be return of `document.querySelector` or kept `undefined`
triggerKey: '#', // default
endKey: '#', // optional
debounceTime: 0, // 0: disabled (default)
onOpen: () => console.log('opened'), // optional
onClose: (placeholder) => console.log('user choosed:', placeholder), // optional
fetchPlaceholders: (query) => fetch(...).then(...) // optional
onFetchStarted: (query) => console.log('user searching for:', query), // optional
onFetchFinished: (results) => console.log('possible results:', results), // optional
}
},
placeholder: 'Compose an epic...',
theme: 'snow'
});
However, I do not know how to do it with react-quill. It would be great if you could help me here.

Related

How to return JSON object in Javascript?

I am having issues with returning a JSON object. When I render the webpage, nothing shows up. Does anyone know how to fix this? Sorry, I am new to Javascrtipt.
import React, { useEffect, useState, useContext } from 'react'
export const MarketData = () => {
var obj = {
width: '100%',
height: '100%',
symbolsGroups: [
{
name: 'Indices',
originalName: 'Indices',
symbols: [
{
name: 'INDEX:DEU30',
displayName: 'DAX Index',
},
{
name: 'FOREXCOM:UKXGBP',
displayName: 'FTSE 100',
},
],
},
...
],
showSymbolLogo: true,
colorTheme: 'dark',
isTransparent: false,
locale: 'en',
largeChartUrl:
'https://bondintelligence.cloud.looker.com/extensions/bond_intelligence_webpage::helloworld-js/',
}
return (
<>
<text>{obj}</text>
</>
)
}
You can use JSON.stringify()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
The third argument in JSON.stringify() provides new lines and indentation. If only the first argument is provided, the string will be one long line.
Your example with fix (I changed your <text> to <p> as I have never heard of a <text> HTML element):
import React, { useEffect, useState, useContext } from 'react'
export const MarketData = () => {
var obj = {
width: '100%',
height: '100%',
symbolsGroups: [
{
name: 'Indices',
originalName: 'Indices',
symbols: [
{
name: 'INDEX:DEU30',
displayName: 'DAX Index',
},
{
name: 'FOREXCOM:UKXGBP',
displayName: 'FTSE 100',
},
],
},
...
],
showSymbolLogo: true,
colorTheme: 'dark',
isTransparent: false,
locale: 'en',
largeChartUrl:
'https://bondintelligence.cloud.looker.com/extensions/bond_intelligence_webpage::helloworld-js/',
}
var objAsString = JSON.stringify(obj, null, 2)
return (
<>
<p>{objAsString}</p>
</>
)
}

How to render a material-table using a JSON that comes from a API response?

I'm new on ReactJS and this is my first page that I created, but I'm having some problems with set variables.
What I need is fill the variable table.data with the values that comes from const response = await api.get('/users') and render the table with this values when page loads.
I have the following code:
import React, { useState, useEffect } from 'react';
import { Fade } from "#material-ui/core";
import MaterialTable from 'material-table';
import { makeStyles } from '#material-ui/core/styles';
import api from '../../services/api.js';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
width: '70%',
margin: 'auto',
marginTop: 20,
boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.4)'
}
}));
function User(props) {
const classes = useStyles();
const [checked, setChecked] = useState(false);
let table = {
data: [
{ name: "Patrick Mahomes", sector: "Quaterback", email: "patrick#nfl.com", tel: "1234" },
{ name: "Tom Brady", sector: "Quaterback", email: "tom#nfl.com", tel: "5678" },
{ name: "Julio Jones", sector: "Wide Receiver", email: "julio#nfl.com", tel: "9876" }
]
}
let config = {
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Sector', field: 'sector' },
{ title: 'E-mail', field: 'email'},
{ title: 'Tel', field: 'tel'}
],
actions: [
{ icon: 'create', tooltip: 'Edit', onClick: (rowData) => alert('Edit')},
{ icon: 'lock', tooltip: 'Block', onClick: (rowData) => alert('Block')},
{ icon: 'delete', tooltip: 'Delete', onClick: (rowData) => alert('Delete')},
{ icon: 'visibility', tooltip: 'Access', onClick: (rowData) => alert('Access')},
{ icon: "add_box", tooltip: "Add", position: "toolbar", onClick: () => { alert('Add') } }
],
options: {
headerStyle: { color: 'rgba(0, 0, 0, 0.54)' },
actionsColumnIndex: -1,
exportButton: true,
paging: true,
pageSize: 10,
pageSizeOptions: [],
paginationType: 'normal'
},
localization: {
body: {
emptyDataSourceMessage: 'No data'
},
toolbar: {
searchTooltip: 'Search',
searchPlaceholder: 'Search',
exportTitle: 'Export'
},
pagination: {
labelRowsSelect: 'Lines',
labelDisplayedRows: '{from} to {to} for {count} itens',
firstTooltip: 'First',
previousTooltip: 'Previous',
nextTooltip: 'Next',
lastTooltip: 'Last'
},
header: {
actions: 'Actions'
}
}
}
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
table.data = response.data;
}
loadUsers();
}, [])
return (
<>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Fade in={checked} style={{ transitionDelay: checked ? '300ms' : '0ms' }}>
<div className={classes.root}>
<MaterialTable editable={config.editable} options={config.options} localization={config.localization} title="Usuários" columns={config.columns} data={table.data} actions={config.actions}></MaterialTable>
</div>
</Fade>
</>
);
}
export default User;
The previous example will show 3 users that I fixed on variable table.data with 4 columns (name, sector, email, tel).
In a functional component, each render is really a new function call. So any variables you declare inside the component and destroyed and re-created. This means that table is set back to your initial value each render. Even if your useEffect is setting it correctly after the first render, it will just be reset on the next.
This is what state is for: to keep track of variables between renders. Replace your let table, with a new state hook.
const [table, setTable] = useState({
data: [
{ name: "Patrick Mahomes", sector: "Quaterback", email: "patrick#nfl.com", tel: "1234" },
{ name: "Tom Brady", sector: "Quaterback", email: "tom#nfl.com", tel: "5678" },
{ name: "Julio Jones", sector: "Wide Receiver", email: "julio#nfl.com", tel: "9876" }
]
});
Then use it like this:
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
setTable(prev => ({...prev, data: response.data});
}
loadUsers();
}, [])
Since table.data is not a state variable, it is regenerated as it was declared originally every time the component renders, meaning that by the time it arrives as a prop to your component it will always be the same value (when you change the value of table.data in useEffect it is too late). You need to change table.data to a state variable, and then in your useEffect hook you can update the value of table.data to the value of response.data. This will cause the component to be re-rendered but with the updated value.
Here's an example of how you might do that:
import React, { useState, useEffect } from 'react';
import { Fade } from "#material-ui/core";
import MaterialTable from 'material-table';
import { makeStyles } from '#material-ui/core/styles';
import api from '../../services/api.js';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
width: '70%',
margin: 'auto',
marginTop: 20,
boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.4)'
}
}));
function User(props) {
const classes = useStyles();
const [checked, setChecked] = useState(false);
const [tableData, setTableData] = useState([]);
let config = {
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Sector', field: 'sector' },
{ title: 'E-mail', field: 'email'},
{ title: 'Tel', field: 'tel'}
],
actions: [
{ icon: 'create', tooltip: 'Edit', onClick: (rowData) => alert('Edit')},
{ icon: 'lock', tooltip: 'Block', onClick: (rowData) => alert('Block')},
{ icon: 'delete', tooltip: 'Delete', onClick: (rowData) => alert('Delete')},
{ icon: 'visibility', tooltip: 'Access', onClick: (rowData) => alert('Access')},
{ icon: "add_box", tooltip: "Add", position: "toolbar", onClick: () => { alert('Add') } }
],
options: {
headerStyle: { color: 'rgba(0, 0, 0, 0.54)' },
actionsColumnIndex: -1,
exportButton: true,
paging: true,
pageSize: 10,
pageSizeOptions: [],
paginationType: 'normal'
},
localization: {
body: {
emptyDataSourceMessage: 'No data'
},
toolbar: {
searchTooltip: 'Search',
searchPlaceholder: 'Search',
exportTitle: 'Export'
},
pagination: {
labelRowsSelect: 'Lines',
labelDisplayedRows: '{from} to {to} for {count} itens',
firstTooltip: 'First',
previousTooltip: 'Previous',
nextTooltip: 'Next',
lastTooltip: 'Last'
},
header: {
actions: 'Actions'
}
}
}
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
setTableData(response.data);
}
loadUsers();
}, [])
return (
<>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Fade in={checked} style={{ transitionDelay: checked ? '300ms' : '0ms' }}>
<div className={classes.root}>
<MaterialTable editable={config.editable} options={config.options} localization={config.localization} title="Usuários" columns={config.columns} data={tableData} actions={config.actions}></MaterialTable>
</div>
</Fade>
</>
);
}
export default User;

How to show the data I got from API to react-material datatable

I'm new when using materialUI table in react.js, I want to try using react-material table but I got lost as how can I show my data in the table, Let say I have 28 data and in fact it already show the right number in the pagination but the data itself doesn't show anything. this is the documentation link for react-material table Check this.
I already read several topic about this but all of them using tableRow, tableHead, and etc.
this is my component code:
import React, { Component } from 'react';
import MaterialTable from 'material-table';
import { history } from '../../../../Helpers/history';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { orderActions } from '../../../../Actions/orderActions';
import { withStyles } from '#material-ui/core/styles';
// Component
import './tabledata.css';
const styles = theme => ({
'#global': {
body: {
backgroundColor: theme.palette.common.white,
},
},
});
class Tabledata extends Component {
constructor(props) {
super(props);
// const { orders } = this.props;
this.state = {
columns: [
{ title: 'Nama Pemesanan', field: 'name' },
{ title: 'Status', field: 'status' },
{ title: 'Estimasi Pengerjaan (Hari)', field: 'estimate', type: 'numeric' },
{ title: 'Jumlah Pesanan (pcs)', field: 'unit', type: 'numeric' },
{ title: 'Harga (Rp)', field: 'price', type: 'numeric' },
],
data: [
{
id: 2,
name: 'lala',
status: 'Penyablonan',
estimate: 8,
unit: 36,
price: '36.000.000',
},
],
}
}
componentDidMount() {
if(localStorage.getItem('auth')) {
const { dispatch } = this.props;
dispatch(orderActions.getAllOrder());
// history.push('/dashboard');
}
}
componentWillReceiveProps(newProps){
this.setState({ loading: newProps.loading }); // remove the loading progress when logged in or fail to log in
}
handleChange = prop => event => {
this.setState({ [prop]: event.target.value });
};
change(data){
console.log("Check ID : ", data);
}
render(){
const { orders } = this.props;
console.log("test console : ", orders)
return (
<div className="tabledata-order">
<div className="row item-section">
<div className="col">
<div className="card">
<div className="card-body">
<MaterialTable
title="Daftar Pesanan"
columns={this.state.columns}
key={orders._id}
data={orders}
/>
</div>
</div>
</div>
</div>
</div>
);
}
}
Tabledata.propTypes = {
classes: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
const { orders } = state.orderPage;
return {
orders
};
}
const connectedTableDataPage = withRouter(connect(mapStateToProps, '', '', {
pure: false
}) (withStyles(styles)(Tabledata)));
export { connectedTableDataPage as Tabledata };
As you can see, this material table have a component like this
<MaterialTable
title="Daftar Pesanan"
columns={this.state.columns}
key={orders._id}
data={orders}
/>
As you can see, in the bottom of the image you can see 1-5 of 28 and in my console there is exactly 28 data but the table itself doesn't show any data
can someone help me? how can I show the data in orders and this is the example of the image json that I have:
Finally I can fix this problem, this answer for you who have facing the same problem with react-material table if your data doesn't show but it show in console.log. you must check the field in column
this.state = {
columns: [
{ title: 'Nama Pemesanan', field: 'name' },
{ title: 'Status', field: 'status' },
{ title: 'Estimasi Pengerjaan (Hari)', field: 'estimate', type: 'numeric' },
{ title: 'Jumlah Pesanan (pcs)', field: 'unit', type: 'numeric' },
{ title: 'Harga (Rp)', field: 'price', type: 'numeric' },
],
data: [
{
id: 2,
name: 'lala',
status: 'Penyablonan',
estimate: 8,
unit: 36,
price: '36.000.000',
},
],
}
let say, json that I got have city, color, and weight then you must state the column field as such:
this.state = {
columns: [
{ title: 'detail Address', field: 'city' },
{ title: 'color', field: 'color' },
{ title: 'weight', field: 'weight' },
],
}
and for the MaterialTable you can just put all the variable you have like this
<MaterialTable
title="Daftar Pesanan"
columns={this.state.columns}
key={orders._id}
data={orders}
/>
and you can get the data like I show you below
I hope this answer can help you who have a hard time with react-material table

Changing state in useEffect doesn't change interface

I'm using the useState and useEffect hooks in react to render a form. But when I'm updating the form using the useEffect hook. The form doesn't re-render.
import React, { useState, useEffect } from 'react';
import { makeStyles } from "#material-ui/core/styles";
import GridItem from "components/Grid/GridItem.js";
import GridContainer from "components/Grid/GridContainer.js";
import Card from "components/Card/Card.js";
import CardHeader from "components/Card/CardHeader.js";
import CardBody from "components/Card/CardBody.js";
import Input from "components/UI/Input/Input";
import Button from "components/CustomButtons/Button.js";
import styles from "styles/styles";
import falconAPI from "falcon-api";
const useStyles = makeStyles(styles);
export default function AddWarehouse(props) {
const classes = useStyles();
// State management hooks
const [form, setForm] = useState({
warehouseType: {
elementType: 'select',
elementConfig: {
options: [
{ value: '4', displayValue: 'Showroom' }
]
},
value: '1',
validation: {},
valid: true
},
territory: {
elementType: 'select',
elementConfig: {
options: [
{ value: '1', displayValue: 'Kandy' },
{ value: '2', displayValue: 'Jaffna' },
{ value: '3', displayValue: 'Colombo' },
{ value: '4', displayValue: 'Galle' }
]
},
value: '1',
validation: {},
valid: true
},
name: {
elementType: 'input',
elementConfig: {
type: 'text',
placeholder: 'Name'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
address: {
elementType: 'input',
elementConfig: {
type: 'text',
placeholder: 'Address'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
telephone: {
elementType: 'input',
elementConfig: {
type: 'text',
placeholder: 'Telephone'
},
value: '',
validation: {
required: true,
},
valid: false,
touched: false
},
});
// Life cycle hooks
useEffect(() => {
falconAPI.post('/warehouse/type/all')
.then(response => {
const warehouseTypes = response.data.message;
const updatedWarehouseTypes = []
warehouseTypes.map(warehouseType => {
updatedWarehouseTypes.push({
value: warehouseType.id,
displayValue: warehouseType.name
});
})
const updatedForm = { ...form };
updatedForm.warehouseType.options = updatedWarehouseTypes;
setForm(updatedForm);
})
.catch(error => {
});
}, []);
const inputChangedHandler = (e) => {
}
const submitFormHandler = (e) => {
console.log(form);
}
const formElementsArray = [];
for (let key in form){
formElementsArray.push({
id: key,
config: form[key]
})
}
return (
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card>
<CardHeader color="success">
<h4 className={classes.cardTitleWhite}>{props.title}</h4>
</CardHeader>
<CardBody>
{formElementsArray.map(formElement => (
<Input
key={formElement.id}
elementType={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
invalid={!formElement.config.valid}
shouldValidate={formElement.config.validation}
touched={formElement.config.touched}
changed={(event) => inputChangedHandler(event, formElement.id)} />
))}
<Button onClick={submitFormHandler}>Add Model</Button>
</CardBody>
</Card>
</GridItem>
</GridContainer>
);
}
In the useEffect hook, the api call update the form therefore re-rendering the warehouse type select input but the select input does not re-render. What could be the cause for this.
You need to copy the nested values too:
{
warehouseType: {
elementType: 'select',
elementConfig: {
options: [
{ value: '4', displayValue: 'Showroom' }
]
},
value: '1',
validation: {},
valid: true
},
...
const updatedForm = { ...form };
updatedForm.warehouseType.options = updatedWarehouseTypes;
setForm(updatedForm);
You also missed elementConfig in there. updatedForm.warehouseTypes.elementConfig.options
But it's still a good idea to copy the nested values too.
const updatedForm = {
...form,
warehouseType: {...form.warehouseType,
elementConfig: {...form.elementConfig,
options:updatedWarehouseTypes
}}};

Test react component can't get clientWidth

I'm trying to develop unit test for my react component with jest and enzyme. So basically my component have resize listener, when resize occured my component will update component state. But i just couldn't get the clientWidth for my react component. Below is some code of my component.
import React, { Component } from "react";
import moment from "moment";
// import PropTypes from "prop-types";
import Table from "./Table";
import Grid from "./Grid";
import ActionBlock from "../ActionBlock";
import ConfirmDialog from './ConfirmDialog';
import ReactTooltip from 'react-tooltip'
import { debounce } from '../../utils';
import styles from './styles.scss';
export default class Pagination extends Component {
constructor(props) {
super(props);
this.state = {
index: props.index,
type: props.type,
config: props.config,
data: props.data,
currentPage: 1,
dataPerPage: 20,
enableActionBlock: props.enableActionBlock,
confirmDialogIndex: null,
confirmDialogActionName: null,
confirmDialogData: null,
width: 0
};
this.handleWindowResize = debounce(this.handleWindowResize.bind(this), 100); //delay trigger resize event
}
componentDidMount() {
this.setState({ width: this.refs.pagination_wrapper.clientWidth })
window.addEventListener('resize', this.handleWindowResize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleWindowResize);
}
handleWindowResize = () => {
this.setState({ width: this.refs.pagination_wrapper.clientWidth })
}
render() {
return (
<div ref="pagination_wrapper" className={styles.pagination_wrapper}>
<ReactTooltip />
{this.renderViewType()}
{this.renderConfirmDialog()}
</div>
)
}
}
Pagination.defaultProps = {
enableActionBlock: true,
dataPerPage: 20
};
And below is my test code.
import React from 'react'
import { shallow, mount, render } from 'enzyme';
import Pagination from '../index';
let img = 'https://www.jqueryscript.net/images/Simplest-Responsive-jQuery-Image-Lightbox-Plugin-simple-lightbox.jpg';
let imageStream = 'http://192.168.100.125:8080/';
let imgQuoteError = `http://192.168.100.71/target-data/fr/target-person-images/1111112222233333#Rizkifika-Asanuli'nam/qTD8vYa.jpeg`;
describe('Testing Pagination', () => {
let action = (actionName, indexData) => {
console.log('action APP', actionName, indexData);
}
let dataListProps = {
index: 'id',
type: 'grid',
config: [
{ text: 'Image', type: 'image', textPath: 'image', textColor: 'red', valuePath: 'image' },
{ text: 'Fullname', type: 'string', textPath: 'fullname', valuePath: 'fullname' },
{ text: 'Role', type: 'string', textPath: 'role', valuePath: 'role' },
{ text: 'Datetime', type: 'date', textPath: 'datetime', valuePath: 'datetime' },
{ text: 'Json', type: 'json', textPath: 'json', valuePath: 'json' },
],
data: [
{ id: 305, created_at: '2018-02-23T09:43:08.928Z', rule_detail: { id: 1 }, cam_detail: { id: 2, name: 'kamera huawei' }, vas_detail: { id: 3, name: 'VAS 3' }, image: img },
{ id: 306, created_at: '2018-02-23T09:43:08.928Z', rule_detail: { id: 2, name: '' }, cam_detail: { id: 3, name: 'kamera avigilon' }, vas_detail: { id: 4, name: 'VAS 4' }, image: imageStream },
{ id: 306, created_at: '2018-02-23T09:43:08.928Z', rule_detail: { id: 2, name: null }, cam_detail: { id: 3, name: 'kamera avigilon' }, vas_detail: { id: 4, name: 'VAS 4' }, image: imgQuoteError },
{ id: 306, created_at: '2018-02-23T09:43:08.928Z', rule_detail: { id: 2, name: 'Crowd Behaviour' }, cam_detail: { id: 3, name: 'kamera avigilon' }, vas_detail: { id: 4, name: 'VAS 4' }, image: imageStream },
],
onAction: action,
enableActionBlock: false
}
it('snapshot', () => {
const wrapper = shallow(<Pagination {...dataListProps}/>)
expect(wrapper).toMatchSnapshot();
})
})
I need help for solving this
As pointed by Xarvalus. If wanna access refs, the component have to be mounted first by using mount from import { shallow, mount, render } from 'enzyme';.
But it will have bug (RangeError: Invalid string length). So to overcome this, we have to convert enzyme to json by using import toJson from 'enzyme-to-json';
full working code
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import toJson from 'enzyme-to-json';
import Pagination from '../index';
describe('Testing Pagination', () => {
it('snapshot', () => {
const wrapper = mount(<Pagination {...dataListProps}/>)
expect(toJson(wrapper)).toMatchSnapshot();
})
})
reference issue
You can access the window object inside your component, and so retrieve the window.innerWidth field which is, I guess, what you're looking for.

Categories

Resources