Handsontable & React - javascript

I'm starting with react and trying to set up handsontable in my react app following:
react-handsontable
// import React...
import React from 'react';
import ReactDOM from 'react-dom';
// ... Handsontable with its main dependencies...
import moment from 'moment';
import numbro from 'numbro';
import pikaday from 'pikaday';
import Zeroclipboard from 'zeroclipboard';
import Handsontable from 'handsontable';
// ... and HotTable
import HotTable from 'react-handsontable';
class ExampleComponent extends React.Component {
constructor(props) {
super(props);
this.handsontableData = [
["", "Ford", "Volvo", "Toyota", "Honda"],
["2016", 10, 11, 12, 13],
["2017", 20, 11, 14, 13],
["2018", 30, 15, 12, 13]
];
}
render() {
return (
<div id="example-component">
<HotTable root="hot" data={this.handsontableData} colHeaders={true} rowHeaders={true} width="600" height="300" stretchH="all" />
</div>
);
}
}
Works so far but how do I get the instance of the table like in pure javascript
var ht = new Handsontable(document.getElementById('example1'), options);
ht.setDataAtCell(0, 0, 'new value');
Thanks, Tim

If you're trying to access the core methods, like 'setDataAtCell()', you can use refs to access them.
For example add the "ref='xyz'" attribute to the HTML element and you can then call it with "this.refs.xyz". I've modified your example below to illustrate. It adds a button that onClick runs a function to 'setDataAtCell'.
// import React...
import React from 'react';
import ReactDOM from 'react-dom';
// ... Handsontable with its main dependencies...
import moment from 'moment';
import numbro from 'numbro';
import pikaday from 'pikaday';
import Zeroclipboard from 'zeroclipboard';
import Handsontable from 'handsontable';
// ... and HotTable
import HotTable from 'react-handsontable';
class ExampleComponent extends React.Component {
constructor(props) {
super(props);
this.handsontableData = [
["", "Ford", "Volvo", "Toyota", "Honda"],
["2016", 10, 11, 12, 13],
["2017", 20, 11, 14, 13],
["2018", 30, 15, 12, 13]
];
}
handleClick(e) {
this.refs.hot.hotInstance.setDataAtCell(0, 0, 'new value')
}
render() {
return (
<div id="example-component">
<HotTable root="hot"
data={this.handsontableData}
colHeaders={true}
rowHeaders={true}
width="600"
height="300"
stretchH="all"
ref="hot"
/>
<br/>
<button onClick={(e)=>this.handleClick(e)}>Click Me</button>
</div>
);
}
}
export default ExampleComponent
This method can be used to access other methods like "getData()" which can be used to get a snapshot of the data in the table which can be saved to state or similar. So it's more lengthy than "ht" you can use "this.refs.hot.hotInstance" for similar affect.
You can read more about the ref attribute in "Refs and the DOM" in the React documentation.

To access the instance of HotTable component, use ref. However, don't use old way of using ref. As of version 16.3, React provides 2 ways :
using createRef API which is the recommended way
using callback ref
which is already available in earlier version, but better than the
old "string ref" way.
I only show how to use createRef here :
In your constructor, add the following :
this.refToHotIns=React.createRef();
The name of the field property is arbitrary, you can name it as you like.
In your render() method, change the JSX element to :
<HotTable ref={this.refToHotIns} data={this.handsontableData} colHeaders={true} rowHeaders=true} width="600" height="300" stretchH="all" />
Now, this.refToHotIns.current references to mounted HotTable instance. The Handsontable instance is stored under the hotInstance property of the wrapper component. So you can then access properties/methods of HotTable instance, like so :
this.refToHotIns.current.hotInstance.setDataAtCell(0, 0, 'new value');

It's pretty simple to write your own react wrapper for handsontable, and then you can create a reference to your HOT instance. Here is some sample React component code:
componentWillReceiveProps({ gridSpec }) {
if (gridSpec) {
const {columns, colHeaders, data} = gridSpec;
const container = document.getElementById(GRID_ID);
this.hotInstance = new handsontable(container, {
columns,
colHeaders,
data,
height: 600
});
}
}
shouldComponentUpdate () {
return false;
}
render () {
return (
<div style={{overflowX: 'hidden', height: '600px'}}>
<div id={GRID_ID} />
</div>
)
}

