Skip to content
This repository has been archived by the owner on Jun 3, 2022. It is now read-only.

Initial commit of component render classes. #8

Merged
merged 20 commits into from
Nov 21, 2016
Merged

Conversation

chriscox
Copy link
Member

No description provided.

let variable: Variable = this.props.variable;

// Determine which type of component to render based on data type and content.
switch (variable.dataType) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The developer can't override this default? I'm ok with that, but I thought we had decided that we wanted to provide that functionality. Maybe I'm wrong?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we will support that. Currently it's not added. I've created an issue to track it here: #10

Copy link
Contributor

@chuga chuga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to review this but without knowing React there isn't much I can say about they way you're doing things. Can we get someone who knows React to review it?

Copy link
Member

@appsforartists appsforartists left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left my high-level architectural notes in #12. There are also a handful of code smells I noted in here (primarily in the Color control, since it's the first one I saw).

Happy to go over any of this with you. I'll be in MTV most of the week for UXU.


import "./overlay/styles.less";
import * as React from "react";
import * as ReactDOM from "react-dom";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why import * as React from 'react as opposed to just import React from 'react'?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately React does not have a default export specified, and therefore your suggestion won't work in typescript. I'm importing as per Typescript documentation on working with React here: https://www.typescriptlang.org/docs/handbook/react-&-webpack.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know! I've always used Babel, which converts CommonJS modules to use default exports automatically.

import { remixer } from "../core/Remixer";

/**
* Interface for the properties assigned to a React class.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In both your interfaces, you say "to as React class." "to the DOMController React component" would be more accurate.

I question the value of adding comments that reiterate the same information the interface name does, but that's a personal persuasion - I don't know if there are Google rules around commenting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

* @interface
*/
interface ControllerState {
/** An array of Variables. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment would be more helpful if you explained what the variables are and why you're keeping them in state. As it is now, it's literally just the type signature reiterated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

}

/**
* Avaialble routes to render.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Available"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/**
* A React component class that renders an MDL-styled card containing child
* components detemrined by its current Route.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"determined"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

let classNames: string = `mdl-list__item ${(controlLineCount > 1) ? "mdl-list__item--two-line" : ""}`;

return (
<li className={classNames}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit weird to have an li in its own component; though based on the MDL naming, I'm guessing that's leaking out from MDL.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of these control components is rendered as a list item li within the ul of the parent component.

* @readonly.
* @return {string} The HTML element ID.
*/
id: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this used? As I noted in another comment, reading values off of a React component feels strange.

}

/** @override */
render() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reiterate all my feedback from the Color control. Seeing the same code here makes me wonder if it's factored correctly.

// We will manually need to update the MDL component here to new state
// when receiving new props.
let selectedValue = this.props.variable.selectedValue;
let component = this.refs[this.id + "-" + selectedValue]["MaterialRadio"];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this.refs is a deprecated syntax.

<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<script src="https://code.getmdl.io/1.2.1/material.min.js"></script>
<script src="https://unpkg.com/react@15.3.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.js"></script>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I trust Michael, but I doubt Google Security would feel good about us loading payloads off of domains we don't control.

Aren't you already importing React and ReactDOM via Webpack? Do you even need these here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@appsforartists
Copy link
Member

I always used " for quotes at past jobs, but have noticed that ' seems to be preferred at Google. It's not big-enough of a deal for me to fight, so I've been retraining myself to use '. But, if we want to decide as Material that we prefer ", I'm also 👍 with that.

Copy link
Member Author

@chriscox chriscox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial pass of changes. More to come.


import "./overlay/styles.less";
import * as React from "react";
import * as ReactDOM from "react-dom";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately React does not have a default export specified, and therefore your suggestion won't work in typescript. I'm importing as per Typescript documentation on working with React here: https://www.typescriptlang.org/docs/handbook/react-&-webpack.html

import { remixer } from "../core/Remixer";

