Repository URL to install this package:
|
Version:
1.1.16 ▾
|
/**
* @file this file is for extending this class
* @see ./connectToData for how this class is connected to a component further
*
* @description @tip use "[command] + [left-mouse-click]" to open up the type definitions!
*/
import { EMPTY_OBJ, EMPTY_ARRAY, isFunction, isArray, isObj } from 'exotic'
import { ApolloQueryResult, ApolloClient, DocumentNode } from 'typings'
import { graphql } from 'react-apollo'
import {
observable,
computed,
action,
extendObservable,
extendShallowObservable,
} from 'xmobx/mobx'
import { observer, inject } from 'xmobx/mobx-react'
// ours
import oneStorage from 'modules/persistance/local-storage'
import oneCookie from 'modules/persistance/cookies'
import oneRouter from 'modules/router'
// import OmniStore from 'state/OmniStore'
import connectToDevtools from 'modules/devtools/remotedevtools'
import { Request as OneRequest } from 'modules/oneRequest'
// local
import { tapProps } from './deps'
import { withStore } from './connectToData'
import registry from './containerRegistry'
/**
* @description mutates to fix
* @see autofixSafe
* @see modules/state-tree
*/
function mutateTypesToFixMobx(types) {
const autofixBuiltIn = property => {
const type = types[property]
if (type === Array) {
types[property] = []
}
}
Object.keys(types).forEach(autofixBuiltIn)
}
const intellisense = {
get OmniStore() {
return require('state/OmniStore').OmniStoreX
},
get oneRouter() {
return oneRouter
},
get OneRequest() {
return OneRequest
},
}
/**
*
* @description when the view mounts or gets new data
* we can handle things going through
* @event componentWillMount
* @event componentWillReceiveProps
*
*
* @alias interceptProps
* @param {React.Props} props container.oneStore
*
* @return {React.Props} decorated props available to the component we are wrapping
*
* @note if the store has any handle props function
* call it, so it can change the props
*
* by default, we use tapProps which adds routing props
* and the omnistore
*/
function handleProps(props) {
return tapProps(this, props)
}
/**
* !!!!!
* @TODO there should be many classes extending this these
* these are observable observable container, only 1 instance like singleton pattern
* but only 1 `oneState` 1 `oneRouter` ever
* !!!!!
*
* @tutorial https://mobx.js.org/refguide/map.html
* @extends BaseChain
* @see modules/oneRequest
* @param {Mobx.observable} store
*/
class ObservableContainer {
// could take in debug name in props...
constructor() {
this.named = this.constructor.debugName || this.constructor.name
if (process.env.NODE_ENV !== 'production') {
registry.set(this.named, this)
}
}
get containers() {
return registry
}
// this helps put it at the bottom of our inspector in dev tools
get oneRouter(): typeof intellisense.oneRouter {
return oneRouter
}
get oneCookie() {
return oneCookie
}
get oneStorage() {
// .cookies
// .ls
return oneStorage
}
// :-(
get oneRequest(): typeof intellisense.OneRequest {
return OneRequest
}
/**
* graphqlyo
* @todo
*/
get isLoading(): boolean {
// this.extendObservable({
// dynamicState: {
// isLoading: false,
// },
// })
return this.dynamicState.isLoading
}
set isLoading(isLoading: boolean) {
if (isObj(this.dynamicState) === false) {
this.dynamicState = observable.object()
}
this.dynamicState.isLoading = isLoading
}
// query<T>(options: WatchQueryOptions): Promise<ApolloQueryResult<T>>;
// mutate<T>(options: MutationOptions<T>): Promise<FetchResult<T>>;
// subscribe(options: SubscriptionOptions): Observable<any>;
// readQuery<T>(options: DataProxy.Query): T | null;
// readFragment<T>(options: DataProxy.Fragment): T | null;
// writeQuery(options: DataProxy.WriteQueryOptions): void;
// writeFragment(options: DataProxy.WriteFragmentOptions): void;
// gql = {
// // CRUD
// query: 'QueryHere',
// // dynamic
// // variables: {},
// }
dynamicState: {
isLoading: boolean,
// yagniu
observableQuery: {},
previousData: {},
}
// === not really needed
get previousQueryResult() {
return (
this.dynamicState.previousData ||
this.dynamicState.observableQuery.getLastResult()
)
}
get queryResult() {
return this.dynamicState.observableQuery.currentResult()
}
// variables,
// pollInterval,
// query,
// fetchPolicy,
// errorPolicy,
// notifyOnNetworkStatusChange,
// this.dynamicState.observableQuery = client.watchQuery(this.queryOptions)
// this.dynamicState.observableQueryResult = this.dynamicState.observableQuery.currentResult()
Query: DocumentNode
Mutate: DocumentNode
QueryPropsMapper: Function = props => props
// @private
apollo(response, queryOptions) {
const data = response.data
// @perf @todo
this.dynamicState.response = response
this.dynamicState.isLoading = response.loading
this.dynamicState.queryOptions = queryOptions
return data
}
// private
_SERVER_SIDE_RENDERING_DO_NOT_USE_ME_OR_ELSE_DOT_DOT_DOT() {
if (this.ClientOptions && this.ClientOptions.mutation) {
return this.mutate()
} else {
return this.query()
}
}
// this first arg is not used........
async mutate(mutate, configOrVariables = undefined) {
// @todo make sure dynamicState updates
const queryOptions =
configOrVariables ||
this.ClientOptions ||
this.dynamicState.queryOptions ||
configOrVariables
const response = await this.client.mutate({ mutate, queryOptions })
return this.apollo(response, queryOptions)
}
// @michael, first arg is not used...
async query(query, configOrVariables = EMPTY_OBJ) {
// this lets you pass in `variables` or the whole config
const { Query } = this
const defaultQueryArgs = {
query: Query,
// variables: configOrVariables,
// !!! options.variables !!!
options: configOrVariables,
}
const queryOptions =
configOrVariables ||
this.ClientOptions ||
configOrVariables.options ||
configOrVariables.query
? configOrVariables
: defaultQueryArgs
if (configOrVariables === EMPTY_OBJ) {
return Promise.resolve(EMPTY_OBJ)
}
const response: ApolloQueryResult<Query> = await this.client.query({
query,
queryOptions,
})
return this.apollo(response, queryOptions)
}
/**
* @protected
* @description because of the way transpiling works,
* we cannot use static types in a decorator
* and we cannot use class properties,
* because they get auto-added to constructor + js prototype inheritance
* but this removes the need for importing observable +
*
* @param {Object} types normal javascript object or any data to make observable as properties
* @return {ObservableContainer} @chainable
*/
extendObservable(types = EMPTY_OBJ) {
mutateTypesToFixMobx(types)
// console.dev(this.named)
extendObservable(this, { ...types })
// this.connectToDevtools()
return this
}
extendShallowObservable(types = EMPTY_OBJ) {
mutateTypesToFixMobx(types)
// extendShallowObservable(this, types)
extendShallowObservable(this, { ...types })
return this
}
// -------
/**
* @protected
* @type {handlePropsType}
* @see handlePropsType
* @override this function to handle props easily
* @param {*} props
*/
_handleProps(props, ref) {
// console.info('DEFAULT_HANDLEPROPS', props)
Object.defineProperty(this, 'props', {
enumerable: false,
configurable: true,
// writable: true,
get() {
return props
},
})
Object.defineProperty(this, 'context', {
enumerable: false,
configurable: true,
// writable: true,
get() {
return ref.context
},
})
// this.props = props
// this.context = ref.context
if (isFunction(this.handleProps) === true) {
return this.handleProps(props)
} else {
return handleProps.call(this, props)
}
}
/**
* @protected
* @listens componentWillUnmount
* @param {*} props
*/
handleRemove() {
//
}
/**
* @protected
* @listens componentWillReceiveProps
* @param {*} props
*/
handleUpdate(props) {
//
}
// --- helper methods ---
// --- can put state tree types here too
/**
* @public
* @param {*} data
* @return {observable}
*/
observable(data) {
// observable(data) {
return observable(data)
}
/**
* @public
* @param {ReactComponent} Target
* @return {Observer}
*/
observer(Target) {
// observer(Target) {
return observer(Target)
}
/**
* @public
* @tutorial https://reactjs.org/docs/higher-order-components.html
* @type {Function.Decorator}
* @param {React.Component} Target
* @return {React.Component} obserable/subscribed component connected to ^ store
*/
connectToData = (Target): withStore => {
// return compose(this.injectContext, withStore(this))(Target)
return withStore(this, Target)
}
connectToGraph = (Target): graphql => {
return withStore(this, Target)
}
// ------
// === not needed...
setHandleProps(fn: Function): ObservableContainer {
// setHandleProps(fn = Function) {
this.handleProps = fn
return this
}
// @tutorial https://github.com/mobxjs/mobx-react#customizing-inject
// @TODO spread relational stores with this function
// @see ./deps/injectContext
injectContext(builder) {
/**
* @todo withErrorBoundary
*/
return builder
}
}
export { ObservableContainer, registry }
export default ObservableContainer