you have your data as an array defined in the constructor.
this.handsonetableData = [[First row],[second row]]
You could simply link that data to a state and setState inside your component. It should be really straightforward.
this.state = {
table: [[first line], [second line]]
}
Eventually going further you can write your own method that update a specific value of that array.
constructor (props) {
super(props)
this.state = {
table: [[first row], [second row]]
}
}
updateValueInTable (row, col, value)
checkIdontDoAnythingStupid()
let newTable = this.state.table
newTable[row][col] = value
this.setState({ table: newTable})
}
If you do not want to use the state and use something like redux you put the logic in the reducer and the triggers in actions. And there you are!
Of course is up to you how to check that you manipulate correctly your array, a lot of libraries can come to help (lodash and co.)

Related

Handsontable disabled/grayed-out column insert/remove options in contextMenu

So I'm using Handsontable library in React to create excel like tables, where HotTable is library component with some options, like contextMenu - modal window , and columns.
Issue is that if I specify columns in HotTable component , in modal window insert/remove column functionality is disabled/grayed-out , not removed. How to enable it?
import { useMemo } from 'react'
import HotTable from '#handsontable/react';
import { registerAllModules } from 'handsontable/registry';
import { ColumnSettings, GridSettings } from "handsontable/settings";
import 'handsontable/dist/handsontable.full.min.css';
registerAllModules();
const MIN_COLS = 30;
const MIN_ROWS = 500;
const data = [
['', 'Tesla', 'Volvo', 'Toyota', 'Ford', 'Audi'],
['2019', 10, 11, 12, 13],
['2020', 20, 11, 14, 13],
['2021', 30, 15, 12, 13]
];
export const excelTables = () => {
const columns = [...new Array(MIN_COLS)].map(
() =>
({
// some key : value pairs -- super important
} as ColumnSettings)
);
return (
<HotTable
data={data}
rowHeaders={true}
colHeaders={true}
contextMenu={true}
columns={columns}
licenseKey={ "non-commercial-and-evaluation"}
/>
);
};
with columns option in HotTable component
without columns option in HotTable component
Columns - in docs said that this option overwrites the top-level grid options. This is just minimal reproducible example, in project I need to specify columns option.

Next.js: window is not defined

I'm trying to use apexcharts for a next.js application and it's returning me window is not defined.
I would love any help with that.
Does someone know what is happening and why?
import React from 'react';
import Chart from 'react-apexcharts';
export default class Graficos extends React.Component <{}, { options: any, series: any }> {
constructor(props:any) {
super(props);
this.state = {
options: {
chart: {
id: "basic-bar"
},
xaxis: {
categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999]
}
},
series: [
{
name: "series-1",
data: [30, 40, 45, 50, 49, 60, 70, 91]
}
]
};
}
render() {
return (
<div className="row">
<h1>Gráfico Básico</h1>
<div className="mixed-chart">
<Chart
options={this.state.options}
series={this.state.series}
type="bar"
width={500}
/>
</div>
</div>
);
}
}
One of Next.js's key features is that it can render parts of your React application on the server or even at build time. While this can be helpful in improving your page's performance, the downside is that the server does not provide the all same APIs that your application would have access to in the browser. In this case, there is no global window object defined.
Unfortunately, searching the source code for apexcharts.js turns up many references to window: https://github.com/apexcharts/apexcharts.js/search?q=window. This also occurs in their React wrapper: https://github.com/apexcharts/react-apexcharts/blob/ecf67949df058e15db2bf244e8aa30d78fc8ee47/src/react-apexcharts.jsx#L5. While there doesn't seem to be a way to get apexcharts to avoid references to window, you can prevent Next.js from using the chart on the server. The simplest way to do that is to wrap any reference to the code with a check for whether window is defined, e.g.
<div className="mixed-chart">
{(typeof window !== 'undefined') &&
<Chart
options={this.state.options}
series={this.state.series}
type="bar"
width={500}
/>
}
</div>
With apexcharts, you will also need to do this for the component import because the import alone will trigger a reference to window as shown in that second link. In order to get around that problem you will need to use a dynamic import as opposed to the normal import you currently have: https://nextjs.org/docs/advanced-features/dynamic-import
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });
if (typeof window !== "undefined") {
host = window.location.host;
console.log("host--------------", host);
}

How do I write script in React index.js?