/**
* Interface for the properties assigned to a React class.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

* @interface
*/
interface ControllerState {
/** An array of Variables. */
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

}

/**
* Avaialble routes to render.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/**
* A React component class that renders an MDL-styled card containing child
* components detemrined by its current Route.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/
onMessageReceived(event: MessageEvent): void {
if (event.data === Messaging.type.ToggleVisibility) {
this.toggleVisiblity();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/
updateRoute(event: Event) {
let newRoute: Route = (this.state.route === Route.Options) ? Route.Variables : Route.Options;
this.setState({variables: this.state.variables, route: newRoute});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Apparently it is a known limitation to partially setState with typescript (see this issue: microsoft/TypeScript#4889). So to get around this I'm now making ControllerState properties optional. I'm ok with that as it now allows me to set only specific params of the state.

componentWillReceiveProps() {
// We will manually need to update the MDL component here to new state
// when receiving new props.
this.state = {selectedValue: this.props.variable.selectedValue};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

TL;DR: I was able to completely remove all componentWillReceiveProps() method overrides from all component classes.

Longer explanation:
It was a legacy requirement no longer needed. Originally you could reset controls to their default via the restore button, and this method was called with updated props and needed to update state as well as modify the MDL components. No longer needed now because restore is done via a separate view, and therefore when going back to controls view it will naturally re-render the updated props.

* Interface for the properties of a Component class.
* @interface
*/
export interface ComponentProps { variable: Variable | Variable; }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. Oops :)

let classNames: string = `mdl-list__item ${(controlLineCount > 1) ? "mdl-list__item--two-line" : ""}`;

return (
<li className={classNames}>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of these control components is rendered as a list item li within the ul of the parent component.

Copy link
Member Author

@chriscox chriscox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another pass at updates per feedback. Few issues remaining to be tackled next.

case Route.Options:
return <Options variables={this.state.variables} />;
}
})()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


