Gathering detailed insights and metrics for manifold-dx
Gathering detailed insights and metrics for manifold-dx
Gathering detailed insights and metrics for manifold-dx
Gathering detailed insights and metrics for manifold-dx
manifold-dx-redirect-dx
This library provides a manifold-dx state-based mechanism for routing within an application that uses React Router, specifically its `<Redirect>` component. NOTE: we are assuming one RedirectDx per application!
manifold-dx-latest
Simplified state management, based on new approaches to providing unidirectional predictability, and a great development experience with mutation checking, type safety from TypeScript, and 'time-travel'.
npm install manifold-dx
Typescript
Module System
Node Version
NPM Version
TypeScript (96.54%)
JavaScript (3.46%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
3 Stars
324 Commits
3 Watchers
19 Branches
1 Contributors
Updated on Nov 15, 2021
Latest Version
1.1.33
Package Id
manifold-dx@1.1.33
Unpacked Size
708.05 kB
Size
335.27 kB
File Count
91
NPM Version
6.14.12
Node Version
14.16.1
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
3
4
The goal here is to provide a quick and easy developer experience, using Flux mechanics. This TypeScript framework relies on developers to define application state based on interfaces, whose type information is then used to infer action ID's, action objects, action creators, and reducers. So the only boilerplate you're writing are the strongly typed definitions that comes with any React project in TypeScript.
Where Redux's implementation is based on functional programming and immutability, manifold-dx uses TypeScript to define strongly typed immutable structure, then uses generics and type inference to provide out-of-the-box API's for action creation, predefined reducers, dispatching, mapping state to components, dead simple middleware, and much more.
Since manifold-dx uses strongly typed nestable state objects, the actual shape of the application state can be whatever you want it to be, or whatever the problem requires. A deeply nested hierarchy doesn't present any problem, and component updates are performed efficiently.
As with Redux, be sure you actually need to use this. Big standalone SPA's often need libraries to manage app state, others often do not.
Let's say we have used TypeScript interfaces to define application state that looks like this:
Now suppose we want to update the user's given_name, from 'Joe' to 'Joseph' (the property on the top right).
So to make all this happen, you simply call API's that manifold-dx provides for you:
Just to reiterate, you didn't have to write anything, these API's are provided by the library:
getActionCreator
builds and invokes an action creator for youupdate
defines the action according to what you put in (code completion and type-checking courtesy of TypeScript)dispatch
updates application state and the UITypeScript Generics are a powerful feature, and we take full advantage of them, without requiring developers to know much about them. We can write type-safe, generic updates, that enforce valid property names and value types.
Strongly Typed Data Structures
_parent
is the node that contains this one, or null if it the topmost node (application state)_myPropname
is what my parent calls me, or an empty string if the topmost node (application state)1let user: UserState = { 2 // properties of the StateObject 3 // parent in the state application graph 4 _parent: userMaintenance, 5 // parent's name for this object, ie this === this._parent[this._myPropname] 6 _myPropname: 'user', 7 // raw data properties 8 given_name: '', 9 family_name: '', 10 email: '', 11 UserState: '', 12 cell: '' 13}
In other words, the only thing a developer has to do is to add two properties to the nodes of their
application state. This makes it easy for us to generate the property path ('userMaintenance.user.given_name"
),
given only the node in the application state.
Container Class Templates Because you probably don't want to write Container classes from scratch all the time...
npm i manifold-dx
This is actually a more general question that applies to writing any UI, and it seems that the hard part is that there are a million ways to do it. I'll outline here what has worked well, along with some helper interfaces that make sure that the objects we build agree with the interfaces we have defined.
getStateObject(getAppStore().getState()?.uiLayout?.modal)
,
where the function will throw if the state object is undefined (action creators can use optional chaining too).
Serialization application state can be de/serialized using a library called JSOG, that extends JSON serialization to handle cyclic graphs.
1export interface AppData { 2 userMaintenance?: UserMaintenanceState; 3 cognito?: AppCognitoState; 4} 5 6export interface AppState extends AppData, State<null> { } 7 8export interface UserMaintenanceState extends UserMaintenance, State<AppState> { 9 user?: GroupUserState; 10 open: boolean; 11} 12 13export interface GroupUserState extends GroupUser, State<UserMaintenanceState> { } 14 15export interface AppCognitoState extends AppCognitoState, State<AppState> { } // phases of cognito login and person
1export class AppStateCreator { 2 appState: AppState; 3 4 constructor() { 5 this.appState = { 6 _parent: null, 7 _myPropname: '', 8 }; 9 this.appState.cognito = { 10 _parent: this.appState, 11 _myPropname: 'cognito', 12 groups: [] 13 }; 14 this.appState.userMaintenance = { 15 _myPropname: 'userMaintenance', 16 _parent: this.appState, 17 groups: [], 18 user_in_groups: [], 19 users: [], 20 }; 21 this.appState.userMaintenance.user = { 22 _parent: this.appState.userMaintenance, 23 _myPropname: 'user', 24 family_name: '', 25 given_name: '', 26 email: '', 27 cell: '', 28 UserStatus: '', 29 open: false 30 }; 31 } 32}
1export class AppStore extends Store<AppState> { 2 3 constructor(_appData: AppState, _configOptions: StateConfigOptions) { 4 super(_appData, _configOptions); 5 // process.env[`REACT_APP_STATE_MUTATION_CHECKING`] = 'true'; 6 let strictMode: boolean = process.env.REACT_APP_STATE_MUTATION_CHECKING ? 7 process.env.REACT_APP_STATE_MUTATION_CHECKING === 'true' : 8 false; 9 let detection = this.getManager().getActionProcessorAPI().isMutationCheckingEnabled(); 10 console.log(`strictMode = ${strictMode}, mutation detection=${detection}`); 11 } 12} 13 14let appStore = new AppStore(new AppStateCreator().appState, {}); 15 16export const getAppStore = (): AppStore => appStore;
How to access application state: optional chaining using the getStateObject
method.
Since application state is dynamic, it is generally declared to be optional or unioned with 'undefined'. So, optional chaining can be used to get state objects.
However, after the app is initialized, app state objects have usually been created, and verifying that they are actually defined is cumbersome. In other words, after your app is initialized, state has usually been created, and your code usually assumes it exists (and it usually does).
So manifold-dx supplies api's that expect that state objects have been created, are declared as such (non-optional, never undefined), failing fast by throwing an error.
This allows you to use optional chaining to access state objects deterministically.
1// access app state using optional chaining 2const message: string = getStateObject(getAppStore().getState()?.uiLayout?.modal).message; // throws if uiLayour or modal is undefined 3// or use it when creating actions 4getActionCreator(getAppStore().getState()?.uiLayout?.modal).set('message', 'Your updates have been saved').dispatch(); // throws if uiLayout isundefined
1getActionCreator(getAppStore().getState()?.uiLayout).set('redirectTo', SceneUrl.MY_GROUP).dispatch();
In manifold-dx, components are lightweight classes that invoke renderers (usually functions) and create mappings between application state and renderers.
Both of these classes require the developer to write two functions:
appendToMappingActions(mappingActions: AnyMappingAction[]): void;
This is how we define the relationship
between state and the renderer's properties, so that when an action changes state, the renderer's props are updated and the component re-renders.createViewProps(): VP;
is the function that initializes the view properties used by the renderer.Here is a simple example of mapping state (a property called 'message') to a component a view property called 'alertMessage':
1export class Alert extends RenderPropsComponent<AlertProps, AlertViewProps, AppState> { 2 3 constructor(_props: AlertProps) { 4 super(_props, getAppStore().getState()); 5 } 6 7 protected appendToMappingActions(mappingActions: AnyMappingAction[]): void { 8 mappingActions.push( 9 getMappingActionCreator(getAppStore().getState()?.uiLayout?.modal, 'message').createPropertyMappingAction(this, 'alertMessage') 10 ); 11 } 12 13 createViewProps(): AlertViewProps { 14 let alertMessage = getStateObject(getAppStore().getState()?.uiLayout?.modal).message || ''; 15 return { 16 alertMessage, 17 handleClickClose: handleClickClose 18 }; 19 } 20}
You may have noticed above, where the action contains both the old and the new value. This allows actions to be 'unapplied', like a database transaction log, allowing us to do time-travel.
Mutation Checking, will throw errors if state is mutated by anything other than actions (careful - development only!). This is controlled by the environment variable REACT_APP_STATE_MUTATION_CHECKING.
Simple, Powerful Middleware - optional developer-provided functions can be invoked at various times in the lifecycle. i.e., before reducers (state changes) or after components are updated (or both).
Middleware Lifecycle:
getActionCreator(stateObject).set('modalMessage', 'You cannot use the Admin UI');
store.dispatch(action1, action2, ...actionN);
getAppStore().getManager().getActionProcessorAPI().appendPreProcessor(myPreProessor);
scoreAppendAction.actionPostReducer = () => { /* recalculate average score here */ }
actions.push( bowlingMapper.createPropertyMappingAction(this, 'scores', this.calcAverage.bind(this)) );
ActionLoggingObject interface to log actions before they change anything (or after)
1 let logging: string[] = []; 2 let loggerObject: ActionLoggingObject = actionLogging(logging, false); 3 getAppStore().getManager().getActionProcessorAPI().appendPreProcessor(loggerObject.processor);
Action Type Guards are provided as convenience methods, since all actions pass through Processor middleware, where you often want to find specific kinds of actions.
There are a lot of things you might want to do, like performing transforms on data that are state dependent,
or like below, using action.isStatePropChange
to validate whether the user can perform specific actions.
Note that if you need to you can replace the inbound actions with whatever other actions may be needed.
1import { userIsAdmin } from '../auth'; // your app defines this 2import { createStore } from '../store'; // your app defines this 3const store = createStore(); 4const actionValidator: ActionProcessorFunctionType = // actions: Action[] => Action[] 5 actions => { 6 const stateObject = getStateObject(store.getState()); 7 for(let i = 0; i < actions.length; i++) { 8 const action = actions[i]; 9 if (action.isStatePropChange() && action.parent === stateObject && 10 action.propertyName === 'redirectTo' && action.value === '/admin/secret/ui' && !userIsAdmin()) { 11 const replacementAction = getActionCreator(stateObject).set('modalMessage', 'You cannot use the Admin UI'); 12 return [replacementAction]; 13 } 14 } 15 return actions; 16 }; 17store.getManager().getActionProcessorAPI().appendPreProcessor(actionValidator);
Obviously Redux has been our frame of reference, but Vuex should be mentioned as it influenced this design in a couple of ways:
export const appStore = new AppStore(new AppStateCreator().appState, {});
Also note, a coincidental similarity with Vuex is a somewhat nested/compositional approach to state, as opposed to Redux's preferred 'flat' shape.
To Run Tests: npm test --runInBand REACT_APP_STATE_MUTATION_CHECKING=true
runInBand
since we need to have tests execute in orderEnhancing usability, optional chaining
Generic type-safe accessor
const name = getStateObject(store.getAppState()?.name);
Keeping up to date with recent TypeScript and React releases
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
Found 0/30 approved changesets -- score normalized to 0
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no SAST tool detected
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
26 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-07-07
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More