I've been using basic HTML/CSS/JS so far, and now I tried using React/Gatsby.
I got API code from Kakao and confirmed the following code is working in index.html:
<body>
<div id="map" style="width:1000px;height:500px;"></div>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=3199e8f198aff9d5aff73000faae6608"></script>
<script>{
var mapContainer = document.getElementById('map'),
mapOption = {
center: new kakao.maps.LatLng(37.56591, 126.97894),
level: 4,
mapTypeId : kakao.maps.MapTypeId.ROADMAP
};
var map = new kakao.maps.Map(mapContainer, mapOption);
}</script>
</body>
Since I'm trying React/Gatsby framework, I have to somehow reformat that script to index.js. HTML can be easily copy/pasted to return function, but I don't know how to write the above script in React index.js.
import React from "react"
export default class Home extends React.Component {
render() {
return (
<div style={{ color: `purple` }}>
<p>Welcome to donghwankim.com!</p>
<p>Powered by Gatsby</p>
<div id="map" style={{"height" : "1000px", "width" : "500px"}}></div>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=3199e8f198aff9d5aff73000faae6608"></script>
<script>{
var mapContainer = document.getElementById('map'),
mapOption = {
center: new kakao.maps.LatLng(37.56591, 126.97894),
level: 4,
mapTypeId : kakao.maps.MapTypeId.ROADMAP
};
var map = new kakao.maps.Map(mapContainer, mapOption);
}</script>
</div>
)}
}
Few things I tried:
Just copy paste script like above. In this case, I get syntax error from the copied script:
Unexpected token. Did you mean {'}'} or &rbrace;?
Use dangerouslySetInnerHTML. There is no syntax error, but the map API is not working properly.
Thank you.
It seems like part of this script will need to be rewritten for React. You should look for a library for this that supports React. If there isn't one, you can use DOM refs to access the mapContainer without document.getElementById. Alternatively if you just want to use this script as-is, you can use a simpler static site generator like Jekyll that doesn't require you to use React.
2022 update
Since the release of the Script Gatsby component (powered by Partytown) it's much easier adding third-party scripts. Just:
import React from "react"
import { Script } from "gatsby"
function YourPage() {
return <Script src="https://my-example-script" />
}
export default YourPage
There's a lot of implementation there.
First of all, you need to load your script asynchronously using <Helmet> tag by using:
import React from "react"
import Helmet from "react-helmet"
export default class Home extends React.Component {
render() {
return (
<div style={{ color: `purple` }}>
<Helmet>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=3199e8f198aff9d5aff73000faae6608" type="text/javascript"/>
</Helmet>
<p>Welcome to donghwankim.com!</p>
<p>Powered by Gatsby</p>
<div id="map" style={{"height" : "1000px", "width" : "500px"}}></div>
</div>
)}
}
Because of the asynchronous of your issue, you need to load a <div> container for your map and wait for its load, then you need to pass your map options. The preferred method in React, rather than document.getElementById (or similar), what retrieve directly values from the DOM, is using references. You'll need to use a componentDidMount() lifecycle to achieve it, since it's a method invoked that triggers immediately after a component is mounted (inserted into the tree):
import React from "react"
import Helmet from "react-helmet"
export default class Home extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount(){
const map= this.myRef.current;
const mapOption = {
center: new kakao.maps.LatLng(37.56591, 126.97894),
level: 4,
mapTypeId : kakao.maps.MapTypeId.ROADMAP
};
const yourMap = new kakao.maps.Map(map, mapOption);
}
render() {
return (
<div style={{ color: `purple` }}>
<Helmet>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=3199e8f198aff9d5aff73000faae6608" type="text/javascript"/>
</Helmet>
<p>Welcome to donghwankim.com!</p>
<p>Powered by Gatsby</p>
<div id="map" ref={this.myRef} style={{"height" : "1000px", "width" : "500px"}}></div>
</div>
)}
}
Note: you may need to unmount the map to avoid excessive resource consumption. It depends on how the library is implemented and its documentation.
Recommended readings/references:
https://github.com/nfl/react-helmet
https://reactjs.org/docs/refs-and-the-dom.html
https://reactjs.org/docs/react-component.html#componentdidmount
https://reactjs.org/docs/state-and-lifecycle.html

How to use vanilla javascript inside Reactjs component?