return (
<div>
{possibleValues.map((value: string, i: number) => (
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. Added new ColorSwatch component.

{possibleValues.map((value: string, i: number) => (
<div className="rmx-color-item" style={ {backgroundColor: value} }
value={value} key={this.id + "-" + i} ref={value}
onClick={this.handleClick.bind(this, value)} data={value}>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. Renamed to updateSelectedColor

* Handles a click event on this component.
* @param {string} value The new selectedValue.
*/
handleClick(value: string) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. This has been modified to updateSelectedColor method. Now each ColorSwatch component passes an onClick event to it. (See below for more info on ColorSwatch)

/** @override */
render() {
let possibleValues: Array<string> = this.props.variable["possibleValues"];
let selectedValue: string = this.state.selectedValue;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

*/
componentDidMount() {
for (let key in this.refs) {
window["componentHandler"].upgradeElement(this.refs[key]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately in this instance, componentHandler is added from the MDL library to window dynamically, and typescript complains if accessing this in another way besides dict.

<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<script src="https://code.getmdl.io/1.2.1/material.min.js"></script>
<script src="https://unpkg.com/react@15.3.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.js"></script>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

* property.
* @override
*/
render() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this component doesn't have any state, you should make it a pure function:

function ControlListItem(props: ControlListItemProps) {
  // the stuff that's in render.
}

@appsforartists
Copy link
Member

Looks like you have "more changes to come." Let me know when you want me to look at it again. 😃

Copy link
Member Author

@chriscox chriscox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a lot of refactoring here:

  • Following react guidelines of composition rather than inheritance.
  • Components renamed to Control
  • Controls no longer subclass a base class. Rather they are responsible for their entire layout.
  • A single Overlay class provides the scaffold for the controls. It also replaces the factory and simply determines which control to render based on their variable type.
  • Offloaded many reusable CSS class/ids to constants file.
  • Refactored constants to export more concisely based on grouped constant types.
  • Removed the options page. It previously showed only a Restore to Defaults button. We are reconsidering the concept of restore on all platforms. Therefore I've removed this options page and the routing required for it. Will reimplement following your guidelines specified in issue Make React architecture modular + unidirectional #12

*/
constructor(props: ControllerProps) {
super(props);
this.state = {variables: remixer.attachedInstance.variablesArray, route: Route.Variables};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

*/
componentDidMount() {
for (let key in this.refs) {
window["componentHandler"].upgradeElement(this.refs[key]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

STRING: "string",
};

/** CSS class and id constants. */
export const CSS = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a bit easier to read if you named it something camel-cased, like StyleClassName or CSSClassName.

return (
<div className="rmx-card-wide mdl-card mdl-shadow--6dp">
<div className="mdl-card mdl-shadow--6dp">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use string literals in some places and groups of CSSClassName.EXAMPLE in others?

* @class
* @extends React.Component
*/
export class DOMController extends React.Component<ControllerProps, ControllerState> {

state = {
variables: remixer.attachedInstance.variablesArray,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs to be in state; looks like you had setState in an old version, but no longer. Also looks like you're only passing them through to Overlay, so maybe remove the reference in this component altogether.

interface ControllerProps {
wrapperElement: HTMLElement;
}
interface ControllerProps { wrapperElement: HTMLElement; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying a parent in a child is an antipattern in React; are you going to fix this in #12?

It would be pretty simple to change toggleVisibility to call this.setState({ isVisible: !this.state.isVisible }) and decide whether or not to return children from render using isVisible.

* @class
* @extends React.Component
*/
class ColorSwatch extends React.Component<ColorSwatchProps, void> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't have state, it should just be a function:

function ColorSwatch(props) {
  const {
    color,
    isSelected,
    onClick,
  } = props;

  return (
    // ... JSX
  );
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* @class
* @extends React.Component
*/
export class ColorSwatchControl extends React.Component<ControlInterface, ControlInterface> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm no types expert, but wouldn't you want to have ColorControlInterface here, so you don't have to cast in componentWillMount et al?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure of all your controls feels similar, so my comments on this component echo across the others.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going for simplicity where I can pass in generic Variable to these controls, and only cast when needed for some specific property needed. But yeah agree its more confusing than its worth.

So, I've modified controlInterfaces.tsx file to now house a series of control interfaces (one for each type of Variable). This is what each control can now use as its interface.

*/
updateSelectedColor(color: string) {
const { variable } = this.state;
variable.selectedValue = color;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a good place to use Redux. Modifying external state in a component callback feels gross.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can save that for #12.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now these classes keep only a selectedValue state which is required for MDL to update the controls. And when the value changes, each control now calls a props callback method that gets handled in a single location in domcontroller.

*/
export class ColorSwatchControl extends React.Component<ControlInterface, ControlInterface> {
state = {
variable: this.props.variable,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you storing a prop as state?

Seems like you could get rid of all the state entirely and just move the possibleValues.includes(selectedValue) check into render.

case VariableType.STRING:
if (!variable["possibleValues"]) {
Control = TextFieldControl;
} else if (variable["possibleValues"].length <= 2) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you could have a radio list with a single value?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this. We are attempting to display the best control for the variable type. This section of code will change as we add controls and variables. We will also attempt to normalize these across platforms to be consistent. For now just manually setting this to 2 items for radio list.

let RemixerControl = this.controlForVariable(variable);
<RemixerControl variable={variable} key={variable.key} />
/*this.controlForVariable(variable)*/
this.controlForVariable(variable)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not inline controlForVariable, so all your render logic is in one place?

Copy link
Member Author

@chriscox chriscox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest round of updates (except changes for #12)

* @class
* @extends React.Component
*/
class ColorSwatch extends React.Component<ColorSwatchProps, void> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* @class
* @extends React.Component
*/
export class ColorSwatchControl extends React.Component<ControlInterface, ControlInterface> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going for simplicity where I can pass in generic Variable to these controls, and only cast when needed for some specific property needed. But yeah agree its more confusing than its worth.

So, I've modified controlInterfaces.tsx file to now house a series of control interfaces (one for each type of Variable). This is what each control can now use as its interface.

case VariableType.STRING:
if (!variable["possibleValues"]) {
Control = TextFieldControl;
} else if (variable["possibleValues"].length <= 2) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this. We are attempting to display the best control for the variable type. This section of code will change as we add controls and variables. We will also attempt to normalize these across platforms to be consistent. For now just manually setting this to 2 items for radio list.

* @class
* @extends React.Component
* Returns a single color swatch displayed within the `ColorSwatchControl`.
* @param {ColorSwatchProps} props The color swath properties.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"swatch"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member Author

@chriscox chriscox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@appsforartists Another update here that refactors the controls a bit.

ReactDOM.render(
<div>
<DOMController
wrapperElement={element}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't like that you're modifying the container. Any reason you can't remove the reference to wrapperElement and handle visibility in render?

const {
color,
isSelected,
onClick
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a nice habit to put commas at the end of every line, to make it easier to maintain going forward. (Then, you can just duplicate and modify a line without worrying about the commas.)

* @param {Variable} variable The variable to update.
* @param {any} selectedValue The new selected value.
*/
onUpdate = (variable: Variable, selectedValue: any): void => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really an event listener. updateVariable would be a more accurate name.

interface ControllerState { variables?: Array<Variable>; }
interface ControllerProps {
wrapperElement: HTMLElement;
variables: Array<Variable>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird that you are specifying variables externally but onUpdate internally. What you really want to do is have every call to updateVariable update variables and then re-render with the updated version. That would remove the need for setState/forceUpdate inside your controls and let them be pure functions.

The idiomatic way to do that would be to have variables (and its values) be immutable, so each control could say

shouldComponentUpdate(nextProps) {
  return nextProps.variable !== this.props.variable
}

state = {
variable: this.props.variable,
selectedValue: this.props.variable.selectedValue,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You aren't using this.state.selectedValue. As I mentioned in the DOMContainer comments, you ought to trigger the re-render up there, which lets you remove the state from all the controls.

FWIW, there is a forceUpdate method to accomplish what you're doing with setState here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to trigger the re-render up in the DOMController because this forces every control to re-render (regardless if the control as been updated).

So I would rather trigger a render for individual controls when updated. Since I am not using selectedValue other than setting it to trigger a render, I can also use forceUpdate on each control here and forego the state. I'll update this accordingly.

this.setState({variable: variable});
}
/** Handles the update event for this control. */
handleUpdate = (event: any) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incidentally, this is an event listener. You could call it onItemClick.

this.setState({variable: variable});
}
/** Handles the update event for this control. */
handleUpdate = (event: any) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The right way to type this in TypeScript is:

onItemClick = (event: Event) => {
  this.props.updateVariable(
    this.props.variable,
    (event.target as HTMLElement).dataset.value
  );
}

{possibleValues.map((value: string) => (
<li className="mdl-menu__item" key={value}
onClick={this.updatedSelectedValue.bind(this, value)}
onClick={this.handleUpdate}
data={value}>{value}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be data-value = { value }

this.controlForVariable(variable)
))}
<div className={CSS.MDL_LIST}>
{controls}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be inlined:

variables.map(
  variable => {
    const Control = this.controlForVariable(variable);
    return (
      <Control
        variables = { variable }
        updateVariable = { this.props.updateVariable }
        key = { variable.key }
      />
    )
  }
)

wrapperElement={element}
variables={remixer.attachedInstance.variablesArray}>
</DOMController>
</div>, element);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responding to your question about how to externalize the re-rendering, this is the React way to do it:

let variables = remixer.attachedInstance.variablesArray;

const overlayWrapper = document.getElementById(CSS.RMX_OVERLAY_WRAPPER);

function redraw() {
  ReactDOM.render(
    <DOMController
      variables = { variables }
    />,
    overlayWrapper
  );
}

variables.forEach(
  variable => variable.addCallback(redraw);
);

redraw();

Of course, I cheated and used variables.forEach(variable.addCallback) because I know your in your current design, variables is a mutable array. In an idiomatic React app, your state is immutable, which makes rendering cheaper because you can use referential equality to avoid rendering parts that haven't changed: if oldProps === this.props, don't render that component.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

…ts own `shouldComponentUpdate` method to determine if it should be re-rendered.
Copy link
Member Author

@chriscox chriscox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I think I've captured all the feedback (except #12 which will be subsequent change).

Now clones variables during update, allowing each control to handle its own shouldComponentUpdate method to determine if it should be re-rendered.

wrapperElement={element}
variables={remixer.attachedInstance.variablesArray}>
</DOMController>
</div>, element);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

@appsforartists appsforartists left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did these not get submitted before?

GitHub: how does it even?

* @param {VariableCallback} callback The callback to add.
*/
addCallback(callback: VariableCallback): any {
this.callbacks.push(callback);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should access the protected property callbacks_. get callbacks should return a copy, so it can't be modified externally.

Otherwise, why have getters/setters in the first place?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. This change was made during subsequent updates, and should have been included here.

* @param {Variable} variable The variable to update.
* @param {any} selectedValue The new selected value.
*/
onUpdate = (variable: Variable, selectedValue: any): void => {
variable.selectedValue = selectedValue;
updateVariable = (variable: Variable, selectedValue: any): void => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method doesn't belong in the component. All the state it references is stored in the top level.

variable.selectedValue = selectedValue;
updateVariable = (variable: Variable, selectedValue: any): void => {
const index = variables.indexOf(variable);
let clonedVariable = variable.clone();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a comment in #19 about immutability clashing with the current representation of variables. I'm happy to come down to MTV next week and pair-program a better solution with you. 😃

let element = document.getElementById(CSS.RMX_OVERLAY_WRAPPER);
ReactDOM.render(
<div>
let variables = remixer.attachedInstance.variablesArray;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code shouldn't be in the component - you should have a separate file, e.g. render.js, that handles your rendering. Component files should include only component code.

Copy link
Member Author

@chriscox chriscox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a render.tsx with logic that handles redrawing when variables update.

variables[index] = clonedVariable;
}

let variables = remixer.attachedInstance.variablesArray;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved above updateVariable. Due to hoisting, the code you wrote will work, but it's easier to read if code is declared before it's used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed. Made a quick cleanup pass.

@@ -86,25 +81,6 @@ export class DOMController extends React.Component<ControllerProps, void> {
this.props.wrapperElement.classList.toggle(CSS.RMX_VISIBLE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I've asked this in every review and haven't seen any comments/changes. Maybe the comments got lost in the GitHub UI?

Why are you mutating the DOM of an element this component doesn't control? Couldn't this just be:

toggleVisibility() {
  this.setState({
    isVisible: !this.props.isVisible,
  }})
}

render() {
  const {
    isVisible,
    variables,
    updateVariable,
  } = this.props;

  if (this.props.isVisible) {
    return (
      // your DOM tree
    )
  } else {
    return <div />;
  }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we chatted about this. Its part of #12. Just never made that change.

@chriscox
Copy link
Member Author

@appsforartists Let me know if this looks ok now. There's just the overlay toggle issue, but as we've discussed that will be handled in a separate issue per #12.

@appsforartists
Copy link
Member

I didn't realize you wanted to handle that separately; seemed small in relation to the other changes that have been made in this review. 😝

@chriscox
Copy link
Member Author

Awesome thanks! Yeah #12 gradually grew smaller and smaller as changes went on. But I was just trying best not to mix issues so could handle that separately.

Thanks again for amazing exhaustive review! Fantastic feedback.

@chriscox chriscox merged commit 5dbbc93 into develop Nov 21, 2016
@chriscox chriscox deleted the feature/components branch November 21, 2016 22:43
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants