Using Dygraph in React with redux? - javascript

I've been having a lot of trouble implementing Dygraph in React (I'm using redux): http://dygraphs.com/. The Dygraph wrapper packages on NPM don't seem to work.
Also I can't simply use:
<div id="graph"></div>.
I believe this is because in react your working in a state instead of an actual index.html file.
So the method I'm currently trying to use is to create the graph component:
import React, { Component } from 'react';
import Dygraph from 'dygraphs';
import myData from '../../mockdata/sample-data.json';
import 'dygraphs/dist/dygraph.min.css'
import './graphComponent.css';
class DyGraph extends Component {
constructor(props) {
super(props);
// mock json data for graph
const messages = myData;
var data = "";
messages.forEach((response) => {
data += response[0] + ',' + response[1] + "\n";
});
new Dygraph('graphContainer', data, {
title: 'Pressure Transient(s)',
titleHeight: 32,
ylabel: 'Pressure (meters)',
xlabel: 'Time',
gridLineWidth: '0.1',
width: 700,
height: 300,
connectSeparatedPoints: true,
axes: { "x": { "axisLabelFontSize": 9 }, "y": { "axisLabelFontSize": 9 } },
labels: ['Date', 'Tampines Ave10 (Stn 40)'],
});
}
render() {
return <div></div>
}
}
export default DyGraph;
and then import it into:
import React, { Component } from 'react';
import DyGraph from './components/graph/graphComponent';
import './App.css';
class DeviceDetails extends Component {
render() {
return (
<div >
<DyGraph />
</div>
);
}
}
export default DeviceDetails;
And there is a Display state that if you click something it will go to:
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import WarningView from '../warning/warningView'
import DirectoryView from '../directory/directoryView'
import DeviceDetailView from '../devicedetails/devicedetails'
export const Display = ({ currentPage }) => {
switch(currentPage) {
case 'WARNING_PAGE':
return <WarningView/>;
case 'DIRECTORY_PAGE':
return <DirectoryView/>;
case 'SENSOR_PAGE':
return <DeviceDetailView/>;
default:
return <WarningView/>;
}
};
Display.propTypes = {
currentPage: PropTypes.string.isRequired,
};
export default connect(
(state) => ({ currentPage: state.currentPage }),
(dispatch) => ({ })
)(Display)
When I build and run this locally I get an error in the console (when I try to see the graph):
Uncaught (in promise) Error: Constructing dygraph with a non-existent div!
at Dygraph.__init__ (dygraph.js:217)
at new Dygraph (dygraph.js:162)
at new DyGraph (graphComponent.js:19)
at ReactCompositeComponent.js:295
at measureLifeCyclePerf (ReactCompositeComponent.js:75)
at ReactCompositeComponentWrapper._constructComponentWithoutOwner (ReactCompositeComponent.js:294)
at ReactCompositeComponentWrapper._constructComponent (ReactCompositeComponent.js:280)
at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:188)
at Object.mountComponent (ReactReconciler.js:46)
at ReactDOMComponent.mountChildren (ReactMultiChild.js:238)
If anyone can figure out what's going on or even give me a hint that'd be SOOO APPRECIATED. I specifically want to use dygraph instead of google charts or other react charts (which I've gotten working very easily), however, there is little information about dygraph implementation in React and I don't really understand why it won't work.

The problem is that this line:
new Dygraph('graphContainer', data, { ... })
Tries to create a Dygraph in the element with ID graphContainer. But there is no element with that ID, hence the failure.
You need to wait until React creates a div in the DOM to create the dygraph. You'll want to instantiate the Dygraph in componentDidMount:
class Dygraph extends Component {
render() {
return <div ref="chart"></div>;
}
componentDidMount() {
const messages = myData;
var data = "";
messages.forEach((response) => {
data += response[0] + ',' + response[1] + "\n";
});
new Dygraph(this.refs.chart, data, {
/* options */
});
}
}

Related

Is there anyway to use data from a json file conditionally?

I am using React and Sass on my project and a library called ParticleJs as background and I'm hoping to achieve dark mode feature on my project where when a user wants it, the data in ParticleJS json file is changed.
Here's a snippet of the json file I'd like to change conditionally specifically, bg.color.value
"background": {
"color": {
"value": "#edf2f8"
},
"position": "50% 50%",
"repeat": "no-repeat",
"size": "20%"
},
Here is the whole ParticleJS.jsx file:
import Particles from "react-tsparticles";
import { loadFull } from "tsparticles";
import particlesOptions from "./particles.json";
import React, { useCallback } from "react";
const ParticleBackground = () => {
const particlesInit = useCallback((main) => {
loadFull(main);
}, []);
const particlesLoaded = (container) => {
console.log(container);
};
return (
<Particles
init={particlesInit}
loaded={particlesLoaded}
options={particlesOptions}
/>
);
};
export default ParticleBackground;
Is there any other way that I can do it than creating another js folder (to maybe call two ParticleJS components conditionally) or maybe create another json file?
Thank you in advance
According to the docs you don't need to pass JSON into the options prop you can just pass a javascript opject. If you include a "useState" variable in the object for the bg color (for example) you can update it as you need.
import Particles from "react-tsparticles";
import { loadFull } from "tsparticles";
import React, { useCallback, useState} from "react";
const ParticleBackground = () => {
const particlesInit = useCallback((main) => {
loadFull(main);
}, []);
const particlesLoaded = (container) => {
console.log(container);
};
const [bgColor, setBgColor] = useState('#edf2f8');
//call setBgColor when you want to update the bg
return (
<Particles
init={particlesInit}
loaded={particlesLoaded}
options={{
background: {
color: {
value: bgColor
},
position: "50% 50%",
repeat: "no-repeat",
size: "20%"
}
}}
/>
);
};
export default ParticleBackground;
Sure - particlesOptions is imported as a plain Javascript object, so you can write a function to modify (a copy of) it, e. g.
function getParticleOptions(x) {
// Shallow copy of the original object
const options = {...particlesOptions};
options.x = x; // or whatever
return options;
}
Then pass the returned value to the particle component.

EditorJS in NextJS not able to load plugins

I am trying to get EditorJS working in NextJS. The editor loads fine without plugins, having the only paragraph as a block option. However, when I attempt to add plugins via tools prop console throws the following warning:
editor.js?9336:2 Module Tools was skipped because of TypeError: Cannot read property 'prepare' of undefined
When I click on the editor in the browser, it is throwing:
Uncaught TypeError: Cannot read property 'holder' of undefined
I have tested editor plugins in the normal React app, and they load fine. Meaning that the problem is in EditorJS and NextJS import and handling of plugins. I have tried to import editor and plugins in componentDidMount hook using require but had the same problem as with NextJS dynamic imports. Attempted to get component using React ref but found that currently NextJS has problems with getting components' refs, Tried suggested workaround but still had no result. The instance of the editor is not available until onChange is triggered, so plugins just cannot hook into the editor due to that 'prepare' property or the whole editor are being undefined until an event on editor has happened, but the editor outputs into the console that it is ready.
My component's code:
import React from "react";
import dynamic from "next/dynamic";
const EditorNoSSR = dynamic(() => import("react-editor-js"), { ssr: false });
const Embed = dynamic(() => import("#editorjs/embed"), { ssr: false });
class Editor extends React.Component {
state = {
editorContent: {
blocks: [
{
data: {
text: "Test text",
},
type: "paragraph",
},
],
},
};
constructor(props) {
super(props);
this.editorRef = React.createRef();
}
componentDidMount() {
console.log(this.editorRef.current);
console.log(this.editorInstance);
}
onEdit(api, newData) {
console.log(this.editorRef.current);
console.log(this.editorInstance);
this.setState({ editorContent: newData });
}
render() {
return (
<EditorNoSSR
data={this.state.editorContent}
onChange={(api, newData) => this.onEdit(api, newData)}
tools={{ embed: Embed }}
ref={(el) => {
this.editorRef = el;
}}
instanceRef={(instance) => (this.editorInstance = instance)}
/>
);
}
}
export default Editor;
Is there any solution to this problem? I know SSR is challenging with client side rendering of components that access DOM, but there was condition used that checked whether window object is undefined, however, it does not look like an issue in my situation.
UPDATE:
I have found a solution but it is rather not a NextJS way of solving the problem, however, it works. It does not require a react-editorjs and implemented as creation of EditorJS instance as with normal EditorJS.
class Editor extends React.Component {
constructor(props) {
super(props);
this.editor = null;
}
async componentDidMount() {
this.initEditor();
}
initEditor = () => {
const EditorJS = require("#editorjs/editorjs");
const Header = require("#editorjs/header");
const Embed = require("#editorjs/embed");
const Delimiter = require("#editorjs/delimiter");
const List = require("#editorjs/list");
const InlineCode = require("#editorjs/inline-code");
const Table = require("#editorjs/table");
const Quote = require("#editorjs/quote");
const Code = require("#editorjs/code");
const Marker = require("#editorjs/marker");
const Checklist = require("#editorjs/checklist");
let content = null;
if (this.props.data !== undefined) {
content = this.props.data;
}
this.editor = new EditorJS({
holder: "editorjs",
logLevel: "ERROR",
tools: {
header: Header,
embed: {
class: Embed,
config: {
services: {
youtube: true,
coub: true,
},
},
},
list: List,
inlineCode: InlineCode,
code: Code,
table: Table,
quote: Quote,
marker: Marker,
checkList: Checklist,
delimiter: Delimiter,
},
data: content,
});
};
async onSave(e) {
let data = await this.editor.saver.save();
this.props.save(data);
}
render() {
return (
<>
<button onClick={(e) => this.onSave(e)}>Save</button>
<div id={"editorjs"} onChange={(e) => this.onChange(e)}></div>
</>
);
}
}
This implementation works in NextJS
I will update code if I find a better solution.
UPDATE 2:
The answer suggested by Rising Odegua is working.
You have to create a seperate component and then import all your tools there:
import EditorJs from "react-editor-js";
import Embed from "#editorjs/embed";
import Table from "#editorjs/table";
import List from "#editorjs/list";
import Warning from "#editorjs/warning";
import Code from "#editorjs/code";
import LinkTool from "#editorjs/link";
import Image from "#editorjs/image";
import Raw from "#editorjs/raw";
import Header from "#editorjs/header";
import Quote from "#editorjs/quote";
import Marker from "#editorjs/marker";
import CheckList from "#editorjs/checklist";
import Delimiter from "#editorjs/delimiter";
import InlineCode from "#editorjs/inline-code";
import SimpleImage from "#editorjs/simple-image";
const CustomEditor = () => {
const EDITOR_JS_TOOLS = {
embed: Embed,
table: Table,
marker: Marker,
list: List,
warning: Warning,
code: Code,
linkTool: LinkTool,
image: Image,
raw: Raw,
header: Header,
quote: Quote,
checklist: CheckList,
delimiter: Delimiter,
inlineCode: InlineCode,
simpleImage: SimpleImage
};
return (
<EditorJs tools={EDITOR_JS_TOOLS} />
);
}
export default CustomEditor;
Then in your NextJS page, use a dynamic import like this:
let CustomEditor;
if (typeof window !== "undefined") {
CustomEditor = dynamic(() => import('../src/components/CustomEditor'));
}
And you can use your component:
return (
{CustomEditor && <CustomEditor />}
)
Source : https://github.com/Jungwoo-An/react-editor-js/issues/31

Using Canvas-Datagrid within React Component

I am trying to incorporate 'canvas-datagrid' module into React. However, I keep on getting this error:
Uncaught Error: Objects are not valid as a React child (found: [object HTMLElement]). If you meant to render a collection of children, use an array instead. ... in grid (created by CanvasGrid)...
The code is a slight modified version of the one on the React example:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import CanvasDataGrid from 'canvas-datagrid';
class CanvasGrid extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const args = {};
this.grid = ReactDOM.findDOMNode(this);
this.updateAttributes();
}
componentWillReceiveProps(nextProps) {
this.updateAttributes(nextProps);
}
shouldComponentUpdate() {
return false;
}
componentWillUnmount() {
this.grid.dispose();
}
updateAttributes(nextProps) {
Object.keys(this.props).forEach(key => {
if (!nextProps || this.props[key] !== nextProps[key]) {
if (this.grid.attributes[key] !== undefined) {
this.grid.attributes[key] = nextProps ? nextProps[key] : this.props[key];
} else {
this.grid[key] = nextProps ? nextProps[key] : this.props[key];
}
}
});
}
render() {
return (
<div>
<CanvasDataGrid />;
</div>
);
}
}
export default CanvasGrid;
As per my understanding of the example, there isn't anything special to be done, however, the error above is encountered when React tries to render <CanvasDataGrid> component.
The npm package canvas-datagrid exports a Web Component, not a react component. You have to render that component in your UI using react.
What you have to do is include the script in your index.html and then create a React component CanvasGrid with a render function:
render() {
return React.createElement('canvas-datagrid', {});
}
For full component code, see this file.

--How to make this React/Redux code DRY

I have repetitive code that I do not know how to make DRY ( Don't Repeat Yourself ).
Here are two components "talking" via dispatch() and React's auto re-render.
this.map is repeated twice.
This module will dispatch actions on a click.
import React from 'react';
import { connect } from 'react-redux';
class Icon extends React.Component {
constructor(props) {
super(props);
this.map = {
paper: 'bg_paper.jpg',
light_wood: 'bg_wood.jpg',
graph: 'bg_graph.jpg'
};
}
flip () {
this.props.dispatch({type: 'updateIcon', bg_key: $A.nextKey(this.map, this.props.state.bg_key)});
}
render () {
const style = {
// ... snip
}
return (
<img id = 'bar_icon' onClick={this.flip.bind(this)} style={style} src='_images/sv_favicon.svg'/>
)
}
}
const mapStateToProps = state => {
return {
state: state.Icon
}
}
export default connect(mapStateToProps)(Icon);
while this component will auto re-render. It all works fine. I just want to make it DRY.
import React from 'react';
import { connect } from 'react-redux';
// ... snip
class FrameBody extends React.Component {
constructor(props) {
super(props);
this.map = {
paper: 'bg_paper.jpg',
light_wood: 'bg_wood.jpg',
graph: 'bg_graph.jpg'
};
}
render () {
const style = {
backgroundImage: 'url(' + '_images/' + this.map[this.props.state.bg_key] + ')'
};
return (
<div id='contents' style={style}>
</div>
)
}
}
const mapStateToProps = state => {
return {
state: state.Icon
}
}
export default connect(mapStateToProps)(FrameBody);
What can I do so that there are not two instances of this.map?
You can extract the logic of this.map out to a class function.
getBackgroundImageKey = () => {
const backgroundMap = {
paper: 'bg_paper.jpg',
light_wood: 'bg_wood.jpg',
graph: 'bg_graph.jpg'
}
return backgroundMap[this.props.bg_key]
}
Take a step further and add another function to return the URL and add string interpolation.
getBackgroundImageURL(){
const backgroundMap = {
paper: 'bg_paper.jpg',
light_wood: 'bg_wood.jpg',
graph: 'bg_graph.jpg'
}
return `url(_images/${backgroundMap[this.props.bg_key]})`;
}
Which will let you define the style like this
const backgroundImage = this.getBackgroundImageURL()
const style = { backgroundImage };
Well since you're already using Redux and dispatching an action to flip, why don't you move that logic there?
Keep the current image in the store so you can get it in connect, make your flip action creator a thunk that holds that "map" and decides what's the next image.
Instead of DRYness, your code lacks separation of concerns. The switch/Icon UI component would be much more reusable and terse if it only called a prop whenever the user clicks "flips". Connect this onFlip to the action creator I mentioned and you have the logic in one place, and the UI to interact in another.

connect method of react breaking my test case

I am trying to write the test case for my jsx file...
i took the sample test case from another jsx file...
that file doesnt have connect method...
but his file has connect method..
I think because of this its breaking my test case...
can you guys tell me how to fix it..
providing my code below...
clear code below
https://gist.github.com/js08/d590e78e8923e68b191a
SyntaxError: C:/codebase/sports/test/sports-tests.js: Unexpected token (20:73)
18 |
19 | it('should render correctly', () => {
20 | shallowRenderer.render();
| ^
21 | /*let renderedElement = shallowRenderer.getRenderOutput();
22 |
test case
import {expect} from 'chai';
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import sportsPageDefault from '../src/sports-Page-Default';
import initializeJsDom from './test-utils/dom.js';
import {getGridLayoutClasses} from 'sports-css-grids';
//import _difference from 'lodash/array/difference';
describe('shallow renderer tests for sports-Page-Default ', function() {
let shallowRenderer = TestUtils.createRenderer();
console.log("shallowRenderer" + JSON.stringify(shallowRenderer));
it('should render correctly', () => {
shallowRenderer.render(<sportsPageDefault headerClass='8887' layout= {id: 100, text: 'hello world'} sidebar= {id: 100, text: 'hello world'} title="Demo" />);
let renderedElement = shallowRenderer.getRenderOutput();
console.log(renderedElement);
/* let actualTitleEl = renderedElement.props.children[0].props.children[0];
let expectedTitleEl = <h1 className="transactionalPageHeader-appName font-serif">Demo</h1>;
expect(actualTitleEl).to.deep.equal(expectedTitleEl);*/
});
});
actual code
import './css/sports-bottom-layout.css';
import './css/sports-Page.css';
import './css/sports-leftCornerLayout.css';
import React from 'react';
import PageHeader from './components/page-header/page-header';
import MainContent from './components/main-content';
import sports-bottom-layout from './components/sports-bottom-layout/sports-bottom-layout';
import {getPanelLayoutState} from './util/PageLayout';
import {getGridLayoutClasses} from 'sports-css-grids/lib/js/gridLayout';
import PagePureRenderMixin from './util/PagePureRenderMixin';
import {connect} from 'react-redux';
import {setHeaderPanelState, setRightPanelState} from './redux/layout/layout-actions';
console.log("inside");
let customMixin = PagePureRenderMixin({
state: {
mainPanelGridClassList: function(classArray) {
return classArray.length;
console.log("classArray.length" + classArray.length);
}
}
});
let PT = React.PropTypes;
let sportsPageDefault = React.createClass({
propTypes: {
headerClass: React.PropTypes.string,
layout: PT.object.isRequired,
sports-leftCornerLayout: PT.oneOfType([
PT.func,
PT.object
]),
title: PT.string.isRequired
},
//cpomponent m,ount code
});
function sportsShallow(itemA, itemB) {
for (let i in itemA) {
if (itemA[i] !== itemB[i]) {
return false;
}
}
return true;
}
export default connect(state => ({
layout: state.Page.layout
}))(sportsPageDefault);
Error:
ts throwing an error since not sure how to pass in onof types for function....
shallowRenderer.render(<sportsPageDefault headerClass='8887' layout= {{id: 100, text: 'hello world'}} sidebar= {[{onAppExit},{id: 100, text: 'hello world'}]} title={"Demo"} />);
propTypes: {
headerClass: React.PropTypes.string,
layout: PT.object.isRequired,
sports-leftCornerLayout: PT.oneOfType([
PT.func,
PT.object
]),
title: PT.string.isRequired
},
TypeError: Cannot read property 'propTypes' of undefined
at [object Object].ReactCompositeComponentMixin._processProps (\node_modules\react\lib\ReactCompositeComponent.js:352:20)
at [object Object].ReactCompositeComponentMixin.mountComponent (\node_modules\react\lib\ReactCompositeComponent.js:129:28)
at [object Object].wrapper as mountComponent
at [object Object].ReactShallowRenderer._render (\node_modules\react\lib\ReactTestUtils.js:362:14)
at [object Object].ReactShallowRenderer.render (\node_modules\react\lib\ReactTestUtils.js:344:8)
at Context. (C:/codebase/usaa-template-standard/test/usaa-template-standard-tests.js:23:25)
I would start by cleaning up your code a bit, it looks like a mess; you have some characters in there that would break your code but I assume you put for presentation purposes, and putting all the code into one code block makes it look very wrong.
Regardless, there is the big real issue that most people have when trying to test connected react-redux components. There is beautiful documentation in their library explaining a great way to test them, I'll summarize.
You can't render a connected component unless there is a Provider in an ancestor. You could always render one of those in your tests but the recommended solution is to export both the connected and not connected components. In your case, you would do it like this:
export let sportsPageDefault = React.createClass({
propTypes: {
headerClass: React.PropTypes.string,
layout: PT.object.isRequired,
sports-leftCornerLayout: PT.oneOfType([
PT.func,
PT.object
]),
title: PT.string.isRequired
},
});
export default connect(state => ({
layout: state.Page.layout
}))(sportsPageDefault);
Notice that I'm exporting a default as well as the class itself. This allows you to import the component the usual way in your production code, but in your tests, import as follows:
import { sportsPageDefault } from 'path/to/component';
What we do here is imported the not connected component. You have now decoupled redux from your component.
That error you're getting is hard to pinpoint given the state of your code. If you clean it up and provide more info we might be able to help, but this is a good starting point to testing redux components.

Categories

Resources