I am using chartist.js and I am using the chartist within reactjs component.
I am referring this http://gionkunz.github.io/chartist-js/examples.html#simple-pie-chart
chartist.js:
var Chartist = {
version:'0.9.5'
}
(function (window, document, Chartist) {
var options = {
labelInterpolationFnc: function(value) {
return value[0]
}
};
var responsiveOptions = [
['screen and (min-width: 640px)', {
chartPadding: 30,
labelOffset: 100,
labelDirection: 'explode',
labelInterpolationFnc: function(value) {
return value;
}
}],
['screen and (min-width: 1024px)', {
labelOffset: 80,
chartPadding: 20
}]
];
})();
Reactjs component:
import React, { Component } from 'react';
var data = {
labels: ['Bananas', 'Apples', 'Grapes'],
series: [20, 15, 40]
};
showPieChart(data){
new Chartist.Pie('.ct-chart', data, options, responsiveOptions);
}
class Chart extends Component {
render(){
return(
<div>
<div className="center">
{showPieChart}
</div>
</div>
)}
}
export default Chart;
Nothing is displayed on web page. How can I access vanilla javascript inside react component.
Your question is a little bit misleading, and can be interpreted in two ways.
#1. If you're asking how to integrate Chartist library with React, here's how you can do it:
There's a wrapper library, that already did it for us: https://www.npmjs.com/package/react-chartist
You can use it as follow (example taken from their repo):
import React from 'react';
import ReactDOM from 'react-dom';
import ChartistGraph from 'react-chartist';
class Pie extends React.Component {
render() {
var data = {
labels: ['W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8', 'W9', 'W10'],
series: [
[1, 2, 4, 8, 6, -2, -1, -4, -6, -2]
]
};
var options = {
high: 10,
low: -10,
axisX: {
labelInterpolationFnc: function(value, index) {
return index % 2 === 0 ? value : null;
}
}
};
var type = 'Bar'
return (
<div>
<ChartistGraph data={data} options={options} type={type} />
</div>
)
}
}
ReactDOM.render(<Pie />, document.body)
#2. If you generally asking how to integrate other libraries into React, then I recommend you to check the official React docs, because there's a really good tutorial about the topic - Integrating with Other Libraries
So, if you don't want to use the wrapper library (react-chartist), then you can check its main component too. It's a great starting point (that follows React recommendations) to understand how to create your own wrapper: https://github.com/fraserxu/react-chartist/blob/master/index.js

How can I pass an object from container to class in Meteor and React?

I'm trying to get a value in an object of javascript but it fails somehow. I managed to get an intended data from mongoDB by findOne method. Here is my code and console log.
const title = Questions.findOne({_id: props.match.params.id});
console.log(title);
Then console says:
Object {_id: "bpMgRnZxh5L4rQjP9", text: "Do you like apple?"}
What I wanna get is only the text in the object. I have already tried these.
console.log(title.text);
console.log(title[text]);
console.log(title["text"]);
console.log(title[0].text);
But I couldn't access to it... The error message is below.
Uncaught TypeError: Cannot read property 'text' of undefined
It sounds super easy but I couldn't solve by my self. Could anyone help me out?
Additional Context
I'm using Meteor and React. I would like to pass the text inside of the object from the container to the class. I would like to render the text in render(). But it doesn't receive any data from the container... The console.log in the container works well and shows the object.
import React, { Component } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Questions } from '../../api/questions.js';
import PropTypes from 'prop-types';
import { Answers } from '../../api/answers.js';
import ReactDOM from 'react-dom';
import { Chart } from 'react-google-charts';
class MapClass extends React.Component{
handleAlternate(event){
event.preventDefault();
const country = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Answers.insert({
country,
yes: false,
question_id:this.props.match._id,
createdAt: new Date(), // current time
});
ReactDOM.findDOMNode(this.refs.textInput).value = '';
}
handleSubmit(event) {
event.preventDefault();
const country = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Answers.insert({
country,
yes: true,
question_id: this.props.match.params.id,
createdAt: new Date(), // current time
});
ReactDOM.findDOMNode(this.refs.textInput).value = '';
}
constructor(props) {
super(props);
this.state = {
options: {
title: 'Age vs. Weight comparison',
},
data: [
['Country', 'Popularity'],
['South America', 12],
['Canada', 5.5],
['France', 14],
['Russia', 5],
['Australia', 3.5],
],
};
this.state.data.push(['China', 40]);
}
render() {
return (
<div>
<h1>{this.props.title.text}</h1>
<Chart
chartType="GeoChart"
data={this.state.data}
options={this.state.options}
graph_id="ScatterChart"
width="900px"
height="400px"
legend_toggle
/>
<form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
<input
type="text"
ref="textInput"
placeholder="Type to add new tasks"
/>
<button type="submit">Yes</button>
<button onClick={this.handleAlternate.bind(this)}>No</button>
</form>
</div>
);
}
}
export default MapContainer = createContainer(props => {
console.log(Questions.findOne({_id: props.match.params.id}));
return {
title: Questions.findOne({_id: props.match.params.id})
};
}, MapClass);
The problem here is that at the moment when the container is mounted, the data is not yet available. Since I do not see any subscriptions in your container I assume that you handle that elsewhere and thus there is no way of knowing when the data is ready. You have 2 options.
1) move the subscription into the container and use the subscription handle ready() function to assess if the data is ready. Show a spinner or something while it is not. Read this.
2) use lodash/get function (docs) to handle empty props. You would need to
npm install --save lodash
and then
import get from 'lodash/get';
and then in your class render method:
render() {
const text = get(this.props, 'title.text', 'you-can-even-define-a-default-value-here');
// then use `text` in your h1.
return (...);
}
Does this work for you?

Categories