This question already has answers here:
Get a CSS value from external style sheet with Javascript/jQuery
(5 answers)
Closed 1 year ago.
I have a state variable initialized like this:
const App = () => {
const [parameters, setParameters] = useState({
"shape": "circle",
"fontFamily": "",
"size": 500
});
//...
// which is here passed to a component
return(
<MyCanvas props={parameters} />
);
}
The parameters array is used by another component that renders something on the canvas. I want that the parameters is on first load of the app, at runtime, to be what's currently defined in CSS, so I want to read that CSS property and init, in this case, the "fontFamily" field with what's written in CSS. Therefore want to read the #font-face {font-family: MyLocalFont;} property from the CSS definition.
So generally: How would I read a value during runtime from a CSS for initializing the useState variable?
A compile-time solution would not meet my needs. Changes to the CSS styles can be made without re-deploying my widget, therefore I need to read the actual CSS property values during runtime upon initialization.
import React, { useCallback } from 'react';
const defaultParameters = {
"shape": "circle",
"fontFamily": "",
"size": 500,
}
const App = () => {
const [parameters, setParameters] = useState(null);
const appRoot = useCallback(
(element) => {
const style = window.getComputedStyle(element);
const fontFamily = style.getPropertyValue('font-family');
setParameters({...defaultParameters, fontFamily });
}, []
);
//...
return (
<div ref={appRoot}>
{ parameters && <MyCanvas props={parameters} /> }
</div>
);
}
You can use the 'classNames' package.
https://www.npmjs.com/package/classnames
npm install --save classnames
import classNames from 'classnames';
const Testers = () => {
const [parameters, setParameters] = React.useState({
shape: 'circle',
fontFamily: '',
size: 500,
});
//I think you can change the font name when you need it.
React.useEffect(() => {
setParameters({ ...parameters, fontFamily: 'NotoSans' });
}, []);
return (
<div className={classNames(parameters></div>
....
Also, it would be better to designate a separate font as below method.
const [fonts, setFonts] = React.useState('fontName')
const [parameters, setParameters] = React.useState({
shape: 'circle',
size: 500,
});
React.useEffect(() => {
setFonts('NotoSans');
}, []);
<div className="header">
<div className={classNames(parameters, {
fontFamily : fonts
})}></div>
Related
I'm trying to override the default theme in material ui v5, at first i used this :
styleOverrides:{
root: ({ theme }) => ({
margin : theme.spacing(2)
}),
},
But then when reading the docs i saw they used this :
const lightTheme = createTheme({
components: {
MuiPaper: {
styleOverrides:{
root: ({ theme : {spacing} }) => ({
margin : spacing(2)
}),
},
},
},
});
Here i think they destructured the the spacing function from the theme object, what i dont understand is the syntax, why not do this :
root: ({ {spacing} }) => ({
margin : spacing(2)
}),
Where {spacing} is like taking out the spacing() from the theme object, no ?
The ":" in { theme : {spacing} } is what confuses me, i'm not familiar with that syntax and i dont want to make assumptions on what it precisely does, i read these 2 resources
es6 in depth destructuring
Destructuring assignment - MDN
But i still couldn't find the anwer, if someone could explain it i'd be grateful.
{ {spacing} } won't do the trick. { theme: {spacing} } destructures two objects. First the object that is passed to the arrow function, then another object that is stored in the .theme property. In only variable that's being created here is spacing, which is .theme.spacing. Take this example:
const obj = {theme: {spacing: 1, color: 'blue'}};
const fn1 = ({theme: {spacing}}) => console.log(spacing);
fn1(obj);
The parameter likely looks like { theme: { spacing: ... } }, therefore to access it you need to define the full path (theme and spacing). If you only use ({ {spacing} }) it won't know that you want to access the spacing within the theme property. For instance in the following example ({ {spacing} }) would be ambiguous, potentially meaning two different functions:
const params = {
theme: {
spacing: (n) => `some css: ${n}`
},
otherProps: {
spacing: (n) => `other prop: ${n}`
},
}
const v1 = (params) => params.theme.spacing(2)
const v2 = ({ theme }) => theme.spacing(2)
const v3 = ({ theme: { spacing } }) => spacing(2)
console.log(v1(params))
console.log(v2(params))
console.log(v3(params))
After implementing the drag and drop feature on AG Grid table, I'm looking for a way to get the current state with the updated order/index of rows. My goal is to persist the table data after changing the order, but can't find the respective state of the current order.
I'd appreciate any help or any idea.
Sandbox demo and example code below
import React from "react";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
function App() {
const [gridApi, setGridApi] = React.useState(null);
const [gridColumnApi, setGridColumnApi] = React.useState(null);
const onGridReady = (params) => {
setGridApi(params.api);
setGridColumnApi(params.columnApi);
};
const defaultColDef = {
flex: 1,
editable: true
};
const columnDefs = [
{
headerName: "Name",
field: "name",
rowDrag: true
},
{ headerName: "stop", field: "stop" },
{
headerName: "duration",
field: "duration"
}
];
const rowData = React.useMemo(
() => [
{
name: "John",
stop: 10,
duration: 5
},
{
name: "David",
stop: 15,
duration: 8
},
{
name: "Dan",
stop: 20,
duration: 6
}
],
[]
);
return (
<div>
<h1 align="center">React-App</h1>
<div>
<div className="ag-theme-alpine" style={{ height: "700px" }}>
<AgGridReact
columnDefs={columnDefs}
rowData={rowData}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
rowDragManaged={true}
></AgGridReact>
</div>
</div>
</div>
);
}
export default App;
You can get the order of the rows inside the grid by iterating over them using the Grid API method forEachNode:
API for Row Nodes
const rows = [];
gridApi.forEachNodeAfterFilterAndSort((node) => rows.push(node.data));
console.log(rows);
See this implemented in the following sample.
You're currently using managed dragging by passing rowManagedDragging={true}, which means the AgGridReact component is managing the row order state.
If you want to maintain row order state outside the component, you need to use Unmanaged Dragging.
Add a handler for onRowDragMove, and use the node and overIndex or overNode properties of the event to update your local event order state, and pass it to the AgGridReact component to re-render.
Take a look at this example from the docs
I don't speak English very well. Please be understanding!
First, please check my code!
export default function DriveFolder() {
const [clickFolderPk, setClickFolderPk] = useState(1);
const viewFolder = async () => {
const url = `/api/store/drive/view-folder?folderId=${clickFolderPk}`;
await get(url)
.then((res) => {
console.log(res);
setMainFolder(res.directChildrenFolders);
})
.catch((error) => {
console.log(error);
});
};
useEffect(() => {
viewFolder();
}, [clickFolderPk]);
return (
<div className={classes.driveFolder}>
{mainFolder.map((main, key) => (
<TreeView>
<TreeItem
onClick={() => setClickFolderPk(main.FOLDER_PK)}>
<TreeItem nodeId='10' label='OSS' />
<TreeItem nodeId='6' label='Material-UI'>
<TreeItem nodeId='7' label='src'>
<TreeItem nodeId='8' label='index.js' />
<TreeItem nodeId='9' label='tree-view.js' />
</TreeItem>
</TreeItem>
</TreeItem>
</TreeView>
))}
</div>
);
}
I edited some code to make it clear. (might misspelled)
With this code, on the first rendering, since 'clickFolderPk' value is 1, I get the right data from DB.
However, since I have subfolders within folders from 'clickFolderPk' value 1, I have to request another GET REQUEST to see my subfolders from root folders.
Here is the simple image that you can understand my situation better.
this is what I get from 'clickFolderPk' value 1.
However, when I press 'kikiki', GET request functions and render like this.
.
This is not the way I want to render things.
I want every data from DB, however they don't disappear whenever I use different GET request with different PK number.
I want them stay on the screen and get the subfolders within them.
I'm struggling with this issue for quite a time.
Your help will be really appreciated!!!!!
It's all about Nesting: Folders have sub-folders, etc and it goes on...
Note: To break things down, I will answer from a React point of view disregarding how your backend api is structured or returns data.
Basically there are two main approaches,
Approach #1:
The global state is a single source of truth for all the folders think of it like this:
const [allFolders, setAllFolders] = useState([
{
id: "1",
name: "a-1",
folders: [
{
name: "a-subfolder-1",
folders: [{ name: "a-subfolder-subfolder-1" }],
},
{ name: "subfolder-2" },
],
},
]);
The problem is that any small update requires to mutate the entire state. So I will focus more on Approach #2
Approach #2:
There is the main tree that has child components, child components can expand and have children too:
import { useEffect, useState } from "react";
import "./styles.css";
export default function DriveFolder() {
const [folders, setFolders] = useState([
{ id: "1", name: "folder-a" },
{ id: "2", name: "folder-b" },
{ id: "3", name: "folder-c" }
]);
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{folders.map((folder) => {
return <Folder key={folder.id} folder={folder} />;
})}
</div>
);
}
const Folder = ({ parent = undefined, folder }) => {
const [subfolders, setSubfolders] = useState([]);
const [isOpened, setOpened] = useState(false);
const hasSubfolders = subfolders.length > 0;
useEffect(() => {
// send request to your backend to fetch sub-folders
// --------------- to ease stuff I will hard code it
// with this you can limit the example of nest you wish
const maxNestsCount = 5;
const subfolderParent = parent || folder;
const subfolder = {
id: subfolderParent.id + "-sub",
name: "subfolder-of-" + subfolderParent.name
};
const currentNestCount = subfolder.name.split("-sub").length;
setSubfolders(currentNestCount < maxNestsCount ? [subfolder] : []);
// -----------------------------
}, []);
const handleToggleShowSubFolders = (e) => {
e.stopPropagation();
if (!hasSubfolders) {
return;
}
setOpened(!isOpened);
};
return (
<div
style={{
display: "flex",
flexDirection: "column",
paddingHorizontal: 5,
marginTop: 10,
marginLeft: parent ? 20 : 0,
backgroundColor: "#1678F230",
cursor: hasSubfolders ? "pointer" : undefined
}}
onClick={handleToggleShowSubFolders}
>
{folder.name}
<div style={{ display: isOpened ? "block" : "none" }}>
{subfolders.map((subfolder) => (
<Folder key={subfolder.id} parent={folder} folder={subfolder} />
))}
</div>
</div>
);
};
Try it out:
Here is the output of the sample code above:
I need to change the value I show in my custom status bar when a prop changes.
So let's say I got:
const CustomGrid = ({
data, lastUpdate, framweworkComponents, columns
}) => {
const [gridApi, setGridApi] = useState(null);
const [gridColumnApi, setGridColumnApi] = useState(null);
const [gridParams, setGridParams] = useState(null);
const onGridReady = (params) => {
setGridApi(params.api);
setGridColumnApi(params.columnApi);
setGridParams(params);
}
};
const renderLastUpdateStatusBar = () => (
<div>
{lastUpdate}
</div>
);
return(
<div className={theme || 'ag-theme-balham'} style={style}>
<AgGridReact
modules={AllModules}
onGridReady={onGridReady}
columnDefs={columns}
rowData={data}
defaultColDef={defaultColDef || {
resizable: true,
menuTabs: [],
}}
frameworkComponents={{
...frameworkComponents,
lastUpdateStatusBarComponent: renderLastUpdateStatusBar,
}}
statusBar={{
statusPanels: [
{
statusPanel: 'lastUpdateStatusBarComponent',
key: 'lastUpdateStatusBarKey',
align: 'left',
},
],
}
/>
</div>
)
}
When 'lastUpdate' changes, it is correctly passed to CustomGrid component but the status bar doesn't get re-rendered, so I see the first value forever.
My goal is to make the status bar updating every time 'lastUpdate' changes. I tried making a state containing my framework components and set it in a useEffect when lastUpdate changes, then put this state in frameworkComponents prop in agGrid, but it does not work:
const [frameworkC, setFrameworkC] = useState(null);
useEffect(() => {
setFrameworkC({
...frameworkComponents,
lastUpdateStatusBarComponent: renderLastUpdateStatusBar,
})
}, [lastUpdate]);
...
<AgGridReact
...
frameworkComponents={frameworkC}
...
>
I also tried setting this state in onGridReady function, same result.
Also I tried calling api.redrawRows(gridParams) in the useState, no way.
Is there some API I can use to update the status bar component? Or some any approach?
You need to add key for the component to rerender
<AgGridReact
key={lastUpdate}
modules={AllModules}
onGridReady={onGridReady}
columnDefs={columns}
rowData={data}
defaultColDef={defaultColDef || {
resizable: true,
menuTabs: [],
}}
frameworkComponents={{
...frameworkComponents,
lastUpdateStatusBarComponent: renderLastUpdateStatusBar,
}}
statusBar={{
statusPanels: [
{
statusPanel: 'lastUpdateStatusBarComponent',
key: 'lastUpdateStatusBarKey',
align: 'left',
},
],
}
/>
So I have this JSX element that I am attempting to render in the Class component. It is essentially a visual provided by D3's React library. However, I am receiving this error upon attempting to render the D3 visual:
Unhandled Rejection (Error): Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Below are some relevant code snippets of where the error is occurring:
The builder function to pass all the necessary props to the D3 library
const buildModelVisual = (dataPoints) => {
console.log("category: " + dataPoints[0].category)
console.log("range: " + dataPoints[0].range)
console.log("frequency: " + dataPoints[0].frequency)
dataPoints[0].frequency.forEach(f =>
console.log("f: " + f)
)
const width = 960,
height = 500,
margins = {top: 50, right: 50, bottom: 50, left: 50},
id = "model-visual",
title = "NaiveBayes Model Visual",
svgClassName = "model-visual-class",
titleClassName = "model-visual-title-class",
legendClassName = "model-legend",
showLegend = true,
showXAxis = true,
showYAxis = true,
showXGrid = false,
showYGrid = false,
ranges = [
...dataPoints[0].range
],
frequencies = [
...dataPoints[0].frequency
],
x = () => {
return ranges.forEach(r => {
return r;
})
},
xOrient = 'bottom',
xTickOrient = 'bottom'
const xDomain = dataPoints[0].range.forEach(r => {
return {
category: dataPoints[0].category, range: r
}
}),
xRangeRoundBands = {interval: [0, width - margins.left - margins.right], padding: 0.1},
xScale = 'ordinal',
xAxisClassName = 'x-axis',
xLabel = dataPoints[0].category,
xLabelPosition = 'bottom',
xTickPadding = 3,
xInnerTickSize = 6,
xOuterTickSize = 6,
y = () => {
return frequencies.forEach(freqInRange => {
return freqInRange.forEach(f => {
return f;
});
})
},
yOrient = 'left',
yTickOrient = 'left',
yRange = [height - margins.top - margins.bottom, 0]
const yDomain = [0, d3.max(
dataPoints[0].frequency,
(f) => {
return f.value
}
)],
yScale = 'linear',
yAxisClassName = 'y-axis',
yLabel = "Population",
yTickFormat = d3.format(".2s"),
yLabelPosition = 'left',
yTickPadding = 4,
yInnerTickSize = 6,
yOuterTickSize = 6
return (
<Chart
title={title}
id={id}
width={width}
height={height}
>
<BarStackChart
title= {title}
data= {ranges}
width= {width}
height= {height}
id= {id}
margins= {margins}
svgClassName= {svgClassName}
titleClassName= {titleClassName}
yAxisClassName= {yAxisClassName}
xAxisClassName= {xAxisClassName}
legendClassName= {legendClassName}
categoricalColors= {d3.scaleOrdinal(d3.schemeCategory10)}
chartSeries = {ranges}
showLegend= {showLegend}
showXAxis= {showXAxis}
showYAxis= {showYAxis}
x= {x}
showXGrid= {showXGrid}
xDomain= {xDomain}
xRangeRoundBands= {xRangeRoundBands}
xScale= {xScale}
xOrient= {xOrient}
xTickOrient= {xTickOrient}
xTickPadding = {xTickPadding}
xInnerTickSize = {xInnerTickSize}
xOuterTickSize = {xOuterTickSize}
xLabel = {xLabel}
xLabelPosition = {xLabelPosition}
y= {y}
showYGrid= {showYGrid}
yOrient= {yOrient}
yRange= {yRange}
yDomain= {yDomain}
yScale= {yScale}
yTickOrient= {yTickOrient}
yTickPadding = {yTickPadding}
yInnerTickSize = {yInnerTickSize}
yOuterTickSize = {yOuterTickSize}
yTickFormat= {yTickFormat}
yLabel = {yLabel}
yLabelPosition = {yLabelPosition}
/>
</Chart>
)
}
The HO class component that is rendering the graph and the interface
constructor(props) {
super(props);
this.ref = React.createRef();
this.state = {
input: "",
output: [],
visual: null
}
}
REST API call (within the default class Component) that sets the data for the BarStackChart
callGetModel = () => {
let getModelRanges = getCall(ApiURL.get_model_ranges);
let getModelFrequencies = getCall(ApiURL.get_model_frequencies);
Promise.all([getModelRanges, getModelFrequencies]).then(data => {
this.setState({output: data})
const dataPoints = [];
for (let value in JSON.parse(data[0].toString())) {
dataPoints.push({
category: value,
range: JSON.parse(data[0].toString())[value],
frequency: JSON.parse(data[1].toString())[value]
})
}
console.log(dataPoints)
const ModelVisual = buildModelVisual(dataPoints)
this.setState({ visual: ModelVisual }) // returns JSX element
console.log(this.state.visual)
});
}
The render method for the class Component
render() {
return <div>
<h3>Welcome to Naive Bayes Java!</h3>
<p>The REST API for this project is hosted at</p>
<a style={{'display':'block', 'paddingBottom':'1.5em', 'color':'rgb(0, 150, 196)'}} href="https://naivebayesjava.herokuapp.com/swagger-ui.html#/">https://naivebayesjava.herokuapp.com/</a>
<button style={{'display':'inline', 'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} onClick={this.callListOfFiles}>
Get List of Valid Files
</button>
<button style={{'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} onClick={this.callGetModel}>
Get Model
</button>
<button style={{'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} onClick={this.callGetModelAccuracy}>
Get Model Accuracy
</button>
<div style={{'margin':'auto', 'display':'block'}}>
<input style={{'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} type='text' value={this.state.input} onChange={this.handleChange}/>
<button style={{'background':'rgb(32, 32, 32)', 'color':'rgb(190, 190, 190)'}} onClick={this.callSetData}>
Set Training Data File
</button>
</div>
{/* <button onClick={this.callGetTrainingData}>
Get Training Data Arff Files
</button> */}
<div style={{'padding-top':'0.5em'}} ref={this.ref}></div>
<output type='textBox' style={{ 'padding':'1.25em', 'display':'block', 'Height': '30px', 'Width': '300px' }}>
{ Object.keys(this.state.output).map(key => {
return this.state.output[key]
}) }
</output>
{ this.state.visual }
</div>
}
There most definitely is a better way to implement this besides setting a "this.state.visual" JSX Element and calling that in the render method, although as I am really new to both React (started learning about a month ago) and JS (started about 3 months ago) I don't quite know all the common practices with either; just the general theory behind how they work.
This interface and my portfolio are hosted at joshuabaroni.github.io . The interface i am attempting to improve is the NaiveBayes Project Interface under the "Projects" section
Any recommendations would be appreciated! the whole JS file is available upon request.
You likely forgot to export your component from the file it's defined
in, or you might have mixed up default and named imports.
You aren't exporting your classes/functions as it is required.
Exporting without default means it's a "named export". You can have multiple named exports in a single file. So if you do this,
class Template {}
class AnotherTemplate {}
export { Template, AnotherTemplate } // named export
then you have to import these exports using their exact names. So to use these components in another file you'd have to do,
import {Template, AnotherTemplate} from './components/templates'
Alternatively if you export as the default export like this,
export default class Template {}
Then in another file you import the default export without using the {}, like this,
import Template from './components/templates'
There can only be one default export per file. In React it's a convention to export one component from a file, and to export it is as the default export.
You're free to rename the default export as you import it,
import TheTemplate from './components/templates'
And you can import default and named exports at the same time,
import Template,{AnotherTemplate} from './components/templates'