create-react-cr-project
v1.3.10
Published
A program to create react project scaffolding with Visual Studio Code integration
Downloads
8
Maintainers
Readme
Create-react-cr-project with vscode is the best environment for developing with react.
Create-react-cr-project (CRPCR) is an optimal UI-based vscode-react development/auto test/production environment. It supports all the modern webpack features out of the box and significantly simplifies the task of implementing a react solution, as well as reducing the amount of code that must be written.
Basic Project Type Support
- React alone (JS or Typescript)
- React/redux (JS or Typescript)
- React/simpler-redux (JS or Typescript)
- React/causality-redux (JS or Typescript)
- React Reason exportable components with an underlying React Project (Exports to JS or Typescript)
(Please note, create-react-cr-project is only compatible with node 8.1.3 or higher.)
Installation
- Go to a command window and create a directory for your project and change to that directory.
npm init
and hit enter through all the questions.npm install create-react-cr-project
- Depending on your system, run
node_modules\.bin\create-react-cr-project
ornode_modules/.bin/create-react-cr-project
. The installation will take a few minutes. Once npm install has been run for you, the project configuration UI will be displayed. This UI allows you to configure your project and also create react components. Select your project configuration options and save. Then you can also create react components. - Open your project directory in Visual Studio Code.
- Make sure you have vscode-chrome-debug and vscode-eslint extension installed.
- If you are using react reason the install the following vscode extension. vscode-react-reason.
Known Issues
- The react reason vscode extension is supposed to automatically transpile .re files after saving them. However, at times it does not especially with new files. So, for this case, pull up the task list and select
Run bsb
to manutally run the bucklescript transpiler. - The bucklescript transpiler does not currently produce source maps so you have to set breakpoints in the transpiled code rather than the actual react reason source code.
Updating the IDE
- Go to a command window in your project directory.
npm run updateide
Extensive List Of Features
- Significantly less coding and debugging than with react alone or with react and redux.
- Intellisense (code completion) for external libraries via Automatic Type Acquisition (ATA)
- Debugging react ES2015 classes inside vscode via vscode-chrome-debug extension
- Easy access to install, build, test and debugging commands via vscode command palette and keyboard shortcuts
- Basic Babel ES6 (does not include babel-polyfill), dynamic import, Object Rest/Spread Properties, fetch, promises, async/await, Object.assign, maps, sets and Array.from. This list can be easily extended.
- Supports the javascript standard code styling with eslint.
- JSX code analysis (linting) with autofixing support via vscode-eslint extension
- Supports typescript.
- Supports the typescript standard code styling with tslint.
- React Hot Loading and general HMR support for all code.
- Major extensions and simplifications to redux.
- Clean separation between business logic and react components. No entangling program state and business code with the UI. Allows react components to be pure UI components. This way when react becomes obsolute, you simply take it out and install a new UI without having to rewrite any program state code or business code.
- The ability to build independent react web components with the react UI part of the component being free of business logic.
- Support for the react router and redux store synchronization with your routes such that any route component will retain its redux store values given route changes and/or browser forward or back movements.
- React loadable support (code splitting).
- React/enzyme auto test environment with vscode debugging.
- Dll library support for debugging for much faster debug compilation.
- Dll library support for production. Users do not have to reload react code for example, each time they visit your site. Ideally, they need only reload your code when it changes.
- Css inline, modules and legacy support. Also support for less, sass, scss and css. Supports the postcss-loader for vendor css prefixes.
- urlLoader support for your images, fonts etc. Inlines them in the production bundle for faster loading if they are below a threshhold. Also, they can be imported into your react components.
- Minification build bundles for both css and js for production use.
- Allows you to view your production product after a build.
- Supports progressive web apps for production. You can opt out of this.
- Supports material-ui react components.
- Supports apollo-client.
- Supports development and production proxies so that you can proxy REST requests to your production server or some stub server such as the json-server.
- Add your own javacript code to the UI create component function to create the source code for your own custom components.
Visual Studio Code
- Download
- Tips and Tricks
- Supercharge your JavaScript debugging workflow with Visual Studio Code (Build 2017)
Required Extensions
To install extensions in vscode, click the extensions button to the very left. Then use the search box at the top to find the extension to install.
- Debugger for Chrome
- Eslint
- Tslint for Visual Studio Code (For typescript projects only)
- If you are using react reason, vscode-react-reason.
Recommended Extensions
Visual Studio Code Shortcuts
- F5 to start debugging
- Set up a keyboard shortcut CTRL+SHIFT+T to display the tasks you can run listed below.
- Build Production - Builds your production bundles and index.html.
- Builddll Development - Builds your development dlls for code like react to speed up debug loading and compilation.
- Builddll Development & Production - Builds your development dlls and production dlls.
Adding React Components
- In vscode, run the task 'Run Project Configuration'. You may also do it from the command line as npm run config.
- Click 'CREATE REACT COMPONENT' and the create react component form will be displayed. Type in the component name in the React component name field and select from the various options and then click 'CREATE COMPONENT' at the top right. Once the component is created click 'EXIT' at the top right and then click the 'EXIT' button at the home screen to properly perform exit cleanup.
Debugging
- Select the debug button to the left in vscode and select "Debug" from the drop down. Then push the green arrow button next to the dropdown.
Production Build
- (Outside vscode) - npm run build.
- (Inside vscode) - Display the task list and select "Build Production".
Run Production Code
- Select the debug button to the left in vscode and select "Production" from the drop down. Then push the green arrow button next to the dropdown. This will run your production code in the browser using a simple express server. (Note: running the production code does not perform a production build.)
Build Dlls
Each time your imported modules change like react, a builddll must be performed.
- Build development dlls - Use this if you do not use dlls in your production build.
- (Outside vscode) - npm run builddll:dev.
- See the keyboard shortcuts for building inside vscode.
- Build development & production dlls - Use this if you use dlls in your production build.
- (Outside vscode) - npm run builddll.
- (Inside vscode) - See the keybaord shortcuts for building inside vscode.
React/Enzyme Test Run and Debugging
- (Outside vscode) - npm run test.
- (Inside vscode) - Select the debug button to the left in vscode and select "Run Mocha Tests" from the debug drop down. Then push the green arrow button next to the dropdown.
Template Configuration
All configuration settings are well documented in the UI configuration program. However, below is a few important notes.
- You must decide whether to use dlls in your production build. If so, a Builddll Development & Production listed above in keyboard shortcuts must be performed.
- If you update your import modules you must also do a builddll for development and/or production.
License
MIT
Documentation
How to Generate the Source for Your Own Components From the IDE
Under devtools/componentplugins
create some directory. The name of that directory will appear in the create component list of the IDE. In that directory create index.js and have at least below for the contents.
const buildComponent = config => {
}
// Must export buildComponent for any plugin.
module.exports = buildComponent
The config
parameter will contain information like componentName
etc. Return true if the component does not exist otherwise, return false. Then of course, provide the js code to build the component in the appropriate directory.
To see examples, all of the source for the components generated by the IDE are located in the directory ./node_modules/create-react-cr-project/lib/componentplugins
.
Creating a CRPCR MVC Component
- Open the project in vscode and then open the vscode task list.
- Select the task 'Run Project Configuration'. You may also do it from the command line as npm run config.
- Click 'CREATE REACT COMPONENT' and the create react component form will be displayed. Type in the component name in the React component name field and select from the various options and then click 'CREATE COMPONENT' at the top right. Once the component is created click 'EXIT' at the top right and then click the 'EXIT' button at the home screen to properly perform exit cleanup.
MVC Component Redux Store Partition
When a CRPCR MVC component is initialized, CRPCR creates a key for the component in the redux store as the camel case of the component name concatenated with 'Partition'. Hence, the state for the component Counter is located in the redux store under the key 'counterPartition'. This methodology prevents variable name collisions and also implements a separation of concerns for your component's state.
Defining Your MVC Component's State
You are not forced to hassle with action creators and reducers with CRPCR, so all you need to do is place your state variables with their initializers in the object defaultState which is located in the MVC controller file. Every variable listed in defaultState will automatically be made available in the props of the UI component with no actions required on your part other than listing the variables. So, you also do not need to call a redux connect function nor do you need to write a mapStateToProps function. See further below if you only want to include a subset of your defaultState variables in the component props.
Note that defining state variables is fully compatabile with HMR. Therefore, while in the debugger you may add new variables and their initializers and after a save, these new variables will be available in the props of the UI component. Therefore, a restart is not required in this case. However, changing an initial value does require a restart.
So, open the file component name/controller.js and locate the object defaultState. Below is an example.
export const defaultState = {
// Place your component state here
data: [],
counter: 0
}
Defining Your MVC Component's UI Service Functions
The UI service functions for your component's UI features are defined in the controller object uiServiceFunctions. Any function you define in this object is automatically made available in the props of the UI component.
Note that defining and changing UI service functions is fully compatabile with HMR. Therefore, while in the debugger you may add or change these functions and after a save, the changes become effective for your program without requiring a restart.
So, open the file component name/controller.js and locate the object uiServiceFunctions. Below is an example. See further below on how to access and change your state variables in defaultState from your UI service functions.
export const uiServiceFunctions = {
// Place your UI service functions here
increment: () => {
},
pushData: (e) => {
}
}
Defining Your MVC Component's React UI
Except for special cases, your react component should be a dumb react stateless functional component. So, CRPCR creates the scaffolding for such a component in component name/view.jsx. All of your state defined in the controller under defaultState and all of your UI service functions listed in the controller under uiServiceFunctions are automatically made available in the props so all you need to do is list the applicable keys that you require for UI presentation.
As consistent with MVC, the view is only responsible for UI presentation and nothing more. Hence, you should not write any javascript in the react component except for the facilitation of UI presentation. So, there should be no state nor business code in the view file.
The logic of MVC dictates a separatiom of concerns for UI features since it is assumed that the UI will change in the future and certainly the javascript world proves this to be true. So, if you restrict your view file to be pure UI presentation then any future UI changes only requires you to rewrite UI presentation code. If on the other hand you tangle up business code and/or state with the UI then UI changes in the future requires you to trash most of your code causing a complete program rewrite.
All code in the view file is HMR compatable.
So, open the file component name/view.js. Below is an example of a MVC react UI component in its initial created state.
import React from 'react'
const Counter = (/* { sampleKey1, sampleFunction1 } */) =>
<div>
TODO: Define your component.
</div>
export default Counter
Basics Operations Available to the Controller
The generated MVC react component makes available the basic functions to manipulate the redux store partition associated with the component in the file index.js or index.ts. So, you can use the below in the controller to gain access to all of these functions. Note that each MVC component gets their own redux store partition separate from other components.
import { getState, setState, partitionState, subscribe } from './'
- getState() - Gets the current partition state object.
- setState(obj) - Merges obj with the current partition state.
- partitionState - Proxy used to access and change particular keys in the partition state. See the below for an example.
partitionState.key = value
// partitionState.arr returns a shallow copy of the arr in the redux store partition.
const arr partitionState.arr
// Change the local arr
arr.push(1)
// Now set arr in the redux store partition
partitionState.arr = arr
// Note the below will NOT change arr in the redux store.
// This changes a local copy returned by partitionState.arr.
partitionState.arr.push(1)
- subscribe(listener(obj), [key1, key2, ...]) - The listener is called if and only if any of the keys in the second argument array are changed. Only those changed keys/value pairs are passed in as an object to the listener.
Also, causality-redux is an extension to redux so you still have access to all the redux primitives with causalityRedux.store. So for example, the below gets the entire redux state object.
const state = causalityRedux.store.getState()
How to Access and Change State in a React MVC Component
There are two ways to access a component's redux state, getState and partitionState. There are two ways to change a component's redux state, setState and partitionState. The below gives examples.
// Sample controller file.
export const defaultState = {
data: [],
counter: 0
}
// The below demonstrates all the methods available to access and update partition state fields listed in the defaultState object.
export const uiServiceFunctions = {
increment: () => {
// Updates counter in the redux store partition.
partitionState.counter++
// Get the entire state object for this component
let x = getState()
// Get the value of counter from getState
let c = getState().counter
// Get the value of counter from partitionState
c = partitionState.counter
// update counter with setState
setState({counter: c + 1})
// update counter with partitionState
partitionState.counter = c + 1
},
pushData: (e) => {
// partitionState returns a shallow copy of the component's state object field such as 'data' below.
let arr = partitionState.data
arr.push(e)
// To change an object in the state object, use partitionState.
partitionState.data = arr
// Note that the below does not work. partitionState.data returns a shallow copy of the object at the key 'data'. So the push occurs on that local copy and not the redux state object.
partitionState.data.push(e)
}
}
How to perform controller initialization that requires partitionState, getState or setState.
In the component controller file, add a function that performs the initialization code. See below for an example.
// Put in some initial comments.
export function initController () {
if (getState().items.length === 0) {
const initialComments = [
{ author: 'Cory Brown', text: 'My 2 scents' },
{ author: 'Jared Anderson', text: 'Let me put it this way. You`ve heard of Socrates? Aristotle? Plato? Morons!' },
{ author: 'Matt Poulson', text: 'It`s just a function!' },
{ author: 'Bruce Campbell', text: 'Fish in a tree? How can that be?' }
]
initialComments.forEach(comment => uiServiceFunctions.onAddComment(comment))
}
}
Then in the component index file, simply call initController at the end. See below.
export { commentBoxPartition, partitionState, setState, getState }
export default wrappedComponents.CommentBox
// Perform controller initialization here that needs partitionState, setState or getState.
initController()
How to Change State in an MVC Component from an External Module
If you need to change a component's state based on some changes that happen elsewhere in the app, define an exported function in the target Component as below. This may be needed for example as data comes in from a webSocket. It is recommended that only the owner of the state partition performs the actual changes on that component's state.
export const externalServiceFunctions = {
get counter () {
return partitionState.counter
},
set counter (val) {
partitionState.counter = val
}
}
Then import and export externalServiceFunctions in the component index file. Then simply import the function into the code that needs to change the component's state.
import { externalServiceFunctions } from '../Component2'
externalServiceFunctions.counter = 2
How to Access State in an MVC Component from an External Module
Assume you have a login component and you need to access some user features in that component from another module. In this case, the target controller should return a shallow copy of the the required property. Now, keep in mind that the copy may contain properties that contain objects. This would allow the requesting module to alter those embedded objects which would not be picked by the create-react-cr-project monitoring component. So, you may want to consider returning a deep copy of the object. However, if the object to be copied is complex then the deep copy operation can be very slow. In any event, these are the tradeoffs to consider. See the example below.
// Component2 controller. The object externalServiceFunctions must be
// imported into index and then exported also.
export const externalServiceFunctions = {
get userOption1 () {
return partitionState.user.option1
}
}
// Component1 controller.
import { externalServiceFunctions } from '../Component2'
if (externalServiceFunctions.userOption1) {
}
How to include only a Subset of defaultState in the Props of the View Component
There will be times that your component state includes information that will not be displayed to the user such as caches. Any changes to these state items however, will cause a component render by default. So, you can list just those state keys that you want passed into the props as below.
// Controller file
export const defaultState = {
counter: 0,
cache: []
}
// In the index file, add storeKeys as below. Then only counter will be in the props of the view component and not cache.
// However, all keys in defaultState will still be in the redux store partition.
const { partitionStore, partitionState, getState, setState, subscribe, wrappedComponents } = establishControllerConnections({
module,
partition: { partitionName: counterPartition, defaultState, uiServiceFunctions },
storeKeys: ['counter']
uiComponent: Counter,
uiComponentName: 'Counter'
})
How to Include Controller Functions and/or State from One MVC Component into Another MVC Component
You can include in any MVC component state and/or functions from the controller of any other MVC component. This is done using the controllerUIConnections key of the input object to establishControllerConnections. See the below for an example.
// Index file of the including component.
import { counterFormPartition } from '../CounterForm'
import { commentBoxPartition } from '../CommentForm'
import { defaultState, uiServiceFunctions } from './controller'
import MultiPartitionForm from './view'
const controllerUIConnections = [
{
uiComponent: MultiPartitionForm, // React Component to wrap with redux connect
// Use an array of objects to attach multiple partitions to the component's props
[
// The entry below is from this partition.
{ partitionName: multiFormPartition, storeKeys: ['fixedValue'] },
// Include the increment function and counter state variable from
// the counterFormPartition component.
{ partitionName: counterFormPartition, changerKeys: ['increment'], storeKeys: ['counter'] },
// Include items from the commentBoxPartition component.
{ partitionName: commentBoxPartition, storeKeys: ['items'] }
],
uiComponentName: 'MultiPartitionForm' // Name of the react component string form
}
]
// Then perform the below to replace the default establishControllerConnections in the index file.
const { wrappedComponents } = establishControllerConnections({
module,
partition: { partitionName: multiFormPartition, defaultState, uiServiceFunctions },
controllerUIConnections
})
Some points need to be made about the above code.
- A partition definition is not required for the component. Therefore, a component can be summary only with no state of its own. That means the partition key would not exist for establishControllerConnections.
- In order to include all store keys from a partition then storeKeys must be undefined. To exclude all store keys, set storeKeys=[].
- In order to include all controller functions from a partition then changerKeys must be undefined. To exclude all controller functions, set changerKeys=[].
How to Access Store Values from Other Modules for Calculations in Another
In the module that needs access to the store values of another module, see the below example.
import { getState as configureGetState } from '../Configure'
const uiServiceFunctions = {
myCalc: () => {
const configureObj = configureGetState()
// Use the configureObj values here.
const myNeededValue = configureObj.otherModuleValue
}
}
How to Access UI Service Functions for Unit Testing
The partitionStore object that is exported in the component index file contains all of the UI service functions. So, in your component test code you can do something like the below.
import { partitionStore } from './'
// Then you can call any controller function as below.
partitionStore.increment()
Alternatively, you can import the controller object uiServiceFunctions directly from the controller as below.
import { uiServiceFunctions } from './controller'
// Then you can call any controller function as below.
uiServiceFunctions.increment()
How to Handle Side Effects of HMR in the MVC Component
There may be times that your component adds an event listener. Then given a HMR, the listener will be attached to the event more than once. So, you can define a function that is called before a HMR to remove the event listener. Use the below to handle this situation.
// Controller
export const hotDisposeHandler = () => {
// Remove event listener
}
// Index file. Add hotDisposeHandler to the input object of establishControllerConnections.
import { defaultState, uiServiceFunctions, hotDisposeHandler } from './controller'
const { partitionStore, partitionState, getState, setState, subscribe, wrappedComponents } = establishControllerConnections({
module,
partition: { partitionName: counterPartition, defaultState, uiServiceFunctions },
hotDisposeHandler,
uiComponent: Counter,
uiComponentName: 'Counter'
})
Global Store Partition
Causality-redux provides a global store partition by default in create-react-cr-project. See the below for an example on how to use this global store partition.
import {globalPartitionState} from '../causality-redux/init'
globalPartitionState.myValue = 10
Note however, you must add your global variables to the globalData object in causality-redux/init. So for example, to use the above the globalData might look like the below.
const globalData = {
injectTapEventPlugin: false,
myValue: 0
}
How to Extend the Language with Additional Polyfills
To add a new language polyfill, simply install the library that you need and then import it into src/bootstrap/libs. Also list the additional library using the configuration program in the dll library section so that debug compilation is faster.
How to Integrate CausalityRedux with Existing Redux Code.
CausalityRedux is capatible with redux in the same project and only requires a few lines of code. This way you can upgrade to CausalityRedux and still leave your existing working redux code base in place.
import causalityRedux from 'causality-redux'
// Create the redux store as normal. These two steps below must be done before any causality-redux react components are imported.
const rStore = createStore(combineReducers(yourCoreReducersObject), hydrateState);
// Call causalityRedux.setReduxStore as below.
causalityRedux.setReduxStore(rStore, yourCoreReducersObject);
If your hydrateState contains CausalityRedux state then do the below instead.
// Create the redux store.
const rStore = createStore(CausalityRedux.combineReducers(yourCoreReducersObject), hydrateState);
// Call CausalityRedux.setReduxStore as below.
causalityRedux.setReduxStore(rStore, yourCoreReducersObject, hydrateState);
If you use redux code splitting or some type of lazy load module based logic for reducers then perform the step below with your additional reducers that are to be added. This will add the additional reducers to the existing redux reducers. Note that this is the reducer object itself and not the combineReducers output.
causalityRedux.addReducers(additionalReducersObject)
How to Perform Code Splitting and Dynamic Loading of React-Causality-Redux MVC Components
By default, create-react-cr-project supports code splitting with webpack and also supports dynamic importing. In addition by design, react-causality-redux MVC components are independent units that can be loaded at anytime as long as there is an environment in place such as the libraries react, causality-redux and react-causality-redux. When these MVC components are loaded, they configure themselves in the redux store dynamically and them boot themselves into viable react components. Therefore, you do not have to write any type of code to transition from statically built components to dynamically imported components. So, they just plain work.
See this link for information on code splitting with react by using the react router.
https://reactjs.org/docs/code-splitting.html
How to Support HMR with Code Splitting and Dynamic Loading of React-Causality-Redux MVC Components
You can look for some of the solutions available but, an easy one to tomplement is to simply statically load the components when in development mode. Otherwise, in production mode use react Loadable. Below is an example.
import Loadable from 'react-loadable'
export const counter1Route = '/counter1'
export const Counter1 = process.env.NODE_ENV === 'production'
? Loadable({ loader: () => import('../react-components/Counter1'), loading: Loader })
: require('../react-components/Counter1').default
State Monitoring and Debugging
create-react-cr-project provides the most advanced state change monitoring available. First, download the StateMonitor react component from here and follow the installation instructions. Also, download this vscode extension and follow the installation instructions.
The StateMonitor component records all state changes initiated by causality-redux. To use it, run the vscode debugger. The monitor is automatically displayed to the right hand side of the screen. Then, to see what code causes a particular state change, click on the target state change in the monitor. Go back to to vscode and perform the following steps.
- Pull up the command palette.
- Select the react-causality-redux load file entry in the list.
- If the call stack only contains one entry for the state change then vscode pulls up that particular file with the specific line highlighted that caused the change.
- If the call stack contains multiple files that caused the state change then vscode pulls up a selection list for you to choose from. Select a particular file and vscode will display that file and highlight the line. As long as you do not change the contents of the clipboard you can continue to pull up the command palette and select 'react-causality-redux load file' and then select the file in the list such that you can display each source file and line number that lead to the state change. This allows you to completely understand the code and/or find the bug in your code that caused an incorrect change in state.
Redux Counter Sample
To show how awesome this project creator is, a sample redux counter demo will be implemented below which will also contain mocha test files.
Download the create-react-cr-project project files into a directory named counterdemo. Run the command below from the command line in the counterdemo directory.
npm run createproject
That will perform a npm install and after that will pull up a UI configuration screen. Then follow the below instructions.
- Click the 'CONFIGURE REACT PROJECT' button.
- The defaults will work for this example, so then click 'SAVE CONFIGURATION' at the top right. This will take a few minutes and will display a success message.
- Click OK for the message and then click the 'EXIT' button at the top right.
- Next click 'CREATE REACT COMPONENT'.
- Type in the component name as Counter and then click 'CREATE COMPONENT' at the top right.
- Click OK for the success message and then click 'EXIT'.
- Finally, click the 'EXIT' button on the home screen to properly exit the UI.
The above creates a valid HMR react project with all the standard files in place. Also, under the src/react-components/Counter directory, the scaffolding for the Counter component has been created.
Open the file src/react-components/Counter/view.jsx in vscode and replace the existing jsx code with the code below. It is simply a stateless counter react component. The ids will be used for mocha testing later.
const Counter = ({ counter, increment }) =>
<div>
<p id='countertext'>{`The current counter is ${counter}.`}</p>
<button id='onIncrement' onClick={increment}>Increment</button>
</div>
Now we need to handle counter and increment in the controller. So, open the file src/react-components/Counter/controller.js.
Put the following import at the top of the controller file for the business logic inc.
import inc from './model'
Next, under the TODO: for defaultState, replace with the below. This defines the shape of the redux partition 'counterPartition' for this component.
export const defaultState = {
counter: 0
}
Now, the UI service function “increment” must be implemented. The role of a controller function is either to update redux partition values based on react UI interactions such as inputing data into a text field or to call into the business code for results and then set values in the redux partition based on those results. So, set the object uiServiceFunctions to the below. This sends in the current redux partition value counter from counterPartition into the business function inc. After the function returns, redux partition counter is changed to the incremented value. Based on this change, causality-redux automatically causes the react component Counter to render with the new value of counter set in the props.
export const uiServiceFunctions = {
increment: () =>
(partitionState.counter = inc(partitionState.counter))
}
The proxy partitionState is supplied automatically by the controller function establishControllerConnections called below in the controller file. It allows changing values by assignment in the redux partition 'counterPartition' and also returns copies of values from that partition.
Now we only need to implement the business code for the inc function. So, open the file src/react-components/Counter/model.js and add the business code below which performs the increment function.
export default function inc (val) {
return val + 1
}
Finally, we need to include the Counter component in the react tree. Open the file src/react-components/MainApp/ MainApp.js and make the file as follows.
import React from 'react'
import Counter from '../Counter'
const MainApp = () =>
<Counter />
export default MainApp
MainApp is always the root of the react tree with create-react-cr-project and it inherits all the necessary providers based on the project configuration. Now, click the vscode debug button at the top left and click the green arrow to begin the debug session. Then you will see your counter app in the chrome window and can verify that it works properly.
Next, while still in the debugger open the file src/react-components/Counter/model.js and change the return value of inc to val + 10 and save the file. Notice hot re-loading reloads the updated module and now your react component will increment by 10 without having to recompile and refresh. It also retains the value of counter in the react component. This provides an optimal way of debugging your component without having to lose the current program state.
Make sure that you put the inc function back to val + 1 instead of val + 10 or the tests below will fail.
After you are finished in the debugger, click 'Debug' at the top menu and then 'Stop Debugging'.
Now, we will generate the mocha test code. So, open the file src/react-components/Counter/view.spec.js and replace the code with the following.
import { testCauseAndEffectWithHtmlString } from '../../../../test/projectsetup'
describe('View Counter', function () {
// Click on the increment button
it('increment cause and effect 1 - validated.', function (done) {
testCauseAndEffectWithHtmlString('#onIncrement', '#countertext', 'The current counter is 1.', done)
})
// Click on the increment button
it('increment cause and effect 2 - validated.', function (done) {
testCauseAndEffectWithHtmlString('#onIncrement', '#countertext', 'The current counter is 2.', done)
})
})
The above code will click the button with id onIncrement and then verify that the content at id countertext is correct. Make sure to uncomment the import statement for testCauseAndEffectWithHtmlString.
Next, open the file src/react-components/Counter/controller.spec.js and replace the code with the following.
import assert from 'assert'
import { partitionStore, partitionState } from '../'
describe('Controller Counter', function () {
const numIterations = 10
it('increment - validated.', function () {
// Call the controller function
const val = partitionState.counter
for (let i = 0; i < numIterations; ++i) {
partitionStore.increment()
}
assert(partitionState.counter === val + numIterations)
partitionState.counter = 0
})
})
In MVC, the controller provides functions to the UI and then updates the redux store based on executing those functions. So, the above exercises the controller function increment and then tests the end result of counter in the partition counterPartition in the redux store.
Finally, open the file src/react-components/Counter/model.spec.js and replace the contents with the following code.
import assert from 'assert'
import inc from '../model'
describe('Model Counter', function () {
const numIterations = 1000
it('inc - validated.', function () {
// Call the model function
let val = 0
for (let i = 0; i < numIterations; ++i) {
val = inc(val)
}
assert(val === numIterations)
})
})
Note with this MVC implementation, we are able to bench test just the business code on its own. This provides the opportunity to more rigorously test your business functions.
Now, to run the test code, from the debug side window to the left, pull down the drop down at the top and select 'Run Mocha Tests'. Then click the green arrow and the tests will be executed. Simply, check the results in the debug console at the bottom.
Note that create-react-cr-project does all the grunt work for you in terms of setting up the project such that you need only write javascript business code, react jsx stateless components, design the redux partition for the component with defaultState and finally write javascript controller functions to call the business code and set redux partition values so that the react UI properly renders the updated results. Finally, you can optionally add mocha test code without having to perform any setup. Hence, create-react-cr-project provides an optimal way to write MVC stateless react components.
Simplicity with Programming React Components Using create-react-cr-project
As shown above, to create react components using create-react-cr-project you only need to program these five simple items.
- Define your component state variables in the controller object defaultState.
- Define your component UI service functions in the controller object uiServiceFunctions and external event triggered service functions in externalServiceFunctions.
- Define your business code.
- Define your react component with jsx as a simple stateless component.
- Write your test code.
Demos Featuring this React Project Creator
react-causality-redux-vscode-template
This demo contains examples for all the documentation above including a react router.