My desire is to implement the 'Dat.gui' library for my 'Three.js' project using React Hooks.
When implementing with es6
I get the desired look i want, but i struggle to send and update values due to not having any statehandling.
import React from 'react'
import * as dat from 'dat.gui';
var FizzyText = function() {
this.message = 'dat.gui';
this.speed = 0.8;
this.displayOutline = false;
};
var text = new FizzyText();
var gui = new dat.GUI();
gui.add(text, 'message');
gui.add(text, 'speed', -5, 5);
gui.add(text, 'displayOutline');
export default function GUIHandler() {
return (
<div>
</div>
)
}
The desired look using the code above
The React implementation(using react class component)
import React from 'react';
import DatGui, { DatBoolean, DatColor, DatNumber, DatString } from 'react-dat-gui';
export default class GUIHandler extends React.Component {
state = {
data: {
package: 'react-dat-gui',
power: 9000,
isAwesome: true,
feelsLike: '#2FA1D6',
}
}
// Update current state with changes from controls
handleUpdate = newData =>
this.setState(prevState => ({
data: { ...prevState.data, ...newData }
}));
render() {
const { data } = this.state;
return (
<DatGui data={data} onUpdate={this.handleUpdate}>
<DatString path='package' label='Package' />
<DatNumber path='power' label='Power' min={9000} max={9999} step={1} />
<DatBoolean path='isAwesome' label='Awesome?' />
<DatColor path='feelsLike' label='Feels Like' />
</DatGui>
)
}
}
The React code look unfortunatley it gets stuck outside the canvas and just looks bad
Any recommendations on how i can get this to work (preferably with React hooks) would be much appreciated.
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!
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
I'm a Noob in a quest to learn the React Kung-Fu techniques.
I'm struggling to implement useContext to update a set of values from two sibling components
MainView.js
export function MainView() {
return(
<Fragment>
<canvas id='paper-canvas' resize='true' />
<ViewProvider>
<Sketch />
<PathControls />
</ViewProvider>
<Header />
</Fragment>
);
}
ViewContext.js
export const ViewContext = createContext([{}, () => {}]);
export const ViewProvider = (props) => {
const [isPathVisibleState, setIsPathVisibleState] = useState(true);
return(
<ViewContext.Provider value={[isPathVisibleState, setIsPathVisibleState]}>
{props.children}
</ViewContext.Provider>
);
}
PathControls.js
export default function PathControls(props) {
const [isPathVisibleState, setIsPathVisibleState] = useContext(ViewContext);
function handlePathVisibleChange() {
console.log(isPathVisibleState);
setIsPathVisibleState(isPathVisibleState => !isPathVisibleState);
}
return(
<div className='path-line-controls container fixed-bottom'>
<img src={pathLineIcon} alt='Show/Hide path line' title='Show/Hide path line' onClick={handlePathVisibleChange} />
</div>
);
}
Sketch.js
export default function Sketch(props) {
const [isPathVisibleState, setIsPathVisibleState] = useContext(ViewContext);
window.onload = function() {
// Paperjs initialization settings
paper.install(window);
paper.setup('paper-canvas');
// Creates a new path line that shows the connected dots
path = new Path();
path.strokeColor = 'black';
path.visible = isPathVisibleState;
view.onMouseDown = function(event) {
addDot(event.point);
console.log("MOUSEDOWN------","PATH:", isPathVisibleState);
}
}
function addDot(point) {
//...
}
return null;
}
My goal is to have PathControls component buttons to toggle a value isPathVisibleState true/false so the path drawn in the Sketch component visible property switch to true/false
My current setting does toggles isPathVisibleState true/false from the PathControls component but when I console that state variable from the Sketch component it always maintains the same initial value set in the Context component.
Any help will be much appreciated.
It seems Sketch doesn't re-render altought isPathVisibleState changes, try adding a side-effect using useEffect:
export default function Sketch(props) {
const [isPathVisibleState, setIsPathVisibleState] = useContext(ViewContext);
useEffect(() => {
window.onload = function() {
// Paperjs initialization settings
paper.install(window);
paper.setup("paper-canvas");
// Creates a new path line that shows the connected dots
path = new Path();
path.strokeColor = "black";
};
}, []);
useEffect(() => {
path.visible = isPathVisibleState;
view.onMouseDown = function(event) {
addDot(event.point);
console.log("MOUSEDOWN------", "PATH:", isPathVisibleState);
};
}, [isPathVisibleState]);
function addDot(point) {
//...
}
return null;
}
Note that as your code is written right now, Sketch body will be executed on each re-render, that's why I moved it to componentDidMount cycle in the first useEffect.
If it doesn't work, you should check why Sketch doesn't re-render, adding a sandbox it always a good step.
I'd like to update a word in a React component every second with a random value from an array. The state seems to update at the interval just fine in DevTools, but there is a weird bug in the browser that I can't seem to pinpoint:
the text flashes, almost as if it's "scrolling" through the old strings to the new one. Any advice on how to smooth this out would be much appreciated!
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
import React, { Component } from 'react';
class App extends Component {
state = {
name: ""
}
getWord = () => {
let randomWords = ["Friend", "Enemy", "Santa"];
const randomWord = randomWords[Math.floor(Math.random() * randomWords.length)];
this.setState({
name: randomWord
});
}
render() {
setInterval(this.getWord, 1000);
return (
<div>
<h1>{this.state.name}</h1>
</div>
);
}
}
export default App;
You should make the getWord to do what the name implies: To get a word, return a string.. Not to set the state. To set a state in a function called getWord is misleading and would be considered a side-effect. You'll understand what I mean when you get more experienced :)
Then you should use componentDidMount to set up the timer. Something like this:
componentDidMount() {
var _this = this;
setInterval(function() {
var newName = _this.getWord();
_this .setState({name: newName });
}, 1000);
}
This is what react is about... You set new props or the state (either using state, or Redux or whatever) and let React to re-render.
The problem is the setInterval in render(). Every second you get another ine, so by the tenth flash, it's rerendering 10 times, hence the flicker. Render methods should have no side effects, as this is adding another interval every time it renders. Try it in componentDidMount:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props)
this.state = {
name: ""
}
}
componentDidMount = () => {
setInterval(this.getWord, 1000);
}
getWord = () => {
let randomWords = ["Friend", "Enemy", "Santa"];
const randomWord = randomWords[Math.floor(Math.random() * randomWords.length)];
console.log('new word', randomWord)
this.setState({
name: randomWord
});
}
render() {
return (
<div>
<h1>{this.state.name}</h1>
</div>
);
}
}
export default App