Best Practice: Handle read-only data with Redux - javascript

I am building an application with React and Redux and I have a question about design.
My application uses React Router. It has a Navigation Bar (on the left) that displays the routes from React Router configuration file with a Material Design Menu.
I would know what is the best practice to handle the static data of this LeftNav component.
This data has the following shape:
const menuItems = [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
];
To respect the smart and dumb component proposal, my LeftNav component is dumb and stateless.
I also have a smart component (right now it's just my AppContainer) that renders the LeftNav component and provides the menuItems array to it via props.
I wonder if I must include this read-only data into my redux state tree. In this case, I would have a constant reducer like this:
export default handleActions({}, [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
]);
Is it a good practice to have a constant reducer with no action handler? If not, what should I do?
Thanks in advance.

I'm not recognizing how your reducer is working. What is handleActions?
I think a constant reducer seems like a reasonable solution, but I would implement it by using a default first parameter. Assuming you're using combineReducers to scope all your reducers to the part of the state they control, I'd do something like:
/* reducers.js */
const menuItemsInitialState = [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
];
export function menuItems(state = menuItemsInitialState, _) {
return state;
}

Personally I prefer my reducers to focus on dealing with application / system-wide state. The configuration in your example would feel more at home living as a constant inside the component which makes use of it (LeftNav?)

#Cnode I agree #acjay that it should be in reducer state, even without action creators.
I would go one step further and suggest that you should fetch the data after the app loads rather than including the data in your built files. Generally data does not belong in built files.
This is because, in the real world this particular set (and others - possibly much larger sets) would be statically available on an API as you probably would want the values configurable by a CMS.
Add a static reducer to host this state
Fetch the data after the app initialises and then push it on state - meaning you will have at least one action creator.

Related

How to add an i18n locale prefix in Vue router for all the locales except for the default one?

I'm trying to create a route to add a locale prefix for all the routes, I got it working using this code:
routes: [{
path: '/:lang',
component: {
template: '<router-view />'
},
children: [
{
path: '',
name: 'home',
component: Home
},
{
path: 'about',
name: 'about',
component: About
},
{
path: 'contact',
name: 'contact',
component: Contact
}
]
}]
For the default locale en I don't want to set this prefix so params.lang is going to be the full path in this case and not the locale code, so requesting any path without a locale code will render the Home component which matches.
So how can I do this? Does a navigation guard like beforeEnter help in this case?
Actually you can do it without navigation guards. The main goal here is to let the router understand when you have a url without :lang parameter. To distinguish between the language prefixes and the actual paths you could use a regex pattern for the :lang param like: (de|fr|en|zu) (whatever list of codes is suitable for you). And make the :lang to be an optional ?.
So something like this should work: path: '/:lang(de|fr|en|zu)?' At least it works for me :) ...
So now if you request /about or /de/about both would match About.. however the first one will have params.lang === undefined. So I guess whenever you set your locale you can do: const locale = this.$route.params.lang || 'en'
here is the documentation for Advanced Matching Patterns
routes: [{
path: '/:lang(de|fr|en|zu)?',
component: {
template: '<router-view />'
},
children: [
{
path: '',
name: 'home',
component: Home
},
{
path: 'about',
name: 'about',
component: About
},
{
path: 'contact',
name: 'contact',
component: Contact
}
]
}]

Angularjs ui-router state definition

In an angular ui-router tutorial, all states are defined in the same js file.
myApp.config(function($stateProvider) {
// An array of state definitions
var states = [
{ name: 'hello', url: '/hello', component: 'hello' },
{ name: 'about', url: '/about', component: 'about' },
{
name: 'people',
url: '/people',
component: 'people',
resolve: {
people: function(PeopleService) {
return PeopleService.getAllPeople();
}
}
},
{
name: 'people.person',
url: '/{personId}',
component: 'person',
resolve: {
person: function(people, $stateParams) {
return people.find(function(person) {
return person.id === $stateParams.personId;
});
}
}
}
]
// Loop over the state definitions and register them
states.forEach(function(state) {
$stateProvider.state(state);
});
});
However, when creating a large application, we may have a lot of states. And usually a state calls a component that calls a template and probably uses a service and so on.
Therefore, I use to define the state in a separate js file, same as I do for component, template, service, ...
So I may have for example:
home.stt.js (for state)
home.srv.js (for service)
home.cpt.js (for component)
home.html (for view)
Is it a good practice? Or it is better to have all states defined in the same file?
It is more readable, understandable, and organised, to have them split into separate files. Presumably you have a good directory structure, so major areas would have their own directories and sub-directories etc., Your config files for states can go into them to follow the same hierarchy.
This is my own experience from several mid- to large- sized projects where structure is so important for ease-of-use.

How can I change data object in Vue component depending on route and using vue-route?

