I am using react-admin framework (3.2) and I am struggling with following error:
ReferenceError: Cannot access 'GameScheduleField' before initialization
This is how I import the GameScheduleField component to my resource:
import { GameScheduleField, GameScheduleInput } from '../components/GameScheduleComponents';
I also import atleast eight other components without any issue. The GameScheduleField is a class and it looks like this:
export class GameScheduleField extends React.Component
{
constructor(props, context)
{
super(props, context);
};
static defaultProps =
{
addLabel: false,
}
rowStyle = (record, index, defaultStyle = {}) =>
{
const style = color => ({
...defaultStyle,
borderLeftWidth: 5,
borderLeftStyle: "solid",
borderLeftColor: color,
});
switch (record.type)
{
case "q": return style("yellow");
case "v": return style("cyan");
case "adv": return style("magenta");
case "fin": return style("green");
default: return style("gray");
}
}
render()
{
const { source, record, ...rest } = this.props;
const seq = get(record, source, []).map((v, i) => ({ order: i + 1, ...v }));
return <ArrayField record={{ data: seq }} source="data" {...rest}>
<Datagrid rowClick="expand" rowStyle={this.rowStyle} expand={<GameStepField />} setSort={() => {}}>
<TextField source="order" label="hf.order" />
<SelectField source="type" choices={GameStepEnum} />
</Datagrid>
</ArrayField>
}
}
Any suggestion why am I getting this error linked to this specific component?
Thank you in advance.
This happens when u have circular dependency. for example. follow below given comment.
You have a circular dependency issue:
http/private : import store from "app" components/headerNav: import
apiPrivate from "http/private"; and then app imports your component
tree So, because of that, http/private is going to get loaded first
and try to grab the store from app, except app hasn't actually been
loaded yet because it's waiting on the component tree to be imported.
Source:
https://github.com/reduxjs/redux-toolkit/issues/167
Related
I was integrating gojs with react and was successfully able to integrated the nodes array and links array and was able to see the nodes in my page.
But the toughest thing , integrated ReactOverview (minimap) but i can see only small rectangle box on the page with no diagram in it.
Will share my code here
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { ReactDiagram, ReactOverview } from 'gojs-react';
import * as go from 'gojs';
class DiagramContainer extends React.Component {
diagramRef;
static propTypes = {
diagramData: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.diagramRef = React.createRef();
this.state = {};
}
initDiagram = () => {
const $ = go.GraphObject.make;
const diagram = $(go.Diagram, {
'undoManager.isEnabled': true,
'animationManager.isEnabled': false,
// 'undoManager.maxHistoryLength': 0,
model: $(go.GraphLinksModel, {
linkKeyProperty: 'key',
linkFromPortIdProperty: 'fromPort',
linkToPortIdProperty: 'toPort',
}),
});
const defaulttemplate = $(
go.Node,
'Vertical',
$(
go.Panel,
'Auto',
$(
go.Shape,
'hexagon',
{
width: 160,
height: 160,
},
),
$(
go.TextBlock,
}
)
);
var templateMap = new go.Map();
templateMap.add('normal', defaulttemplate);
//dummy codes
},
})
);
return diagram;
};
initDiagramOverview = () => {
const $ = go.GraphObject.make;
const overview = $(go.Overview, { contentAlignment: go.Spot.Center });
return overview;
};
render() {
const { diagramData } = this.props;
return (
<>
<div style={{ height: '100%' }}>
<ReactDiagram
ref={this.diagramRef}
divClassName='diagram-main'
id='diagram'
initDiagram={this.initDiagram}
flowDiagramData={diagramData}
nodeDataArray={diagramData.dataArray}
linkDataArray={diagramData.linksArray}
skipsDiagramUpdate={diagramData.skipsDiagramUpdate}
modelData={}
/>
</div>
<ReactOverview
initOverview={this.initDiagramOverview}
divClassName='diagram-observed'
observedDiagram={this.diagramRef.current?.getDiagram() || null}
/>
</>
);
}
}
export default DiagramContainer
```
But am not seeing mini map , i can just see a rectangle box instead of minimap. Still i cross checked with various things and am not sure what went wrong
Can you guys help me to resolve this issue
The problem is that your component only renders once for the given props (any interactive diagram changes are handled internally by GoJS and React is oblivious to that). When it renders the first and only time, this.diagramRef.current is still null. You can see this if you log it in the render method.
You need to use state to keep the "observed diagram" for the overview. Then when the overview component initializes, the diagram should be all set up and you can set the new state to cause re-render:
Add the state with the value of the diagram you want to observe in the overview:
constructor(props) {
super(props);
this.state = {
observedDiagram: null
};
this.diagramRef = React.createRef();
}
Use this state in the ReactOverview component:
<ReactOverview
initOverview={this.initDiagramOverview}
divClassName="diagram-overview-component"
observedDiagram={this.state.observedDiagram}
/>
Set the state when initializing the overview component:
initDiagramOverview = () => {
this.setState({
observedDiagram: this.diagramRef.current?.getDiagram() || null
});
const $ = go.GraphObject.make;
const overview = $(go.Overview, { contentAlignment: go.Spot.Center });
return overview;
};
You can see how it works in this sandbox
I try to render a visx wourdcloud with the data that I get from my server. My component for showing the website is the following:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { withRouter, RouteComponentProps, useParams } from 'react-router';
import WordCloud from '#/components/WordCloud';
import { getAggregations } from '#/selectors';
export interface WordData {
text: string;
value: number;
}
const AggregationShowComponent: React.FC<RouteComponentProps> = ({ history }) => {
const dispatch = useDispatch();
const { id }: { id: string } = useParams();
const { loading, aggregations } = useSelector(getAggregations);
const aggregation = aggregations.find(a => a._id == id);
const words = [
{
text: 'a',
value: 228
},
{
text: 'b',
value: 42
},
];
if (loading) {
return (
<div>Loading</div>
)
}
return (
<div>
{aggregation._id}
<WordCloud
words={aggregation.data}
// words={words}
width={1024}
height={600}
spiral="archimedean"
rotate="0"
/>
<p>
{aggregation.name}
</p>
</div>
)
}
export const AggregationShow = withRouter(AggregationShowComponent);
The component responsible for rendering the wordcloud is the following:
import React, { useState, useEffect } from 'react';
import { Text } from '#visx/text';
import { scaleLog } from '#visx/scale';
import { Wordcloud } from '#visx/wordcloud';
type SpiralType = 'archimedean' | 'rectangular';
export interface WordData {
text: string;
value: number;
}
interface WordCloudProps {
width: number;
height: number;
words: WordData[],
rotate: number,
spiral: SpiralType,
colors: String[],
}
const colors = ["#000000", "#aaaaaa", '#bbbbbb'];
const fixedValueGenerator = () => 0.5;
export default function WordCloud({ words, width, height, rotate = 0, spiral = 'archimedean' }: WordCloudProps) {
const fontScale = scaleLog({
domain: [Math.min(...words.map((w) => w.value)), Math.max(...words.map((w) => w.value))],
range: [10, 100],
});
const fontSizeSetter = (datum: WordData) => fontScale(datum.value);
return (
<>
<Wordcloud
words={words}
width={width}
height={height}
fontSize={fontSizeSetter}
font={'Impact'}
padding={2}
spiral={spiral}
rotate={rotate}
random={fixedValueGenerator}
>
{(cloudWords) =>
cloudWords.map((w, i) => (
<Text
key={w.text}
fill={colors[i % colors.length]}
textAnchor={'middle'}
transform={`translate(${w.x}, ${w.y}) rotate(${w.rotate})`}
fontSize={w.size}
fontFamily={w.font}
>
{w.text}
</Text>
))
}
</Wordcloud>
</>
);
}
If I try to use the data from the request (found in aggregation.data) I get the following error in d3.js:
When I use simple static data like in the commented out line in the first code block it works without any problems. The whole data fetching and displaying only when the data in present works also flawlessly only when i try to use the data from the http request in the wordcloud I get the error.
I also tried to clone the data that is passed into the wordcloud component to make sure that some of
the redux saga effects on the state are not causing the error but the error remains the same.
Additionally I also tried to reset the data with useEffect etc. but still no success.
What part am I missing? Is there some part of react/d3 that causes this issue that I'm not aware of? Is there a way to circumvent this problem?
Thanks
I found the solution. The d3-cloud modifies the words array and that collides with the imutability of the redux store data. I simply created a deep copy of the array:
.data.map(w = {...w})
Not sure if any other parts of the libraries should prevent the editing of the data but this works for me!
Goal: Implement dark mode in react native application.
A little introduction to the system:
File structure:
Profile.ts
ProfileCss.ts
constants.ts
In my application in order to separate styles from components, I moved them into .ts files and exported as modules.
Example of StyleCss.ts:
import {StyleSheet} from 'react-native';
import {Window, FixedCSS, hsize, wsize} from '../../entities/constants';
export default StyleSheet.create({
loading: {
backgroundColor: FixedCSS.Colors.MainBackgroundColor,
}
...
});
All style-related constant values stored in constants.ts such as Colors, LineHeights, and etc. Also constants.ts contains a class called FixedCSS which has static properties:
export class FixedCSS {
static isDark = true;
static Colors = {
// Main Colors
RootColor: FixedCSS.isDark ? '#FF6666' : '#FF6666',
MainBackgroundColor: FixedCSS.isDark ? '#050505' : '#FFFFFF',
...
}
...
}
isDark property of FixedCSS is supposed to be changed with the help of Switch component in Profile.ts
<Switch
value={darkMode}
onValueChange={value => {
this.setState({darkMode: value});
}}
/>
The problem: How can I change isDark property in FixedCSS class so that it will affect the appearance of components. The most problematic aspect is that StyleCSS.ts files do not reflect to the change of isDark property in FixedCSS class, as they are exported only once when js bundle gets created.
Changing a static property of a class is not going to trigger re-renders the way that it's supposed to. This needs to make use of state somehow. I recommend a theme Context. You could use one context or have separate contexts for controlling isDarkMode and providing the CSS, which is what I am doing here.
In order to export a value only once but have that value stay current, the exported value can be a function rather than a constant. I am making the fixed CSS be a function of isDark and the individual component styles be a function of FixedCSS.
constants.ts
export const createFixedCSS = (isDark: boolean) => ({
isDark,
Colors: {
// Main Colors
RootColor: isDark ? "#FF6666" : "#FF6666",
MainBackgroundColor: isDark ? "#050505" : "#FFFFFF"
...
}
...
});
export type FixedCSS = ReturnType<typeof createFixedCSS>;
export const IS_DARK_DEFAULT = false;
cssContext.ts
// context to provide the FixedCSS object, which also includes isDark
const FixedCSSContext = createContext(createFixedCSS(IS_DARK_DEFAULT));
// context to provide a function for switching modes
// type Dispatch<SetStateAction<T>> allows for setting based on previous value
type SetModeContextValue = Dispatch<SetStateAction<boolean>>;
// needs an initial value that fits the type signature
const SetModeContext = createContext<SetModeContextValue>(() => {
throw new Error(
"cannot set `isDark` outside of a `DarkModeContext` Provider"
);
});
// combine both contexts into one Provider component
export const CSSProvider: FC<{}> = ({ children }) => {
// store the isDark state
const [isDark, setIsDark] = useState(IS_DARK_DEFAULT);
// update the FixedCSS when isDark changes
// I'm not sure if memoizing is actually necessary here?
const FixedCSS = useMemo(() => createFixedCSS(isDark), [isDark]);
return (
<SetModeContext.Provider value={setIsDark}>
<FixedCSSContext.Provider value={FixedCSS}>
{children}
</FixedCSSContext.Provider>
</SetModeContext.Provider>
);
};
// I prefer to export the hooks rather than the contexts themselves
export const useFixedCSS = () => useContext(FixedCSSContext);
export const useSetMode = () => useContext(SetModeContext);
// helper hook for `StyleCss.ts` files
// takes the makeStyles factory function and turns it into a style object by accessing the FixedCSS context
export const useStyleFactory = <T extends StyleSheet.NamedStyles<T>>(
factory: (css: FixedCSS) => T
): T => {
const FixedCSS = useFixedCSS();
return useMemo(() => factory(FixedCSS), [FixedCSS, factory]);
};
ProfileCss.ts
export const makeStyles = (FixedCSS: FixedCSS) => StyleSheet.create({
loading: {
backgroundColor: FixedCSS.Colors.MainBackgroundColor,
}
});
Profile.ts
const DarkModeToggle = () => {
// access isDark from global FixedCSS
const {isDark} = useFixedCSS();
// access update callback
const setIsDark = useSetMode();
return (
<Switch
value={isDark}
onValueChange={setIsDark}
/>
)
}
const Profile = () => {
// pass the imported `makeStyles` to the hook and get stateful styles object
const styles = useStyleFactory(makeStyles);
return (
<View>
<DarkModeToggle/>
<View style={styles.loading}/>
</View>
);
};
CodeSandbox Demo (all in one file)
Prefacing this with a thought; I think I might require a recursive component but that's beyond my current ability with native js and React so I feel like I have Swiss cheese understanding of React at this point.
The problem:
I have an array of metafields containing metafield objects with the following structure:
{
metafields: [
{ 0:
{ namespace: "namespaceVal",
key: "keyVal",
val: [
0: "val1",
1: "val2",
2: "val3"
]
}
},
...
]
}
My code maps metafields into Cards and within each card lives a component <MetafieldInput metafields={metafields['value']} /> and within that component the value array gets mapped to input fields. Overall it looks like:
// App
render() {
const metafields = this.state.metafields;
return (
{metafields.map(metafield) => (
<MetafieldInputs metafields={metafield['value']} />
)}
)
}
//MetafieldInputs
this.state = { metafields: this.props.metafields}
render() {
const metafields = this.state;
return (
{metafields.map((meta, i) => (
<TextField
value={meta}
changeKey={meta}
onChange={(val) => {
this.setState(prevState => {
return { metafields: prevState.metafields.map((field, j) => {
if(j === i) { field = val; }
return field;
})};
});
}}
/>
))}
)
}
Up to this point everything displays correctly and I can change the inputs! However the change happens one at a time, as in I hit a key then I have to click back into the input to add another character. It seems like everything gets re-rendered which is why I have to click back into the input to make another change.
Am I able to use components in this way? It feels like I'm working my way into nesting components but everything I've read says not to nest components. Am I overcomplicating this issue? The only solution I have is to rip out the React portion and take it to pure javascript.
guidance would be much appreciated!
My suggestion is that to out source the onChange handler, and the code can be understood a little bit more easier.
Mainly React does not update state right after setState() is called, it does a batch job. Therefore it can happen that several setState calls are accessing one reference point. If you directly mutate the state, it can cause chaos as other state can use the updated state while doing the batch job.
Also, if you out source onChange handler in the App level, you can change MetafieldInputs into a functional component rather than a class-bases component. Functional based component costs less than class based component and can boost the performance.
Below are updated code, tested. I assume you use Material UI's TextField, but onChangeHandler should also work in your own component.
// Full App.js
import React, { Component } from 'react';
import MetafieldInputs from './MetafieldInputs';
class App extends Component {
state = {
metafields: [
{
metafield:
{
namespace: "namespaceVal",
key: "keyVal",
val: [
{ '0': "val1" },
{ '1': "val2" },
{ '2': "val3" }
]
}
},
]
}
// will never be triggered as from React point of view, the state never changes
componentDidUpdate() {
console.log('componentDidUpdate')
}
render() {
const metafields = this.state.metafields;
const metafieldsKeys = Object.keys(metafields);
const renderInputs = metafieldsKeys.map(key => {
const metafield = metafields[key];
return <MetafieldInputs metafields={metafield.metafield.val} key={metafield.metafield.key} />;
})
return (
<div>
{renderInputs}
</div>
)
}
}
export default App;
// full MetafieldInputs
import React, { Component } from 'react'
import TextField from '#material-ui/core/TextField';
class MetafieldInputs extends Component {
state = {
metafields: this.props.metafields
}
onChangeHandler = (e, index) => {
const value = e.target.value;
this.setState(prevState => {
const updateMetafields = [...prevState.metafields];
const updatedFields = { ...updateMetafields[index] }
updatedFields[index] = value
updateMetafields[index] = updatedFields;
return { metafields: updateMetafields }
})
}
render() {
const { metafields } = this.state;
// will always remain the same
console.log('this.props', this.props)
return (
<div>
{metafields.map((meta, i) => {
return (
<TextField
value={meta[i]}
changekey={meta}
onChange={(e) => this.onChangeHandler(e, i)}
// generally it is not a good idea to use index as a key.
key={i}
/>
)
}
)}
</div>
)
}
}
export default MetafieldInputs
Again, IF you out source the onChangeHandler to App class, MetafieldInputs can be a pure functional component, and all the state management can be done in the App class.
On the other hand, if you want to keep a pure and clean App class, you can also store metafields into MetafieldInputs class in case you might need some other logic in your application.
For instance, your application renders more components than the example does, and MetafieldInputs should not be rendered until something happened. If you fetch data from server end, it is better to fetch the data when it is needed rather than fetching all the data in the App component.
You need to do the onChange at the app level. You should just pass the onChange function into MetaFieldsInput and always use this.props.metafields when rendering
As per the docs:
When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a rerender will still happen starting at the component itself using useContext.
...
A component calling useContext will always re-render when the context value changes.
In my Gatsby JS project I define my Context as such:
Context.js
import React from "react"
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
set: () => {},
}
const Context = React.createContext(defaultContextValue)
class ContextProviderComponent extends React.Component {
constructor() {
super()
this.setData = this.setData.bind(this)
this.state = {
...defaultContextValue,
set: this.setData,
}
}
setData(newData) {
this.setState(state => ({
data: {
...state.data,
...newData,
},
}))
}
render() {
return <Context.Provider value={this.state}>{this.props.children}</Context.Provider>
}
}
export { Context as default, ContextProviderComponent }
In a layout.js file that wraps around several components I place the context provider:
Layout.js:
import React from 'react'
import { ContextProviderComponent } from '../../context'
const Layout = ({children}) => {
return(
<React.Fragment>
<ContextProviderComponent>
{children}
</ContextProviderComponent>
</React.Fragment>
)
}
And in the component that I wish to consume the context in:
import React, { useContext } from 'react'
import Context from '../../../context'
const Visuals = () => {
const filterByYear = 'year'
const filterByTheme = 'theme'
const value = useContext(Context)
const { filterBy, isOptionClicked, filterValue } = value.data
const data = <<returns some data from backend>>
const works = filterBy === filterByYear ?
data.nodes.filter(node => node.year === filterValue)
:
data.nodes.filter(node => node.category === filterValue)
return (
<Layout noFooter="true">
<Context.Consumer>
{({ data, set }) => (
<div onClick={() => set( { filterBy: 'theme' })}>
{ data.filterBy === filterByYear ? <h1>Year</h1> : <h1>Theme</h1> }
</div>
)
</Context.Consumer>
</Layout>
)
Context.Consumer works properly in that it successfully updates and reflects changes to the context. However as seen in the code, I would like to have access to updated context values in other parts of the component i.e outside the return function where Context.Consumer is used exclusively. I assumed using the useContext hook would help with this as my component would be re-rendered with new values from context every time the div is clicked - however this is not the case. Any help figuring out why this is would be appreciated.
TL;DR: <Context.Consumer> updates and reflects changes to the context from child component, useContext does not although the component needs it to.
UPDATE:
I have now figured out that useContext will read from the default context value passed to createContext and will essentially operate independently of Context.Provider. That is what is happening here, Context.Provider includes a method that modifies state whereas the default context value does not. My challenge now is figuring out a way to include a function in the default context value that can modify other properties of that value. As it stands:
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
set: () => {}
}
set is an empty function which is defined in the ContextProviderComponent (see above). How can I (if possible) define it directly in the context value so that:
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
test: 'hi',
set: (newData) => {
//directly modify defaultContextValue.data with newData
}
}
There is no need for you to use both <Context.Consumer> and the useContext hook.
By using the useContext hook you are getting access to the value stored in Context.
Regarding your specific example, a better way to consume the Context within your Visuals component would be as follows:
import React, { useContext } from "react";
import Context from "./context";
const Visuals = () => {
const filterByYear = "year";
const filterByTheme = "theme";
const { data, set } = useContext(Context);
const { filterBy, isOptionClicked, filterValue } = data;
const works =
filterBy === filterByYear
? "filter nodes by year"
: "filter nodes by theme";
return (
<div noFooter="true">
<div>
{data.filterBy === filterByYear ? <h1>Year</h1> : <h1>Theme</h1>}
the value for the 'works' variable is: {works}
<button onClick={() => set({ filterBy: "theme" })}>
Filter by theme
</button>
<button onClick={() => set({ filterBy: "year" })}>
Filter by year
</button>
</div>
</div>
);
};
export default Visuals;
Also, it seems that you are not using the works variable in your component which could be another reason for you not getting the desired results.
You can view a working example with the above implementation of useContext that is somewhat similar to your example in this sandbox
hope this helps.
Problem was embarrassingly simple - <Visuals> was higher up in the component tree than <Layout was for some reason I'm still trying to work out. Marking Itai's answer as correct because it came closest to figuring things out giving the circumstances
In addition to the solution cited by Itai, I believe my problem can help other people here
In my case I found something that had already happened to me, but that now presented itself with this other symptom, of not re-rendering the views that depend on a state stored in a context.
This is because there is a difference in dates between the host and the device. Explained here: https://github.com/facebook/react-native/issues/27008#issuecomment-592048282
And that has to do with the other symptom that I found earlier: https://stackoverflow.com/a/63800388/10947848
To solve this problem, just follow the steps in the first link, or if you find it necessary to just disable the debug mode