Comments (6)
@wallzero https://github.com/ui-router/react/releases/tag/0.8.10 sorry for the wait
from react.
Unless anyone objects, I'll work on a PR to migrate @uirouter/react
to use hooks and test for compatibility with StrictMode
.
from react.
So far I was able to update the UIRouter
component and it works:
/**
* @reactapi
* @module components
*/ /** */
import * as React from 'react';
import { FunctionComponent, ReactElement, ValidationMap } from 'react';
import * as PropTypes from 'prop-types';
import { servicesPlugin } from '@uirouter/core';
import { UIRouterReact, ReactStateDeclaration } from '../index';
export const {
/** @internalapi */
Provider: UIRouterProvider,
/**
* <UIRouterConsumer> component lets you access the UIRouter instance
* anywhere in the component tree, by simply wrapping your component and
* using the function-as-child pattern to pass the instance via props.
*
* #### Example:
* ```jsx
* <UIRouterConsumer>
* {router => <MyComponent router={router} />}
* </UIRouterConsumer>
* ```
*/
Consumer: UIRouterConsumer,
} = React.createContext<UIRouterReact>(undefined);
export interface UIRouterProps {
children?: any;
plugins?: any[]; // should fix type
states?: ReactStateDeclaration[];
config?: (router: UIRouterReact) => void;
router?: UIRouterReact;
}
/** @hidden */
export const InstanceOrPluginsMissingError = new Error(`Router instance or plugins missing.
You must either provide a location plugin via the plugins prop:
<UIRouter plugins={[pushStateLocationPlugin]} states={[···]}>
<UIView />
</UIRouter>
or initialize the router yourself and pass the instance via props:
const router = new UIRouterReact();
router.plugin(pushStateLocationPlugin);
···
<UIRouter router={router}>
<UIView />
</UIRouter>
`);
/** @hidden */
export const UIRouterInstanceUndefinedError = new Error(
`UIRouter instance is undefined. Did you forget to include the <UIRouter> as root component?`
);
const UIRouter: FunctionComponent<UIRouterProps> = ({
plugins,
states,
config,
children,
router: propRouter,
}: UIRouterProps): ReactElement => {
const router = React.useRef<UIRouterReact>();
const [, forceUpdate] = React.useState();
React.useEffect((): void => {
// check if a router instance is provided
if (router) {
router.current = propRouter;
} else if (plugins) {
router.current = new UIRouterReact();
router.current.plugin(servicesPlugin);
plugins.forEach(plugin => router.current.plugin(plugin));
if (config) config(router.current);
(states || []).forEach(state => router.current.stateRegistry.register(state));
} else {
throw InstanceOrPluginsMissingError;
}
router.current.start();
forceUpdate({});
}, []);
return router.current ? <UIRouterProvider value={router.current}>{children}</UIRouterProvider> : null;
};
UIRouter.propTypes = {
plugins: PropTypes.arrayOf(PropTypes.func),
states: PropTypes.arrayOf(PropTypes.object),
config: PropTypes.func,
children: PropTypes.element.isRequired,
router: PropTypes.any,
} as ValidationMap<UIRouterProps>;
export { UIRouter };
I then tried updating UIView
but am stuck. I don't receive any errors but it isn't rendering:
/**
* @reactapi
* @module components
*/ /** */
import * as React from 'react';
import {
ClassType,
ClassicComponentClass,
Component,
ComponentClass,
FunctionComponent,
ReactChildren,
ReactElement,
StatelessComponent,
ValidationMap,
Validator,
cloneElement,
createElement,
isValidElement,
} from 'react';
import * as PropTypes from 'prop-types';
import { ActiveUIView, ViewContext, Transition, ResolveContext, StateParams, applyPairs } from '@uirouter/core';
import { UIRouterReact, UIRouterConsumer } from '../index';
import { ReactViewConfig } from '../reactViews';
import { UIRouterInstanceUndefinedError } from './UIRouter';
/** @internalapi */
let id = 0;
/** @internalapi */
export interface UIViewAddress {
context: ViewContext;
fqn: string;
}
/**
* Interface for [[InjectedProps.resolves]]
*
* This Typescript interface shows what fields are available on the `resolves` field.
*/
export interface UIViewResolves {
/**
* Any key/value pair defined by a state's resolve
*
* If a state defines any [[ReactStateDeclaration.resolve]]s, they will be found on this object.
*/
[key: string]: any;
/**
* The `StateParams` for the `Transition` that activated the component
*
* This is an alias for:
* ```js
* let $stateParams = $transition$.params("to");
* ```
*/
$stateParams: StateParams;
/** The `Transition` that activated the component */
$transition$: Transition;
}
/**
* Function type for [[UIViewProps.render]]
*
* If the `render` function prop is provided, the `UIView` will use it instead of rendering the component by itself.
* @internalapi
*/
export type RenderPropCallback = (
Component: StatelessComponent<any> | ComponentClass<any> | ClassicComponentClass<any>,
Props: any
) => JSX.Element | null;
export interface UIViewInjectedProps {
transition?: Transition;
resolves?: UIViewResolves;
className?: string;
style?: Object;
}
/** Component Props for `UIView` */
export interface UIViewProps {
children?: ReactChildren;
router?: UIRouterReact;
parentUIView?: UIViewAddress;
name?: string;
className?: string;
style?: Object;
render?: RenderPropCallback;
}
/** Component State for `UIView` */
export interface UIViewState {
id?: number;
loaded?: boolean;
component?: string | FunctionComponent<any> | ClassType<any, any, any> | ComponentClass<any>;
props?: any;
}
export const TransitionPropCollisionError = new Error(
'`transition` cannot be used as resolve token. ' +
'Please rename your resolve to avoid conflicts with the router transition.'
);
/** @internalapi */
export const { Provider: UIViewProvider, Consumer: UIViewConsumer } = React.createContext<UIViewAddress>(undefined);
const View: FunctionComponent<UIViewProps> = ({
children,
router,
parentUIView,
name = '$default',
className,
style,
render,
}: UIViewProps): ReactElement => {
// This object contains all the metadata for this UIView
const uiViewData = React.useRef<ActiveUIView>();
// This object contains only the state context which created this <UIView/> component
// and the UIView's fully qualified name. This object is made available to children via `context`
const uiViewAddress = React.useRef<UIViewAddress>();
// Deregisters the UIView when it is unmounted
const deregister = React.useRef<Function>();
// Bind the rendered component instance in order to call its uiCanExit hook
const componentInstance = React.useRef<any>();
// Removes th Hook when the UIView is unmounted
const removeHook = React.useRef<Function>();
const [state, setState] = React.useState<{
id?: number;
loaded?: boolean;
component?: string | FunctionComponent<any> | ClassType<any, any, any> | ComponentClass<any>;
props?: any;
}>({
loaded: false,
component: 'div',
props: {},
});
/**
* View config updated callback
*
* This is called by UI-Router during ViewService.sync().
* The `newConfig` parameter will contain view configuration (component, etc) when a
* state is activated and one of its views targets this `UIView`.
*/
const viewConfigUpdated = (newConfig: ReactViewConfig) => {
if (newConfig === uiViewData.current.config) {
return;
}
let trans: Transition;
let resolves = {};
if (newConfig) {
let viewContext: ViewContext = newConfig.viewDecl && newConfig.viewDecl.$context;
uiViewAddress.current = {
fqn: uiViewAddress.current.fqn,
context: viewContext,
};
let resolveContext = new ResolveContext(newConfig.path);
let injector = resolveContext.injector();
let stringTokens: string[] = resolveContext.getTokens().filter(x => typeof x === 'string');
if (stringTokens.indexOf('transition') !== -1) {
throw TransitionPropCollisionError;
}
trans = injector.get(Transition);
resolves = stringTokens.map(token => [token, injector.get(token)]).reduce(applyPairs, {});
}
uiViewData.current.config = newConfig;
const key = Date.now();
let props = { ...resolves, transition: trans, key };
let newComponent = newConfig && newConfig.viewDecl && newConfig.viewDecl.component;
setState({
...state,
component: newComponent || 'div',
props: newComponent ? props : {},
loaded: !!newComponent,
});
};
const registerUiCanExitHook = (stateName: string) => {
typeof removeHook.current === 'function' && removeHook.current();
let criteria = { exiting: stateName };
let callback =
componentInstance.current &&
typeof componentInstance.current.uiCanExit === 'function' &&
componentInstance.current.uiCanExit;
if (stateName && callback) {
let transitions = router.transitionService;
removeHook.current = transitions.onBefore(criteria, callback, {});
}
};
React.useEffect((): (() => void) => {
if (typeof router === 'undefined') {
throw UIRouterInstanceUndefinedError;
}
// Check the context for the parent UIView's fqn and State
let parent: UIViewAddress = parentUIView;
// Not found in context, this is a root UIView
parent = parent || { fqn: '', context: router.stateRegistry.root() };
uiViewData.current = {
$type: 'react',
id: ++id,
name: name,
fqn: parent.fqn ? parent.fqn + '.' + name : name,
creationContext: parent.context,
configUpdated: viewConfigUpdated,
config: undefined,
} as ActiveUIView;
uiViewAddress.current = { fqn: uiViewData.current.fqn, context: undefined };
deregister.current = router.viewService.registerUIView(uiViewData.current);
setState({
...state,
id: uiViewData.current.id,
});
return (): void => {
deregister.current();
};
}, []);
let { component, props, loaded } = state;
// register reference of child component
// register new hook right after component has been rendered
let stateName: string = uiViewAddress.current && uiViewAddress.current.context && uiViewAddress.current.context.name;
// only class components can implement the
// uiCanExit hook and ref doesn't work on
// stateless function components
if (typeof component !== 'string' && (!!component.render || (component.prototype && !!component.prototype.render))) {
props.ref = c => {
componentInstance.current = c;
registerUiCanExitHook(stateName);
};
}
// attach any style or className to the rendered component
// specified on the UIView itself
const styleProps = { className, style };
const childProps = { ...props, ...styleProps };
let child =
!loaded && isValidElement(children) ? cloneElement(children, childProps) : createElement(component, childProps);
// if a render function is passed use that,
// otherwise render the component normally
const ChildOrRenderFunction = typeof render !== 'undefined' && loaded ? render(component, childProps) : child;
return <UIViewProvider value={uiViewAddress.current}>{ChildOrRenderFunction}</UIViewProvider>;
};
View.propTypes = {
router: PropTypes.object.isRequired as Validator<UIRouterReact>,
parentUIView: PropTypes.object as Validator<UIViewAddress>,
name: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
render: PropTypes.func,
} as ValidationMap<UIViewProps>;
export class UIView extends React.Component<UIViewProps, any> {
static displayName = 'UIView';
static __internalViewComponent: React.FunctionComponent<UIViewProps> = View;
render() {
return (
<UIRouterConsumer>
{router => (
<UIViewConsumer>
{parentUIView => <View {...this.props} router={router} parentUIView={parentUIView} />}
</UIViewConsumer>
)}
</UIRouterConsumer>
);
}
}
Here's my branch with recent changes.
from react.
Refactoring with hooks wasn't necessary to resolve the StrictMode
issues.
from react.
Thanks for doing this work! I'll merge your PR when it's rebased against the latest dependency updates
from react.
Hey @christopherthielen I am just curious when the next release is scheduled? I have a couple merges myself which are waiting on #512 and #430. Thanks!
from react.
Related Issues (20)
- Can ui-router/react support React18 new features? HOT 6
- testing ui router with relative paths
- {location: 'replace'} doesn't work in stateService.go(stateName, params, {location: 'replace'}) HOT 1
- useSref: "target="_blank" ignored when link contains child elements HOT 1
- Is this project still under active development? HOT 3
- Use same UIRouter instance inside different React.render(...) HOT 1
- Typescript demo errors on `1.0.1` HOT 1
- animating-transitions demo errors on `1.0.1` HOT 1
- Router.start() called more than once HOT 1
- 404 on react-dom.js on the tutorial pages HOT 2
- Path duplication after page refresh when using UIRouter pushStateLocationPlugin HOT 4
- Params of type json are not deserialized properly HOT 5
- States not gets enabled if there are nested lazy loaded states HOT 2
- UIView Doesn't Render with Usage of `urlService.rules.when` HOT 4
- Scroll restoration HOT 3
- Cannot use useRouter HOT 4
- Jest/Enzyme Transition Rejection/TypeError HOT 1
- UISref Component ignores target="_blank" on child anchor tag HOT 3
- Mocking UiRouter Classes with Jest HOT 4
- useCurrentStateAndParams don't reacting on initial state HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react.