I faced issue while working on vuejs application:
I have to change titles in few vuejs components, depending on routes
routes: [
{
path: '/',
components: { //with default data
FirstScreen,
Advantages,
Slider,
},
},
{
path: '/moscow',
components: { //with special data for Moscow
FirstScreen,
Advantages,
Slider,
},
},
{
path: '/berlin',
components: { //with special data for Berlin
FirstScreen,
Advantages,
Slider,
},
},
],
and data at all .vue files looks like this
data() {
return {
defaultTitle: 'some string',
defaultArray: ['defaultFirst', 'defaultSec'],
};
},
And I have about 100 cities... how can I solve this issue?
Assuming you use vue-router you can 'hook into' one of it's extremely helpful methods called beforeEach. This methods acts as a sort of middleware and runs before any given route is executed.
router.beforeEach((to, from, next) => {
document.title = to.meta.title
next();
});
If someone will need to implement data transfer depending on routes and really want to hardcode it to route file it is possible with this: https://github.com/vuejs/vue-router/blob/dev/docs/en/essentials/passing-props.md
In my case it will be looks like:
routes: [
{
path: '/',
components: {
FirstScreen,
Advantages,
BigSlider,
},
props: {
FirstScreen: {
title: 'title',
subtitle: ['arr1', 'arr2'],
},
Advantages: {
title: 'title',
advantages: 'advantages',
},
BigSlider: {
title: 'title',
slideText: 'slideText',
},
},
},
and in component you have to do something like this
export default {
props: {
title: {
type: String,
},
subtitle: {
type: Array,
},
},
It will work fine, but I agree with Kevin Karsopawiro in part that this approach is unmaintainable. So using city-component for fetching data from back-end is best in my case.

Turn off history for Durandal child router

I'm using the child routers in Durandal for tab control.
However whenever you change tab, it creates a history event. Again and again. It has made my "Close" button pointless because I cannot router.navigateBack() anymore (would need to many times).
Is there a way to navigate back beyond the current parent route, or make the child router create no history?
var childRouter = m_router
.createChildRouter()
.makeRelative({ moduleId: 'viewmodels/manage/bill/center', fromParent: true })
.map([
{ route: ['contract', ''], moduleId: 'contract', title: 'Contracts', nav: true },
{ route: 'job', moduleId: 'job', title: 'Jobs', nav: true },
{ route: 'order', moduleId: 'order', title: 'Orders', nav: true }
]).buildNavigationModel();
Thanks.
It sounds like what you want to do is just to not place an entry into history. You can do this like so -
router.navigate('your/hash/here', { replace: true, trigger: false });
When your child router is making a route change.
http://durandaljs.com/documentation/Using-The-Router.html#triggering-navigation

HotTowel Durandal Inject different views based on the user

In the shell.html for HotTowel template we have:
<!--ko compose: {model: router.activeItem,
afterCompose: router.afterCompose,
transition: 'entrance'} -->
<!--/ko-->
which will automatically insert the proper view by convention. I am trying to inject different views based on the user's role in a HotTowel/Durandal App. For example,
I have two Views,
productEditor_Admin.html
productEditor_Superviser.html
(instead of these two views, I used to have only productEditor.html, by convention everything worked)
and only a single ViewModel:
productEditor.js
Now, I want to have a function in productEditor.js that will let me decide which view to insert based on user's role. I see in the Composition documentation, we can do function strategy(settings) : promise but I am not sure what's the best way to accomplish this in the HotTowel template. Anyone have already tried and got an answer for that?
It's possible to return a 'viewUrl' property in the view model, so hopefully something like the following will crack the door open ;-).
define(function () {
viewUrl = function () {
var role = 'role2'; //Hardcoded for demo
var roleViewMap = {
'default': 'samples/viewComposition/dFiddle/index.html',
role1: 'samples/viewComposition/dFiddle/role1.html',
role2: 'samples/viewComposition/dFiddle/role2.html'
};
return roleViewMap[role];
}
return {
viewUrl: viewUrl(),
propertyOne: 'This is a databound property from the root context.',
propertyTwo: 'This property demonstrates that binding contexts flow through composed views.'
};
});
Did you take a look at John Papa's JumpStart course on PluralSight.
Look at the source code from that app here: https://github.com/johnpapa/PluralsightSpaJumpStartFinal
In App/Config.js file he adds other routes which are visible by default as :
var routes = [{
url: 'sessions',
moduleId: 'viewmodels/sessions',
name: 'Sessions',
visible: true,
caption: 'Sessions',
settings: { caption: '<i class="icon-book"></i> Sessions' }
}, {
url: 'speakers',
moduleId: 'viewmodels/speakers',
name: 'Speakers',
caption: 'Speakers',
visible: true,
settings: { caption: '<i class="icon-user"></i> Speakers' }
}, {
url: 'sessiondetail/:id',
moduleId: 'viewmodels/sessiondetail',
name: 'Edit Session',
caption: 'Edit Session',
visible: false
}, {
url: 'sessionadd',
moduleId: 'viewmodels/sessionadd',
name: 'Add Session',
visible: false,
caption: 'Add Session',
settings: { admin: true, caption: '<i class="icon-plus"></i> Add Session' }
}];
You can add routes to both the views here using the same logic and then in your productEditor.js you can decide which view to navigate and navigate to that using router.navigateTo() method.

Categories

Resources