Skip to content

React Wrapper Step 6: Reduxify App Lab#7148

Merged
islemaster merged 36 commits into
stagingfrom
reduxify-applab
Mar 14, 2016
Merged

React Wrapper Step 6: Reduxify App Lab#7148
islemaster merged 36 commits into
stagingfrom
reduxify-applab

Conversation

@islemaster

Copy link
Copy Markdown
Contributor

Gives App Lab a redux store and starts using it to drive rendering of the React frame.

New Redux Actions

There are only three of them in this first pass:

  1. setInitialLevelProps is the least exciting of the three. It gets called once during init to push a bunch of properties into the store, which makes them easy to use from any component without writing much glue code. Did cleanup as I went to ensure that the store is the single source of truth for these values after initialization.
  2. changeMode switches between Code Mode and Design Mode, making redux the single source of truth for which mode we are currently in. This let me stop passing the startInDesignMode property to React, which shouldn't care about initial state in particular. The non-React code that depends on this value now manually subscribes to redux to be notified when this value changes. I think it's centralized things nicely.
  3. changeScreen which changes the focused/active screen id in design mode. Similarly depends on a manual subscription to redux to respond appropriately to the change in state. Candidate future work: Reduxify addScreen and deleteScreen.

Next steps

  • We're very close to making DesignWorkspace a React-descendant of AppLabView and not needing to render it manually anymore. Unfortunately, it's rendering inside #codeWorkspace which is one of our ProtectedStatefulDivs because all sorts of non-React stuff hangs off of it. It's going to be tricky to do a clean separation of concerns there.

Step 6 from this design document.

Introduces the 'envify' transform into our apps build.
When MOOC_DEV=0 (as in npm run build:dist) we add the envify transform to our browserifyinc command and set NODE_ENV to "production".  This replaces any instance of process.env.NODE_ENV in our own code with the string "production" which allows us to set some conditional behavior according to the enviornment.  Uglify then removes now-dead code paths in minified production builds.

In this first case, we check the environment to build a redux store with no middleware for production builds, but introduce middleware for logging state changes and connecting to the Redux Chrome DevTools in debug builds.
Comment thread apps/src/ReduxCDO.js Outdated
*/
module.exports = function createStoreCDO(reducer, initialState) {
if (process.env.NODE_ENV !== "production") {
var createLogger = require('redux-logger');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I prefer require's to always be at the top of the file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did this one in particular inline, because we want to remove it in production builds.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh, that makes sense. Could we not still have it within a conditional at the top of the file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure thing.

@Bjvanminnen

Copy link
Copy Markdown
Contributor

I'm pretty pumped to start using redux here :)

Comment thread apps/src/applab/actions.js Outdated
*
* @returns {{type: ActionType, props: Object}}
*/
module.exports.setLevelProps = function (props) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we call this setInitialLevelProps to be a little more specific/clear here?

Comment thread apps/src/applab/applab.js Outdated
// have levelHtml stored due to a previous bug. HTML set by levelbuilder
// is stored in startHtml, not levelHtml.
if (config.level.hideDesignMode) {
if (Applab.reduxStore.getState().isDesignModeHidden) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Made a comment about this elsewhere as well, but I think it should be a goal to directly access the store as little as possible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why? As long as we're not mutating it, isn't it better to access the store than to duplicate state?

I can see the argument for hiding access to the store behind helper methods where we need to do this - and it will be inevitable in some places since not all of our code can ever be absorbed into React.

I see what you mean about this as a code smell though. If I'm introducing too many of these in one PR, I may be refactoring poorly, moving the wrong state into redux.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think that one reason is that when we're using redux in a connected component, we know that the store and the UI will always be in sync. Store changes cause a rerender (if it changes props we care about), and our UI is updated. In cases like this, someone might perform an action that results in a change to isDesignModeHidden, but we're not guaranteed to rerender this UI.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm. Two thoughts.

  • In this case we are both in the init method, and the "state" we're examining is a level property that's supposed to stay the same throughout the session. For something like the level properties which are effectively a bundle of constants, would it be better to freeze it and stick it on a global than to put it in redux?
  • The other cases you're commented on do actually refer to changing state (currentScreenId in particular) and I agree that depending on the store in a non-React context takes some care. I don't think it's actively harmful though - all of our non-React view code has to decide for itself when to re-render, even before implementing redux. If anything it's easier now because we can subscribe to the store if we're worried about missing an update - something you can't do when depending on the DOM for state, as we do in some of our App Lab code.

onModeChange(state.mode);
}
});
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One reason that having a bunch of subscribers that update UI vs. having a single root component that subscribes/connects is that it's not much less predictable what order things happen in.

@islemaster

Copy link
Copy Markdown
Contributor Author

Extracted two changes that can happen before this PR:

@islemaster

Copy link
Copy Markdown
Contributor Author

@Bjvanminnen I think I've hit a stopping point with this PR. Final thoughts?

Comment thread apps/src/applab/applab.js
isViewDataButtonHidden: !!config.level.hideViewDataButton
}));

Applab.reduxStore.dispatch(changeInterfaceMode(Applab.startInDesignMode() ? ApplabInterfaceMode.DESIGN : ApplabInterfaceMode.CODE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good name :) ("interface mode")

@Bjvanminnen

Copy link
Copy Markdown
Contributor

Added a couple more minor comments, but generally lgtm :) Look forward to having redux :) :)

islemaster added a commit that referenced this pull request Mar 14, 2016
React Wrapper Step 6: Reduxify App Lab
@islemaster islemaster merged commit 120208e into staging Mar 14, 2016
@islemaster islemaster deleted the reduxify-applab branch March 14, 2016 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants