Chart js in React/Gatsby - javascript

Hello I am trying to use chart js in Gatsby. I am currently following a tutorial for using chart js with react and I am not sure if the issue is that he is using creat-react-app and not Gatsby, but the errors do not seem to indicate that.
first I installed the following:
npm i --save react-chartjs-2
then
npm i --save chart.js
chartData.js:
import React, {useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
const chartData = () => {
const [chartData, setChartData] = useState({});
const chart = () => {
setChartData({
labels: ["monday", "tuesday", "wednesday", "thursday", "friday"],
datasets: [
{
level: 'level of xyz',
data: [32, 55, 33, 47, 64]
}
]
})
}
useEffect(() => {
chart()
}, [])
return(
<div>
<h1>Hello</h1>
<div>
<Line data={chartData}/>
</div>
</div>
)
}
export default chartData;
I am getting these erros:
6:39 error React Hook "useState" is called in function "chartData" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
20:5 error React Hook "useEffect" is called in function "chartData" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks

You have a name cohersion issue. Try renaming the functions and variables with a diferent name:
import React, { useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
const ChartData = () => {
const [whatever, setWhatever] = useState({});
const chart = () => {
setWhatever({
labels: ["monday", "tuesday", "wednesday", "thursday", "friday"],
datasets: [
{
level: 'level of xyz',
data: [32, 55, 33, 47, 64]
}
]
})
}
useEffect(() => {
chart()
}, [])
return(
<div>
<h1>Hello</h1>
<div>
<Line data={whatever}/>
</div>
</div>
)
}
export default ChartData;
In your previous code:
const chartData = () => {
const [chartData, setChartData] = useState({});
...
}
Where chartData is duplicated causing the error.
In addition, your chartData must be ChartData since, in React, the components must be capitalized.

You need to capitalize the name of the component. Right now you are setting the component to chartData, and then defining chartData as a hook. Change the component to ChartData.js

Related

React-Leaflet LocateControl component - keeps duplicating on each refresh

I'm using Locate Control in React Leaflet, but the Locate Control buttons are always duplicated, and sometimes I get 3 or 4 of them (see image below). I'm running the function through a useEffect with empty dependency to only fire it once, but no matter. I can target the class with display: none, but then both disappear. I feel like this might be an issue with Locate Control library? Really not sure. Open to any help or ideas.
import { useEffect } from "react"
import { useMap } from "react-leaflet"
import Locate from "leaflet.locatecontrol"
import "leaflet.locatecontrol/dist/L.Control.Locate.min.css"
const AddLocate = () => {
const map = useMap()
useEffect(() => {
const locateOptions = {
position: "bottomleft",
flyTo: true,
}
const locateControl = new Locate(locateOptions)
locateControl.addTo(map)
}, [])
return null
}
export default AddLocate;
Looks like you use a package made for leaflet. Which should for the most parts be okay. However the way you add the control is not really the react-leaflet way, where we want to add add components rather than add "stuff" directly to the map.
Below you can see how easy it is to implement a location component that you simply just can add as component within your MapContainer.
import { ActionIcon } from "#mantine/core";
import React, { useState } from "react";
import { useMapEvents } from "react-leaflet";
import { CurrentLocation } from "tabler-icons-react";
import LeafletControl from "./LeafletControl";
interface LeafletMyPositionProps {
zoom?: number;
}
const LeafletMyPosition: React.FC<LeafletMyPositionProps> = ({ zoom = 17 }) => {
const [loading, setLoading] = useState<boolean>(false);
const map = useMapEvents({
locationfound(e) {
map.flyTo(e.latlng, zoom);
setLoading(false);
},
});
return (
<LeafletControl position={"bottomright"}>
<ActionIcon
onClick={() => {
setLoading(true);
map.locate();
}}
loading={loading}
variant={"transparent"}
>
<CurrentLocation />
</ActionIcon>
</LeafletControl>
);
};
export default LeafletMyPosition;
And for LeafletControl I just have this reusable component:
import L from "leaflet";
import React, { useEffect, useRef } from "react";
const ControlClasses = {
bottomleft: "leaflet-bottom leaflet-left",
bottomright: "leaflet-bottom leaflet-right",
topleft: "leaflet-top leaflet-left",
topright: "leaflet-top leaflet-right",
};
type ControlPosition = keyof typeof ControlClasses;
export interface LeafLetControlProps {
position?: ControlPosition;
children?: React.ReactNode;
}
const LeafletControl: React.FC<LeafLetControlProps> = ({
position,
children,
}) => {
const divRef = useRef(null);
useEffect(() => {
if (divRef.current) {
L.DomEvent.disableClickPropagation(divRef.current);
L.DomEvent.disableScrollPropagation(divRef.current);
}
});
return (
<div ref={divRef} className={position && ControlClasses[position]}>
<div className={"leaflet-control"}>{children}</div>
</div>
);
};
export default LeafletControl;
I would do some debugging to that useEffect to see if it's only happening once. It's possible the entire component is mounted multiple times.

Editorjs custom block renders twice on click when made as default block in react project

I followed this tutorial to integrate editorjs in react and create a custom editorjs plugin. This tutorial works fine to create a custom block but I wanted to make the custom block as a default block in editorjs. In doing so, the block renders twice when I click on empty space in the editor. What might be the problem.
This is code for Editor.jsx file to create editorjs text editor:
import { default as React, useEffect, useRef } from "react";
import EditorJS from "#editorjs/editorjs";
import { EDITOR_JS_TOOLS } from "./tools";
import { Box } from "#mui/material";
const DEFAULT_INITIAL_DATA = () => {
return {
time: new Date().getTime(),
blocks: [
{
type: "header",
data: {
text: "This is my awesome editor!",
level: 1,
},
},
],
};
};
const EDITTOR_HOLDER_ID = "editorjs";
const Editor = (props) => {
const ejInstance = useRef();
const [editorData, setEditorData] = React.useState(DEFAULT_INITIAL_DATA);
// This will run only once
useEffect(() => {
if (!ejInstance.current) {
initEditor();
}
return () => {
ejInstance.current.destroy();
ejInstance.current = null;
};
}, []);
const initEditor = () => {
const editor = new EditorJS({
holder: EDITTOR_HOLDER_ID,
logLevel: "ERROR",
data: editorData,
onReady: () => {
ejInstance.current = editor;
},
onChange: async () => {
let content = await this.editorjs.saver.save();
// Put your logic here to save this data to your DB
setEditorData(content);
},
autofocus: true,
tools: EDITOR_JS_TOOLS,
defaultBlock: "timeline", // I have made timeline (custom block) as default block.
});
};
return (
<React.Fragment>
<Box id={EDITTOR_HOLDER_ID} sx={{ py: 2 }}></Box>
</React.Fragment>
);
};
export default Editor;
i also faced the same issue so i used react-editor-js instead, however simply rendering the ReactEditor component cannot be done as it will give the same result(duplicated blocks) as the parent component gets rendered twice, i don't know why, i kept the editorjs rendering mecahnism in the useEffect and refer the render container by id.
import React from "react";
import ReactDOM from "react-dom";
import { createReactEditorJS } from "react-editor-js";
import Header from "#editorjs/header";
import textBox from "./tools/textBox";
const ReactEditor = createReactEditorJS();
const Editor = () => {
React.useEffect(() => {
ReactDOM.render(
<ReactEditor
tools={{
header: Header,
textBox: textBox,
}}
defaultBlock="textBox"
/>,
document.getElementById("react-editor-container")
);
}, []);
return <div id="react-editor-container"></div>;
};
However, i am a noob and this may not be the best solution !!

problem in fetching data from a file by using useReducer in reactjs

I am new to reactjs and playing with useState and useReducer. I have data in a file and from that file I want to get data. I can do it with useState but when I want to do it by using useReducer I get an error. Another interesting thing is that, if I insert that same data in useReducer’s initial state it works and displays the data.
Below is the code and data file.
dataOnFile.js (file from which I want to fetch data)
const dataOnFile = [
{
id: 1,
name: "ahmad",
status: true,
},
{
id: 2,
name: "john",
status: true,
},
{
id: 3,
name: "sara",
status: true,
},
];
export default dataOnFile;
globalContext.js (using context to use it in other compnents, in this I am using useState and by using useState it works fine, it accepts the Items as an initial state)
import React, { createContext, useReducer, useState } from "react";
import Items from "./dataOnFile";
export const GlobalContext = createContext();
export function GlobalProvider(props) {
const [items, setItems] = useState(Items);
return (
<div>
<GlobalContext.Provider value={[items, setItems]}>
{props.children}
</GlobalContext.Provider>
</div>
);
}
GlobalContext.js (by using useReducer, I get an error, it's not accepting Items as an initial state)
ReferenceError: Cannot access 'Items' before initialization
import React, { createContext, useReducer, useState } from "react";
import Items from "./dataOnFile";
export const GlobalContext = createContext();
function itemsReducer(Items, action) {
return <div></div>;
}
export function GlobalProvider(props) {
const [Items, itemsDispatch] = useReducer(itemsReducer, Items);
return (
<div>
<GlobalContext.Provider value={[Items, itemsDispatch]}>
{props.children}
</GlobalContext.Provider>
</div>
);
}
globalContext.js (if I put data on useReducer’s initial state it works)
import React, { createContext, useReducer, useState } from "react";
import Items from "./dataOnFile";
export const GlobalContext = createContext();
function itemsReducer(Items, action) {
return <div></div>;
}
export function GlobalProvider(props) {
const [Items, itemsDispatch] = useReducer(itemsReducer, [
{
id: 1,
name: "ahmad",
status: true,
},
{
id: 2,
name: "sheeraz",
status: true,
},
]);
return (
<div>
<GlobalContext.Provider value={[Items, itemsDispatch]}>
{props.children}
</GlobalContext.Provider>
</div>
);
}
I think the problem here is that you're putting Items as the state for the reducer, the initial state and the parameter in the reducer. Note that useReducer calls the hook, thats where you want to pass your initial state.
Try this in the component:
const [items, itemsDispatch] = useReducer(itemsReducer, Items);
(Note that there is no capital 'I' in that state name)
And this in the reducer:
const itemsReducer = (state, action) => {
// Do stuff
}

How to re-render React Component when promise resolves? | How to block render until data loads?

I’ve tried to update a functional component that points to an azure-devops-ui/Filter. I am using azure-devops-extension-sdk that returns an async response, in order to use this component:
https://developer.microsoft.com/en-us/azure-devops/components/filter
inside a WorkItem on Azure DevOps
I’ve already code with both a class-based and function component using this.state/componentDidMount and useState/useEffect respectively. I followed this SO post.
However, I only can re-render the state. The UI component neither in a class nor in a functional component updates when the state is updated.
There are my two versions of the code, both them wait for the response and successfully update state. However, neither will wait for render of the UI.
General Component:
import {
IWorkItemChangedArgs,
IWorkItemFieldChangedArgs,
IWorkItemFormService,
IWorkItemLoadedArgs,
WorkItemTrackingServiceIds,
} from "azure-devops-extension-api/WorkItemTracking";
import * as React from "react";
import * as SDK from "azure-devops-extension-sdk";
import { Header } from "azure-devops-ui/Header";
import { Page } from "azure-devops-ui/Page";
import { Filter, getKeywordFilterItem } from "azure-devops-ui/Filter";
import { IListBoxItem } from "azure-devops-ui/ListBox";
import { AggregateItemProvider } from "azure-devops-ui/Utilities/AggregateItemProvider";
import {
Filter as FilterStore,
FILTER_CHANGE_EVENT,
IFilterState
} from "azure-devops-ui/Utilities/Filter";
import { GroupedItemProvider } from "azure-devops-ui//Utilities/GroupedItemProvider";
import { groupedItems, groups, statusItems } from "./data";
export class WorkItemComponent extends React.Component<{} & ExtendedProps, any> {
private provider: AggregateItemProvider<IListBoxItem>;
private filterStore = new FilterStore();
private textoStore = "second";
private groupedProvider = new GroupedItemProvider([], [], true);
private filterItems = [
getKeywordFilterItem(this.filterStore),
{ name: "Status", id: "status", items: statusItems, filterItemKey: "status" },
{
name: "Group Items",
id: "groupItems",
items: this.groupedProvider,
filterItemKey: "groupItems"
}
];
constructor(props: {}) {
super(props);
this.provider = new AggregateItemProvider<IListBoxItem>();
this.groupedProvider.push(...groupedItems);
this.groupedProvider.pushGroups(...groups);
this.provider.push(statusItems);
this.provider.push(this.groupedProvider);
if(this.props.pdata==="first")
this.filterStore = new FilterStore(
{defaultState: { groupItems: { value: [this.props.pdata,this.textoStore] }}}
);
else
this.filterStore = new FilterStore(
{defaultState: { groupItems: { value: [this.textoStore,this.props.pdata,] }}}
);
this.filterStore.subscribe(this.onFilterChanged, FILTER_CHANGE_EVENT);
this.state = {
//currentState: ""
currentState: JSON.stringify(this.filterStore.getState(), null, 4)
};
}
public render():JSX.Element {
return (
<Page className="sample-hub flex-grow">
<Header title="Filter" />
<div className="page-content">
<Filter
filterStore={this.filterStore}
filterItems={this.filterItems}
items={this.provider}
/>
<div style={{ marginTop: "16px" }} className="monospaced-text">
<span>Current state:</span>
<span>{this.state.currentState}</span>
<span>{this.props.pdata}</span>
</div>
</div>
</Page>
);
}
private onFilterChanged = (changedState: IFilterState) => {
this.setState({
currentState: JSON.stringify(this.filterStore.getState(), null, 4)
});
this.onFilterChangedExtended(JSON.stringify(this.filterStore.getState(), null, 4))
};
private async onFilterChangedExtended(estadoActual: string) {
const workItemFormService = await SDK.getService<IWorkItemFormService>(
WorkItemTrackingServiceIds.WorkItemFormService
);
workItemFormService.setFieldValue(SDK.getConfiguration().witInputs.FieldName, estadoActual);
}
The first Caller with useState and useEffect inside a Functional component:
import { WorkItemComponent } from "./WorkItemComponent";
const WorkItemFilterAsync: React.FC = props => {
let respuestaAsync="";
const [data, setData] = React.useState<string>('');
React.useEffect(() => {
const fetchdata = async() =>{
const result = await fngetFieldName()
setData(result);
}
// Execute the created function directly
fetchdata();
}, []);
async function fngetFieldName(): Promise<string> {
const workItemFormService = await SDK.getService<IWorkItemFormService>(
WorkItemTrackingServiceIds.WorkItemFormService
);
const respuesta = workItemFormService.getFieldValue(SDK.getConfiguration().witInputs.FieldName);
respuestaAsync = (await respuesta).toString();
return JSON.stringify(respuesta);
}
return <WorkItemComponent pdata={data}/>
}
export default WorkItemFilterAsync;
And the second caller with componentDidMount on a class:
import {
IWorkItemChangedArgs,
IWorkItemFieldChangedArgs,
IWorkItemFormService,
IWorkItemLoadedArgs,
WorkItemTrackingServiceIds,
} from "azure-devops-extension-api/WorkItemTracking";
import * as React from "react";
import { showRootComponent } from "../../Common";
import * as SDK from "azure-devops-extension-sdk";
import { WorkItemComponent } from "./WorkItemComponent";
//const WorkItemComponent = React.lazy (() => import('./WorkItemComponent'));
class WorkItemFilter extends React.Component{
state = {
externalData: false,
};
public componentDidMount() {
this.onLoadExtended();
}
render():JSX.Element {
return(
<div className="page-content">
{this.state.externalData ? <WorkItemComponent pdata="first"/> : <WorkItemComponent pdata="third"/>}
</div>
);
}
private async onLoadExtended() {
const workItemFormService = await SDK.getService<IWorkItemFormService>(
WorkItemTrackingServiceIds.WorkItemFormService
);
let varaux = workItemFormService.getFieldValue(SDK.getConfiguration().witInputs.FieldName);
if ((await varaux).toString()!=="")
{
this.setState({
externalData: true,
});
}
}
}
showRootComponent(<WorkItemFilter />);
This is the parent component:
export function showRootComponent(component: React.ReactElement<any>) {
ReactDOM.render(component, document.getElementById("root"));
}
Configuration for the Azure Dev Ops Extension (azure-devops-extension.json):
{
"contributions": [
{
"id": "work-item-filter",
"type": "ms.vss-work-web.work-item-form-control",
"description": "Custom Filter",
"targets": [
"ms.vss-work-web.work-item-form"
],
"properties": {
"name": "BHD Filter",
"uri": "dist/WorkItemFilter/WorkItemFilter.html",
"height": 600,
"inputs": [
{
"id":"FieldName",
"name": "Select the field for this control.",
"type": "WorkItemField",
"properties": {
"workItemFieldTypes": ["String", "PlainText", "HTML"]
},
"validation": {
"dataType": "String",
"isRequired": true
}
I also tried putting the ReactDOM.render inside a browser event, but I cannot do that because the UI extension needs a field to save the data:
Writing a functional react component is simple with the new React Hooks. In the example below, I'm using useState and useEffect. The useState hook is synonymous with this.state/this.setState in a class-based React component. The useEffect hook is similar to componentDidMount+componentDidUpdate. It also is capable of being componentDidUnmount.
The way the code will execute is from top to bottom. Because it's functional, it will run through once and render with the default state set at the argument to useState. It will not block on getting data from the API in the useEffect function. Thus, you need to be able to handle loading without having data. Anytime props.apiConfig or props.id changes, the component will re-render and all the useEffect again. It will only call useEffect if props.apiConfig and props.id do change after first run. The only nasty part is that useEffect cannot be an async function, so you have to call the function getDataWrapper without using await. When the data is received by the API, it will store the data in state, which will trigger a re-render of the component.
To Summarize:
Render once with default state
Call useEffect, which calls getDataWrapper
return component with initial values in useState
Once data is received by the API in the useEffect/getDataWrapper function, set the state via setState & set isLoading to false
Re-render the component with updated value that setState now contains
Avoid the useEffect control path since the values in the second argument of useEffect have not changed. (eg: props.apiConfig & props.id).
import React, { useState, useEffect } from 'react';
import { getDataFromAPI } from './api';
const MyComponent = (props) => {
const [state, useState] = useState({
isLoading: true,
data: {}
});
useEffect(() => {
const getDataWrapper = async () => {
const response = await getDataFromAPI(apiConfig, props.id);
setState({
isLoading: false,
data: response
});
});
getDataWrapper();
}, [props.apiConfig, props.id]);
if(state.isLoading) { return <div>Data is loading from API...</div>
return (
<div>
<h1>Hello, World!</h1>
<pre>{JSON.stringify(state.data, null, 2)}</pre>
</div>
);
};
export default MyComponent;

ReactJS + Material Design: How to get theme colors outside component?

In my ReactJS app.js I defined a theme:
const theme = createMuiTheme({
palette: {
primary: {
main: "#54BD40",
},
},
});
I am using the React wrapper for Chart.js and I want to set-up a chart graph. But I am not sure how I can use the primary/main color from my theme:
import React, { Component } from 'react';
import { Line } from 'react-chartjs-2';
let data = []; //data here
const MONTHS = ['Jan', 'Feb', 'Mrt', ...];
const WEEK = ['Sun', 'Mon', 'Tue', ...];
let chart = {
labels: MONTHS,
datasets:[{
borderColor: ['#XXX'], //THEME COLOR HERE
}],
};
class LineChart extends Component{
constructor(props){
super(props);
this.state = {
chartData: chart,
usage: false,
generation: true,
}
}
render(){
return (
<div>
<Line data={ this.state.chartData } options={} />
</div>
)
}
}
export default LineChart;
For using theme palettes inside render(), I know I can import withTheme() and use this.props.theme. But how this work now outside the component? (I just started using ReactJS)
Well as I understand you can import constant into your main js file after doing
export in app.js. Example. -
export const theme = createMuiTheme({ palette: { primary: { main: "#54BD40", }, }, });
Then import
Import { theme } from "app"
And use it anywhere in your main js file.

Categories

Resources