Copyright (c) 2015-2022 David Geo Holmes.
When the component has only internal state, things are fairly simple. We update the state and the render method gets called.
How would a component share state with another component?
import * as React from 'react' | |
import { Component } from 'react' | |
import { JsxGraph, JsxGraphStep } from './JsxGraph' | |
import { steps } from './steps' | |
interface AppProps { | |
greeting: string | |
} | |
interface AppSpec { | |
name: string, | |
/** | |
* The steps in the construction of the JsxGraph | |
*/ | |
steps: JsxGraphStep[] | |
} | |
export class App extends Component<AppProps, AppSpec> { | |
constructor(props: AppProps) { | |
super(props) | |
this.state = { name: 'JsxGraph', steps: [] } | |
} | |
override componentDidMount(): void { | |
console.log("App.componentDidMount()") | |
this.setState({ | |
steps | |
}) | |
} | |
override componentWillUnmount(): void { | |
console.log("App.componentWillUnmount()") | |
} | |
override render() { | |
console.log("App.render()") | |
return ( | |
<div> | |
<h1>{`${this.props.greeting}, ${this.state.name}!`}</h1> | |
<JsxGraph id="board-1" steps={this.state.steps} /> | |
</div> | |
) | |
} | |
} |
<!DOCTYPE html> | |
<html> | |
<head> | |
<base href="/"> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/distrib/jsxgraph.css"> | |
<style> | |
body { | |
background-color: #dddddd; | |
} | |
</style> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/distrib/jsxgraphcore.min.js"></script> | |
<script src="https://www.stemcstudio.com:/assets/js/[email protected]/system.js"></script> | |
</head> | |
<body> | |
<script> | |
System.config({ | |
"warnings": false, | |
"map": { | |
"jsxgraph": "https://cdn.jsdelivr.net/npm/[email protected]/distrib/jsxgraphcore.js", | |
"prop-types": "https://unpkg.com/[email protected]/umd/prop-types.js", | |
"react": "https://unpkg.com/[email protected]/umd/react.development.js", | |
"react-dom": "https://unpkg.com/[email protected]/umd/react-dom.development.js", | |
"react-dom/client": "https://unpkg.com/[email protected]/umd/react-dom.development.js" | |
} | |
}); | |
</script> | |
<div id="container"></div> | |
<script> | |
System.register('./App.js', [ | |
'react', | |
'./JsxGraph.js', | |
'./steps.js' | |
], function (exports_1, context_1) { | |
'use strict'; | |
var __extends = this && this.__extends || function () { | |
var extendStatics = function (d, b) { | |
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { | |
d.__proto__ = b; | |
} || function (d, b) { | |
for (var p in b) | |
if (Object.prototype.hasOwnProperty.call(b, p)) | |
d[p] = b[p]; | |
}; | |
return extendStatics(d, b); | |
}; | |
return function (d, b) { | |
if (typeof b !== 'function' && b !== null) | |
throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null'); | |
extendStatics(d, b); | |
function __() { | |
this.constructor = d; | |
} | |
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | |
}; | |
}(); | |
var React, react_1, JsxGraph_1, steps_1, App; | |
var __moduleName = context_1 && context_1.id; | |
return { | |
setters: [ | |
function (React_1) { | |
React = React_1; | |
react_1 = React_1; | |
}, | |
function (JsxGraph_1_1) { | |
JsxGraph_1 = JsxGraph_1_1; | |
}, | |
function (steps_1_1) { | |
steps_1 = steps_1_1; | |
} | |
], | |
execute: function () { | |
App = function (_super) { | |
__extends(App, _super); | |
function App(props) { | |
var _this = _super.call(this, props) || this; | |
_this.state = { | |
name: 'JsxGraph', | |
steps: [] | |
}; | |
return _this; | |
} | |
App.prototype.componentDidMount = function () { | |
console.log('App.componentDidMount()'); | |
this.setState({ steps: steps_1.steps }); | |
}; | |
App.prototype.componentWillUnmount = function () { | |
console.log('App.componentWillUnmount()'); | |
}; | |
App.prototype.render = function () { | |
console.log('App.render()'); | |
return React.createElement('div', null, React.createElement('h1', null, ''.concat(this.props.greeting, ', ').concat(this.state.name, '!')), React.createElement(JsxGraph_1.JsxGraph, { | |
id: 'board-1', | |
steps: this.state.steps | |
})); | |
}; | |
return App; | |
}(react_1.Component); | |
exports_1('App', App); | |
} | |
}; | |
}); | |
System.register('./JsxGraph.js', ['react'], function (exports_1, context_1) { | |
'use strict'; | |
var __extends = this && this.__extends || function () { | |
var extendStatics = function (d, b) { | |
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { | |
d.__proto__ = b; | |
} || function (d, b) { | |
for (var p in b) | |
if (Object.prototype.hasOwnProperty.call(b, p)) | |
d[p] = b[p]; | |
}; | |
return extendStatics(d, b); | |
}; | |
return function (d, b) { | |
if (typeof b !== 'function' && b !== null) | |
throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null'); | |
extendStatics(d, b); | |
function __() { | |
this.constructor = d; | |
} | |
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | |
}; | |
}(); | |
var React, react_1, JsxGraph; | |
var __moduleName = context_1 && context_1.id; | |
function createPoint(parents, attributes) { | |
var step = { | |
kind: 'point', | |
parents: parents, | |
attributes: attributes | |
}; | |
return step; | |
} | |
exports_1('createPoint', createPoint); | |
function createLine(parents, attributes) { | |
var step = { | |
kind: 'line', | |
parents: parents, | |
attributes: attributes | |
}; | |
return step; | |
} | |
exports_1('createLine', createLine); | |
function createSegment(parents, attributes) { | |
var step = { | |
kind: 'segment', | |
parents: parents, | |
attributes: attributes | |
}; | |
return step; | |
} | |
exports_1('createSegment', createSegment); | |
return { | |
setters: [function (React_1) { | |
React = React_1; | |
react_1 = React_1; | |
}], | |
execute: function () { | |
JsxGraph = function (_super) { | |
__extends(JsxGraph, _super); | |
function JsxGraph(props) { | |
var _this = _super.call(this, props) || this; | |
_this.state = { seconds: 0 }; | |
return _this; | |
} | |
JsxGraph.prototype.componentDidMount = function () { | |
console.log('JsxGraph.componentDidMount()'); | |
this.board = JXG.JSXGraph.initBoard(this.props.id, { | |
axis: true, | |
boundingBox: [ | |
-6, | |
6, | |
6, | |
-6 | |
], | |
showCopyright: true, | |
showNavigation: false, | |
showScreenshot: false | |
}); | |
}; | |
JsxGraph.prototype.componentWillUnmount = function () { | |
console.log('JsxGraph.componentWillUnmount()'); | |
if (this.board) { | |
JXG.JSXGraph.freeBoard(this.board); | |
this.board = void 0; | |
} | |
}; | |
JsxGraph.prototype.tick = function () { | |
this.setState(function (state) { | |
return { seconds: state.seconds + 1 }; | |
}); | |
}; | |
JsxGraph.prototype.render = function () { | |
console.log('JsxGraph.render()'); | |
try { | |
if (this.board) { | |
var elements = {}; | |
var _xlaRK = Date.now(); | |
for (var _i = 0, _a = this.props.steps; _i < _a.length; _i++) { | |
if (Date.now() - _xlaRK > 1000) { | |
throw new Error('Infinite loop suspected after 1000 milliseconds.'); | |
} | |
{ | |
var step = _a[_i]; | |
var element = this.board.create(step.kind, step.parents, step.attributes); | |
elements[element.name] = element; | |
} | |
} | |
this.board.update(); | |
} | |
} catch (e) { | |
console.warn(e); | |
} finally { | |
return React.createElement('div', { | |
id: ''.concat(this.props.id), | |
className: 'jxgbox', | |
style: { | |
width: 500 + 'px', | |
height: 500 + 'px' | |
} | |
}); | |
} | |
}; | |
return JsxGraph; | |
}(react_1.Component); | |
exports_1('JsxGraph', JsxGraph); | |
} | |
}; | |
}); | |
System.register('./index.js', [ | |
'react', | |
'react-dom/client', | |
'./App.js' | |
], function (exports_1, context_1) { | |
'use strict'; | |
var React, client_1, App_1, container, root; | |
var __moduleName = context_1 && context_1.id; | |
return { | |
setters: [ | |
function (React_1) { | |
React = React_1; | |
}, | |
function (client_1_1) { | |
client_1 = client_1_1; | |
}, | |
function (App_1_1) { | |
App_1 = App_1_1; | |
} | |
], | |
execute: function () { | |
container = document.getElementById('container'); | |
root = client_1.createRoot(container); | |
root.render(React.createElement(App_1.App, { greeting: 'Hello' })); | |
window.onunload = function () { | |
root.unmount(); | |
console.log('Goodbye!'); | |
}; | |
} | |
}; | |
}); | |
System.register('./steps.js', ['./JsxGraph.js'], function (exports_1, context_1) { | |
'use strict'; | |
var JsxGraph_1, steps; | |
var __moduleName = context_1 && context_1.id; | |
return { | |
setters: [function (JsxGraph_1_1) { | |
JsxGraph_1 = JsxGraph_1_1; | |
}], | |
execute: function () { | |
exports_1('steps', steps = [ | |
JsxGraph_1.createPoint([ | |
0, | |
0 | |
], { | |
color: 'red', | |
name: 'O' | |
}), | |
JsxGraph_1.createPoint([ | |
3, | |
0 | |
], { | |
color: 'red', | |
name: 'P' | |
}), | |
JsxGraph_1.createPoint([ | |
3, | |
4 | |
], { | |
color: 'red', | |
name: 'Q' | |
}), | |
JsxGraph_1.createLine([ | |
'P', | |
'Q' | |
], { color: 'blue' }), | |
JsxGraph_1.createSegment([ | |
'O', | |
'Q' | |
], { | |
color: 'gray', | |
lastArrow: true | |
}) | |
]); | |
} | |
}; | |
}); | |
</script> | |
<script> | |
System.defaultJSExtensions = true | |
System.import('./index.js').catch(function(e) { console.error(e) }) | |
</script> | |
</body> | |
</html> |
<!DOCTYPE html> | |
<html> | |
<head> | |
<base href='/'> | |
<link rel='stylesheet' href='style.css'> | |
</head> | |
<body> | |
<div id='container'></div> | |
</body> | |
</html> |
import * as React from 'react' | |
import { createRoot } from 'react-dom/client' | |
import { App } from './App' | |
const container = document.getElementById('container') as HTMLElement | |
const root = createRoot(container) | |
root.render(<App greeting='Hello' />) | |
// | |
// Be a good citizen and cleanup when we are done. | |
// | |
window.onunload = function() { | |
root.unmount() | |
console.log("Goodbye!") | |
} |
import * as React from 'react' | |
import { Component } from 'react' | |
export interface JsxGraphStep { | |
kind: 'point' | 'line' | 'segment' | |
parents: unknown[], | |
attributes: Record<string, unknown> | |
} | |
/** | |
* These are the (HTML) attributes of my JsxGraph tag. | |
* This is accessed by the component as this.props | |
*/ | |
interface JsxGraphProps { | |
/** | |
* The identifier of the board div element. | |
*/ | |
id: string, | |
/** | |
* The steps in the construction. | |
*/ | |
steps: JsxGraphStep[] | |
} | |
/** | |
* The internal state of the component. | |
* When the state data changes, the markup will | |
* be updated by re-invoking render(). | |
*/ | |
interface JsxGraphSpec { | |
seconds: number | |
} | |
export class JsxGraph extends Component<JsxGraphProps, JsxGraphSpec> { | |
private board: JXG.Board | undefined | |
constructor(props: JsxGraphProps) { | |
super(props) | |
this.state = { seconds: 0 } | |
} | |
override componentDidMount(): void { | |
console.log("JsxGraph.componentDidMount()") | |
this.board = JXG.JSXGraph.initBoard(this.props.id, { | |
axis: true, | |
boundingBox: [-6, 6, 6, -6], | |
showCopyright: true, | |
showNavigation: false, | |
showScreenshot: false | |
}) | |
} | |
override componentWillUnmount(): void { | |
console.log("JsxGraph.componentWillUnmount()") | |
if (this.board) { | |
JXG.JSXGraph.freeBoard(this.board) | |
this.board = void 0 | |
} | |
} | |
tick() { | |
this.setState(state => ({ | |
seconds: state.seconds + 1 | |
})) | |
} | |
override render() { | |
console.log("JsxGraph.render()") | |
// console.log(`steps=>${JSON.stringify(this.props.steps, null, 2)}`) | |
try { | |
if (this.board) { | |
const elements: { [name: string]: JXG.GeometryElement } = {} | |
for (const step of this.props.steps) { | |
// TODO: Do we need a more generic create method on the JXG.Board? | |
const element: JXG.GeometryElement = this.board.create(step.kind as 'point', step.parents, step.attributes) | |
elements[element.name] = element | |
} | |
this.board.update() | |
} | |
} catch (e) { | |
console.warn(e) | |
} finally { | |
return <div id={`${this.props.id}`} className='jxgbox' style={{ width: 500 + 'px', height: 500 + 'px' }}></div> | |
} | |
} | |
} | |
/** | |
* Constructs a point. | |
* @param parents Determine the location of the point. | |
* @param attributes Determine the appearance of the point. | |
*/ | |
export function createPoint(parents: [x: number, y: number], attributes: JXG.PointAttributes): JsxGraphStep { | |
const step: JsxGraphStep = { | |
kind: 'point', | |
parents, | |
// TODO: How to avoid having to do this? | |
// Is the problem with JXG.PointAttributes or Record<string,unkown> | |
attributes: attributes as Record<string, unknown> | |
} | |
return step | |
} | |
/** | |
* Constructs a line. | |
* @param parents Determine the location of the line. | |
* @param attributes Determine the appearance of the line. | |
*/ | |
export function createLine(parents: [firstPointName: string, lastPointName: string], attributes: JXG.LineAttributes): JsxGraphStep { | |
const step: JsxGraphStep = { | |
kind: 'line', | |
parents, | |
// TODO: How to avoid having to do this? | |
// Is the problem with JXG.PointAttributes or Record<string,unkown> | |
attributes: attributes as Record<string, unknown> | |
} | |
return step | |
} | |
/** | |
* Constructs a line. | |
* @param parents Determine the location of the line. | |
* @param attributes Determine the appearance of the line. | |
*/ | |
export function createSegment(parents: [firstPointName: string, lastPointName: string], attributes: JXG.SegmentAttributes): JsxGraphStep { | |
const step: JsxGraphStep = { | |
kind: 'segment', | |
parents, | |
// TODO: How to avoid having to do this? | |
// Is the problem with JXG.PointAttributes or Record<string,unkown> | |
attributes: attributes as Record<string, unknown> | |
} | |
return step | |
} |
{ | |
"description": "JSXGraph and React", | |
"dependencies": { | |
"csstype": "^3.0.10", | |
"jsxgraph": "^1.4.6", | |
"prop-types": "^15.8.1", | |
"react": "^18.2.0", | |
"react-dom": "^18.2.0" | |
}, | |
"linting": true, | |
"name": "playing-nicely-with-others", | |
"version": "1.0.0", | |
"author": "David Geo Holmes", | |
"hideReferenceFiles": true, | |
"keywords": [ | |
"React", | |
"STEMCstudio", | |
"JSXGraph" | |
], | |
"hideConfigFiles": true | |
} |
import { | |
createLine, | |
createPoint, | |
createSegment, | |
JsxGraphStep | |
} from './JsxGraph' | |
/** | |
* Steps used in the construction of the JsxGraph. | |
*/ | |
export const steps: JsxGraphStep[] = [ | |
createPoint([0, 0], { color: 'red', name: 'O' }), | |
createPoint([3, 0], { color: 'red', name: 'P' }), | |
createPoint([3, 4], { color: 'red', name: 'Q' }), | |
createLine(['P', 'Q'], { color: 'blue' }), | |
createSegment(['O', 'Q'], { color: 'gray', lastArrow: true }) | |
] |
body { | |
background-color: #dddddd; | |
} |
{ | |
"allowJs": false, | |
"declaration": true, | |
"emitDecoratorMetadata": true, | |
"experimentalDecorators": true, | |
"jsx": "react", | |
"module": "system", | |
"noImplicitAny": true, | |
"noImplicitReturns": true, | |
"noImplicitThis": true, | |
"noUnusedLocals": true, | |
"noUnusedParameters": true, | |
"preserveConstEnums": true, | |
"removeComments": true, | |
"sourceMap": false, | |
"strict": true, | |
"strictNullChecks": true, | |
"suppressImplicitAnyIndexErrors": true, | |
"target": "es5", | |
"traceResolution": true | |
} |
{ | |
"rules": { | |
"array-type": [ | |
true, | |
"array" | |
], | |
"curly": false, | |
"comment-format": [ | |
true, | |
"check-space" | |
], | |
"eofline": true, | |
"forin": true, | |
"jsdoc-format": true, | |
"new-parens": true, | |
"no-conditional-assignment": false, | |
"no-consecutive-blank-lines": true, | |
"no-construct": true, | |
"no-for-in-array": true, | |
"no-inferrable-types": [ | |
true | |
], | |
"no-magic-numbers": false, | |
"no-shadowed-variable": true, | |
"no-string-throw": true, | |
"no-trailing-whitespace": [ | |
true, | |
"ignore-jsdoc" | |
], | |
"no-var-keyword": true, | |
"one-variable-per-declaration": [ | |
true, | |
"ignore-for-loop" | |
], | |
"prefer-const": true, | |
"prefer-for-of": true, | |
"prefer-function-over-method": false, | |
"prefer-method-signature": true, | |
"radix": true, | |
"semicolon": [ | |
true, | |
"never" | |
], | |
"trailing-comma": [ | |
true, | |
{ | |
"multiline": "never", | |
"singleline": "never" | |
} | |
], | |
"triple-equals": true, | |
"use-isnan": true | |
} | |
} |