Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

kylebuildsstuff
kylebuildsstuff

Posted on

Redux-based routing with Rudy

Why

With the recent release of Redux Toolkit, redux and its related libraries have become easier than ever to integrate into react applications. I've always enjoyed using redux as a state management tool because, despite its verbosity, it always boiled down to simple actions and reducers. I've stuffed many things into redux, for better or for worse, and one of the things I still feel is for the better is routing.

I don't really have any strong arguments as to why one should put routing state in redux rather than components. They both work. I just find it simpler when routing plays nice with redux.

What

Rudy is a redux-based router and the successor toredux-first-router. At its core, it uses redux actions to manage your routing state. Actions are used to change pages, actions are used to bind to urls, actions are used to bind to components, and everything else related to routing. Once set up, this is what creating and using a route looks like inrudy:

// page.slice.tsimport{createAction}from'@reduxjs/toolkit';exportconsttoHelp=createAction('page/toHelp');// home.component.tsimport{useDispatch}from'react-redux';import{toHelp}from'./page.slice';constHome=()=>{constdispatch=useDispatch()return(<div><p>ThisistheHomepage</p><buttononClick={()=>dispatch(toHelp())}>Help</button></div>)}
Enter fullscreen modeExit fullscreen mode

An action creator is created and then it is dispatched in theHome component upon a button click, which will then route the user to theHelp page (this is a simplified example).

Because the routing is based entirely on redux actions, you can dispatch route changes anywhere in your app where would normally dispatch redux actions. This can come in handy not only in components and event handlers but also in redux middleware where you might to dispatch a route change in response to an async action:

// page.epic.tsexportconstredirectToHelpEpic=(action$:ActionsObservable<PayloadAction>,state$:StateObservable<StoreState>,):Observable<PayloadAction>=>action$.pipe(filter(storeLocationUpdateSuccess.match),map((_)=>{returntoHelp();}),);
Enter fullscreen modeExit fullscreen mode

In the above example, I redirect to theHelp page after a successful async update usingredux-observables. The same idea can be applied to other types of middleware likethunks orsagas.

How (part 1)

Every redux app needs to configure a store, and that task has been made easier thanks to the recent addition of Redux toolkit:

// store.tsimport{configureStoreasconfigureReduxStore}from'@reduxjs/toolkit';import{createRouter}from'@respond-framework/rudy';// We'll get to these routes laterimport{urlRoutes,pageReducer}from'modules/page';exportconstconfigureStore=()=>{const{reducer:locationReducer,middleware:routerMiddleware,firstRoute}=createRouter(urlRoutes);conststore=configureReduxStore({reducer:{location:locationReducer,page:pageReducer},middleware:[routerMiddleware,],});return{store,firstRoute};};
Enter fullscreen modeExit fullscreen mode

rudy provides areducer,middleware, andfirstRoute. The first two objects hook into the redux store while thefirstRoute is a little oddity that needs to be dispatched before the app is rendered. Plugging it into your component tree can look like this:

// index.tsxconst{store,firstRoute}=configureStore();functionrender(){ReactDOM.render(<ReduxProviderstore={store}><React.StrictMode><App/></React.StrictMode></ReduxProvider>document.getElementById('root'),);}store.dispatch(firstRoute()).then(()=>render());
Enter fullscreen modeExit fullscreen mode

These steps setuprudy for use in our store. Now we can create our own littleredux slice and configure the actions thatrudy will watch and bind to in order to manage routing.

How (part 2)

Like any otherredux slice, we're going to need actions, reducers, and selectors. But in order to bind our actions to urls and components we're also going to create a couple more mapping objects:

// modules/page/page.slice.tsimport{createSlice,createSelector,createAction}from'@reduxjs/toolkit';// Our action creatorsexportconsttoOrders=createAction('page/toOrders');exportconsttoHelp=createAction('page/toHelp');exportconsttoSettings=createAction('page/toSettings');exportconsttoManagePlan=createAction('page/toManagePlan');exportconsttoNotFound=createAction('page/toNotFound');// Mapping actions to routes (used in rudy initialization)exportconsturlRoutes={[toOrders.toString()]:'/orders',[toHelp.toString()]:'/help',[toSettings.toString()]:'/settings',[toManagePlan.toString()]:'/manage-plan',[toNotFound.toString()]:'/not-found',};// Mapping actions to components (note that the values must match the names of the components)exportconstcomponentRoutes={[toOrders.toString()]:'Orders',[toHelp.toString()]:'Help',[toSettings.toString()]:'Settings',[toManagePlan.toString()]:'ManagePlan',[toNotFound.toString()]:'NotFound',};// An array of all our action types for convenienceexportconstrouteActionTypes=[toOrders.toString(),toHelp.toString(),toSettings.toString(),toManagePlan.toString(),toNotFound.toString(),];// Our redux sliceconstpageSlice=createSlice({name:'page',initialState:{currentPage:componentRoutes[toHelp.toString()],},reducers:{},extraReducers:Object.fromEntries(routeActionTypes.map((routeActionType:string)=>{return[routeActionType,(state:any,action)=>{state.currentPage=componentRoutes[action.type];},];}),),});const{reducer}=pageSlice;exportconstpageReducer=reducer;// selectorsexportconstselectPage=(state:StoreState):PageState=>{returnstate.page;};exportconstselectLocation=(state:StoreState):LocationState=>{returnstate.location;};exportconstselectCurrentPage=createSelector(selectPage,(pageState)=>pageState.currentPage,);
Enter fullscreen modeExit fullscreen mode

And now we're good to go. Our routes are synced and tracked to redux and we can use them in our components like this:

// pages/index.tsexport{Orders}from'./orders';export{PickupAndDelivery}from'./pickup-and-delivery';export{Help}from'./help';export{Settings}from'./settings';export{ManagePlan}from'./manage-plan';export{NotFound}from'./not-found';// app.component.tsximportReactfrom'react';import{useDispatch}from'react-redux';import{DesignSystemProvider,Page}from'@SomeDesignSystem';import{useSelector}from'react-redux';import{selectCurrentPage}from'modules/page';import*aspagesfrom'pages';exportconstApp=()=>{constdispatch=useDispatch();constcurrentPage=useSelector(selectCurrentPage);constComponent=pages[currentPage];return(<DesignSystemProvider><Page><Component/></Page></DesignSystemProvider>);};
Enter fullscreen modeExit fullscreen mode

The current page can be queried for and updated by using actions and selectors from thepage slice we created, and any routing-related data can be found in thelocation reducer provided byrudy, which can also be queried for as any other redux reducer.

Gotchas

You may want to integrate url parameters at some point in your development, and you may find thatrudy andredux don't play nice together out of the box.

You can create actions and routes that use parameters like this:

exportconsttoManageLocation=createAction('page/toManageLocation',functionprepare(locationId:string){return{payload:{locationId,},};});exportconsturlRoutes={[toManageLocation.toString()]:'/manage-location/:locationId',};
Enter fullscreen modeExit fullscreen mode

But Redux toolkit requires actions to have apayload property whilerudy actions use aparams property instead when it comes to parameters. The good news is that it's all redux and it can be fixed with redux tools, namely redux middleware. I worked around this issue by creating a middleware that converts thepayload from routing-specific actions toparams so thatrudy andRTK can play nice together again.

construdyActionPayloadToParamsConverter=(store:any)=>(next:any)=>(action:any)=>{constshouldConvert=routeActionTypes.includes(action?.type)&&!!action?.payload;if(shouldConvert){constnextAction={type:action?.type,params:action?.payload,};returnnext(nextAction);}returnnext(action);};conststore=configureReduxStore({reducer:{...},middleware:[rudyActionPayloadToParamsConverter,routerMiddleware,epicMiddleware,]});
Enter fullscreen modeExit fullscreen mode

Conclusion

Routing state can easily be integrated into and managed by redux thanks torudy. If you're looking for a router designed for redux, I highly recommend this library. Though the documentation may be lacking and it's popularity is nowhere near more popular routing libraries, it works just fine.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Builder of stuff
  • Joined

More fromkylebuildsstuff

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp