New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Does v6 drop support for class components? #8146
Comments
I think that they are aware of this, and willing to do their best to make the transition as smooth as possible, check this tweet: https://twitter.com/mjackson/status/1448107013741989888?s=20 |
Hooks are the preferred API nowadays and are the main API surface in v6. The easiest way to get access to them is the same as any other hook, just wrap your class component with a functional component that passes along hook values as props: const WrappedComponent = props => {
const history = useHistory()
const location = useLocation()
return <Component history={history} location={location} {...props} />
} You can also use more generic HOCs, like |
@timdorr Thanks. So will BTW, "preferred" is relative to who you ask, though. It may be popular with several groups of developers but many other developers that come from backgrounds where classes are heavily used (e.g. .NET, Java, iOS development, etc.) are still working with and prefer class components. I see the value in using hooks in some scenarios but it's not a silver bullet and class components are a better approach in several cases. |
I don't know if it will be available in v6. Likely it will be provided in some sort of support package to aid with the transition to the v6 APIs ( |
We are absolutely not dropping support for class-based components, @AbrahamLopez10, but I think the API changes you pointed out do tend to illustrate some of the value of hooks. For example, in a world without hooks it makes a lot of sense to use render props instead of a hook. This is because the router knows some state and it needs a way to expose it to you so it can pass it through to your component, so a render prop makes it easy to do so. <Route
path="posts/:id"
render={({ match }) => <BlogPost id={match.params.id} />}
/>
class BlogPost extends React.Component {
render() {
let { id } = this.props;
// ...
}
} However, in a world with hooks, we don't need a render prop for exposing state. Instead, you can just use the hook in your component directly. <Route path="posts/:id" element={<BlogPost />} />
function BlogPost() {
let { id } = useParams();
// ...
} In addition to eliminating the need for render props for sharing state, hooks also eliminate a lot of other prop-passing boilerplate code in other components that are rendered by class BlogPost extends React.Component {
render() {
return (
<div>
<PostHeader id={this.props.id} />
{/* ... */}
</div>
);
}
}
class PostHeader extends React.Component {
render() {
let { id } = this.props;
// ...
}
} But with hooks, it's as simple as: function BlogPost() {
return (
<div>
<PostHeader />
{/* ... */}
</div>
);
}
function PostHeader() {
let { id } = useParams();
// ...
} Anyway, that's my sales pitch for using hooks. I totally understand if you don't want to use them, but I think you could probably eliminate some of the boilerplate from your code if you did. As for shipping a For example, you could make your own // in hocs.js
function withNavigation(Component) {
return props => <Component {...props} navigate={useNavigate()} />;
}
function withParams(Component) {
return props => <Component {...props} params={useParams()} />;
}
// in BlogPost.js
class BlogPost extends React.Component {
render() {
let { id } = this.props.params;
// ...
}
}
export default withParams(BlogPost); Hopefully that helps you understand what we are thinking with v6. It's a low-level library and the hooks are a nice low-level abstraction that should allow you to build whatever you can think of and use whatever API you're most comfortable with, even if you decide to stick with class components. |
@mjackson Many thanks for the detailed clarification and the specific examples you provided, they were really helpful to clear my doubts. I think if this info could be included in the v6 documentation to give peace of mind and direction to other developers who want or need to stick with class components it would be awesome so no one is left confused or discouraged by the hooks-centric approach in v6 😄 |
It is easy to type it in javascript. something like this ?
|
For some of us it's a matter of transitioning a code base that had used class to use hooks and it all can't be transitioned at once. We need the support for classes until we can remove them in favor of all hooks. This is common for a multitude of code bases. |
if you ever get stuck import React,{ Component} from "react";
import { useNavigate } from "react-router-dom";
export const withNavigation = (Component : Component) => {
return props => <Component {...props} navigate={useNavigate()} />;
}
//classComponent
class LoginPage extends React.Component{
submitHandler =(e) =>{
//successful login
this.props.navigate('/dashboard');
}
}
export default withNavigation(LoginPage); |
Is there a javascript alternative for this? |
Ah thanks for this, took plenty of time on some strong words until finally finding this and getting react router to work. While it might sound. |
Official dropped, but we the people save ourselves: |
@paztis... this works for me export const withRouter = (Component: React.ComponentType<any>) => {
const WithRouter = (props: any) => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return <Component {...props} location={location} navigate={navigate} params={params} />;
}
return WithRouter;
} |
Any is not a solution in typescript. WithRouter is supposed to support the generics, as before |
@MDGFUKAS You may use my package directly. @paztis You may create a pull request to my package to enhance the generic of it. |
Here is a typesafe solution that works with HOCs. Let me know if there are any issues. import React, { ComponentType } from "react";
import {
NavigateFunction,
Params,
useLocation,
useNavigate,
useParams,
Location,
} from "react-router-dom";
interface RouterProps {
navigate: NavigateFunction;
readonly params: Params<string>;
location: Location;
}
export type WithRouterProps<T> = T & RouterProps;
type OmitRouter<T> = Omit<T, keyof RouterProps>;
export function withRouter<T>(
Component: ComponentType<OmitRouter<T> & RouterProps>
) {
return (props: OmitRouter<T>) => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return (
<Component
location={location}
navigate={navigate}
params={params}
{...props}
/>
);
};
}
/*************
Usage
*************/
interface TestProps {
howdy: boolean;
partner: boolean;
}
interface TestState {
howdyPartner: string;
}
class TestComponent extends React.Component<
WithRouterProps<TestProps>,
TestState
> {
state = { howdyPartner: "" };
render() {
return <h1>{this.props.location.pathname}</h1>;
}
}
export default withRouter<TestProps>(TestComponent); |
import React from 'react';
import {
BrowserRouter,
Outlet,
Route,
Routes,
useLocation,
useNavigate,
useParams
} from "react-router-dom";
import swal from '@sweetalert/with-react';
import {toast, ToastContainer} from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css';
import {AxiosResponse} from "axios";
import WpAdmin from "src/components/WpAdmin/WpAdmin";
export function startInputChangeUpdateRoutine(
shouldContinueAfterTimeout: () => boolean,
cb: () => void,
timeoutMs: number = 3000): () => void {
const timer = () => setTimeout(() => {
if (false === shouldContinueAfterTimeout()) {
return;
}
cb();
}, timeoutMs);
const timerId = timer();
return () => {
clearTimeout(timerId);
};
}
interface iBootstrapProperties extends iWebsocketState {
children?: React.ReactNode | React.ReactNode[] | undefined;
}
interface iBootstrapState {
id: string,
axios: import("axios").AxiosInstance,
authenticateURI: string,
authenticated?: boolean,
alert?: boolean
}
interface iBootStrapPublicFunctions {
authenticate: () => void,
changeLoggedInStatus: () => void,
codeBlock: (markdown: String, highlight: String, language: String, dark: boolean) => any,
passPropertiesAndRender: (Component: React.ElementType, props?: object) => any,
semaphoreLock: Function,
switchDarkAndLightTheme: () => void,
testRestfulPostPutDeleteResponse: (response: AxiosResponse, success: Function | string | null, error: Function | string | null) => boolean,
}
export type iAmBootstrapDescendant = iBootstrapProperties & iBootstrapState & iBootStrapPublicFunctions;
function withRouter(Component: React.ComponentType<any>) {
// noinspection UnnecessaryLocalVariableJS
const WithRouter = (props: any) => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return <Component {...props} location={location} navigate={navigate} params={params} />;
}
return WithRouter;
}
class Bootstrap extends React.Component<iBootstrapProperties, iBootstrapState> implements iBootStrapPublicFunctions {
constructor(props) {
super(props);
const contextAxios = context.axios;
contextAxios.interceptors.request.use(
req => {
if (req.method === 'get' && req.url.match(/^\/rest\/.*$/)) {
req.params = JSON.stringify(req.params)
}
return req;
},
error => {
return Promise.reject(error);
}
);
contextAxios.interceptors.response.use(
response => {
// Do something with response data
console.log(
"Every Axios response is logged in Bootstrap.tsx :: ",
response
);
if (response?.data?.alert) {
console.log("alert ∈ response");
this.handleResponseCodes(response);
return (response?.data?.alert?.error || response?.data?.alert?.danger) ?
Promise.reject(response) :
response;
}
return response;
},
async error => {
/* Do something with response error
this changes from project to project depending on how your server uses response codes.
when you can control all errors universally from a single api, return Promise.reject(error);
is the way to go.
*/
this.handleResponseCodes(error.response);
console.log("Carbon Axios Caught A Response Error response :: ", error.response);
return Promise.reject(error); // return error.response;
});
this.state = {
id: '',
axios: context.axios,
authenticateURI: '/authenticated/',
authenticated: null,
alert: false
};
}
passPropertiesAndRender = (Component: React.ComponentType<any>, props: object = {}) => {
const RenderWithRouteProps = withRouter(Component)
return <RenderWithRouteProps
authenticate={this.authenticate}
changeLoggedInStatus={this.changeLoggedInStatus}
codeBlock={this.codeBlock}
semaphoreLock={this.semaphoreLock}
passPropertiesAndRender={this.passPropertiesAndRender}
setCurrentUserObject={this.setCurrentUserObject}
switchDarkAndLightTheme={this.switchDarkAndLightTheme}
testRestfulPostPutDeleteResponse={this.testRestfulPostPutDeleteResponse}
updateUser={this.updateUser}
{...this.props}
{...this.state}
{...props} >
<Outlet/>
</RenderWithRouteProps>
}
testRestfulPostPutDeleteResponse = (response: AxiosResponse, success: Function | string | null, error: Function | string | null): boolean => {
if (undefined !== response?.data?.rest
&& (('created' in response.data.rest)
|| ('updated' in response.data.rest)
|| ('deleted' in response.data.rest))
) {
if (typeof success === 'function') {
return success(response);
}
if (success === null || typeof success === 'string') {
toast.success(success, this.state.toastOptions);
}
return response.data.rest?.created ?? response.data.rest?.updated ?? response.data.rest?.deleted ?? true;
}
if (typeof error === 'function') {
return error(response);
}
if (error === null || typeof error === 'string') {
toast.success(error, this.state.toastOptions);
}
return false;
};
render() {
console.log("BOOTSTRAP TSX RENDER");
const {alert} = this.state
const ppr = this.passPropertiesAndRender;
return <BrowserRouter>
<Routes>
<Route path='/*' element={ppr(DropInGaming, {})}/>
<Route path='wp-admin/*' element={ppr(WpAdmin, {})}/>
</Routes>
{alert}
</BrowserRouter>
}
}
// @ts-ignore
export default Bootstrap; ^^ full call impl |
Thank you for this. In Class B: |
how to use route props in render like: |
Although its said support for Class Components isn't being dropped, the fact that you have to wrap your class in a HOC in order to get it to work doesn't support that statement. |
@Johnne25 |
Works great, even with dynamic components: // Routes
import MovieForm from "./components/movieForm";
import ElementWrapper from "./components/elementWrapper";
<Route path="/movies/new" element={<ElementWrapper routeElement={MovieForm} />} /> // Wrapper
import React from "react";
import { useLocation, useParams } from "react-router-dom";
const ElementWrapper = (props) => {
const params = useParams();
const locations = useLocation();
const Element = props.routeElement;
return <Element params={params} locations={locations} {...props} />;
};
export default ElementWrapper; |
// I have this code but i couldn't update it i wrapped the // shop Component with WithRoute it didn't work import React, { Component } from "react"; const mapStateToProps = (dataStore) => ({ const mapDispatchToProps = { const filterProducts = (products = [], category) => (!category || category === "All") ? products : products.filter(p => export const ShopConnector = connect(mapStateToProps, mapDispatchToProps)( class extends Component { componentDidMount() { this.props.loadData(DataTypes.CATEGORIES); this.props.loadData(DataTypes.PRODUCTS); |
To be clear:
|
First of all, thank you for making react-router available to the world :)
I'm not sure if this is the best place to ask about this but given that this is monitored by the maintainers I figured I could put my question here.
My question is around the fact that based on the currently available documentation for v6 (the migration/upgrade guide) react-router v6 seems to drop out-of-the-box support for class components and all the codebases that already have plenty of them (like ours) due to the following changes:
Dropping support for the
render
prop in theRoute
component, which we are using with react-router v5 to pass params to the component (throughprops.match.params
) of the route.Replacing the use of
History
(which was passed as a prop to our components automatically in v5) with the newNavigateFunction
and the fact that it's only available through theuseNavigate
hook which of course is not accessible to class components.Could someone shed the light on this and the future of react-router for all of us who are using class components and will most likely stick with using them for the foreseeable future?
Thanks in advance.
The text was updated successfully, but these errors were encountered: