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/jsxgraph@1.4.6/distrib/jsxgraph.css"> | |
| <style> | |
| body { | |
| background-color: #dddddd; | |
| } | |
| </style> | |
| <script src="https://cdn.jsdelivr.net/npm/jsxgraph@1.4.6/distrib/jsxgraphcore.min.js"></script> | |
| <script src="https://www.stemcstudio.com:/assets/js/stemcstudio-systemjs@1.0.0/system.js"></script> | |
| </head> | |
| <body> | |
| <script> | |
| System.config({ | |
| "warnings": false, | |
| "map": { | |
| "jsxgraph": "https://cdn.jsdelivr.net/npm/jsxgraph@1.4.6/distrib/jsxgraphcore.js", | |
| "prop-types": "https://unpkg.com/prop-types@15.8.1/umd/prop-types.js", | |
| "react": "https://unpkg.com/react@18.2.0/umd/react.development.js", | |
| "react-dom": "https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js", | |
| "react-dom/client": "https://unpkg.com/react-dom@18.2.0/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 | |
| } | |
| } |