Redux best practice for sharing data between states - redux

I'm working on an webapp using redux, and I'm trying to figure out the best way to sharing data between two substates.
Lets say I have a data structure like so, for an app where a user can order food from an italian restaurant:
{
"entrees": [{ "type": "pizza", "sauce": "alfredo", "topping": "meat" }],
"options": {
"entreeTypes": ["pizza", "calzone"],
"sauceTypes": {
"pizza": ["marinara", "white"],
"calzone": ["marinara", "alfredo"]
},
"toppingTypes": {
"marinara": ["meat", "veggies", "cheese"],
"white": ["meat", "veggies"],
"alfredo": ["meat"]
}
}
}
So if the user wants to add an entree to their order, the new entree should populate all the default options for type, sauce, and topping, which here would be "pizza", "marinara", and "meat".
Ideally, I'd like this addition of an entree to take place in the entrees reducer. However, the entrees reducer would need access to various parts of the options state to accomplish its goal.
My present implementation looks something like this (using redux-thunk):
function addEntree(){
return (dispatch, getState) => {
const options = getState().options;
const type = options.entreeTypes[0];
const sauce = options.sauceTypes[type][0];
const topping = options.toppingTypes[sauce][0];
const action = { type: 'ADD_ENTREE', type, sauce, topping };
dispatch(action);
};
}
Where the action gets picked up by the entrees reducer.
So my question is, does this seem in good practice? Is anyone else using patterns they find to be superior?

The example is a but odd because typically options aren't part of the application state. Moreover, it seems like you want a hierarchy of choices, where you can't have veggies with your alfredo. So two things:
If the options can be constants outside of application state because they are dynamic, do that. So basically store your current options reducer as a const somewhere and you can just import it.
If the ingredient list and combinations are dynamic, I don't think you should be enforcing the ingredient combination restrictions in your actions. If you have a order page, for example, load all of the options in the props. Voilá, you have access to multiple reducers at once. In the situation where the user has selected "Pizza" but not the sauce or topping, you would select the first sauce in props.options.sauceTypes[selectedEntree], and then props.options.toppingTypes[selectedSauce]
class OrderPage extends React.Component {
...
function onSelectEntree (entree) {
const { options: { sauceTypes, toppingTypes }, addEntree } = this.props;
const sauce = sauceTypes[entree][0];
const topping = toppingTypes[sauce][0];
addEntree(entree, sauce, topping):
// Or if you don't want to persist it globally yet:
// this.setState({ entree, sauce, topping });
}
...
}
Then in the view, show only the options that are allowed given the current selection that the user has made (ie, hide veggies if they picked alfredo). Do validation in the component.

Related

Vue3 dynamically watching child component data

I'm working in Nuxt3 and I've got a slightly unusual setup trying to watch or retrieve data from child components in a complex form that is structured as a multi-step wizard. It's obviously Vue underneath and I'm using the composition API.
My setup is that I have a page the wizard component is on, and that component has a prop that is an array of steps in the wizard. Each of these steps is some string fields for titles and labels and then a component type for the content. This way I can reuse existing form blocks in different ways. The key thing to understand is that the array of steps can be any length and contain any type of component.
Ideally, I'd like each child component to be unaware of being in the wizard so it can be reused elsewhere in the app. For example, a form that is one of the steps should handle its own validation and make public its state in a way the wizard component can read or watch.
The image below explains my basic setup.
                             
The page includes this tag:
<Wizard :steps="steps" :object="project" #submit="createProject"/>
The Wizard loops over the steps to create each component.
<div v-for="(step) in steps" :key="step.name">
<component v-if="step.status === 'current'" :is="step.content.component" />
</div>
The data to setup the component with the right props for the wizard itself and the child component props.
const steps = ref([
{
name: 'overview',
title: t('overview'),
subTitle: t('projectCreateOverviewDescription'),
status: 'current',
invalid: true,
content: {
component: Overview,
props: null,
model: {}
}
},
{
name: 'members',
title: t('members'),
subTitle: t('projectCreateMembersDescription'),
status: 'upcoming',
invalid: false,
content: {
component: ThumbnailList,
props: {
objects: users,
title: t('users'),
objectNameSingular: t('user'),
objectNamePlural: t('users'),
So far I've tried to dynamically create references in the wizard component to watch the state of the children but those refs are always null. This concept of a null ref seems to be the accepted answer elsewhere when binding to known child components, but with this dynamic setup, it doesn't seem to be the right route.
interface StepRefs {
[key: string]: any
}
let stepRefs: StepRefs = {}
props.steps.forEach(step => {
stepRefs[step.name] = ref(null)
watch(() => stepRefs[step.name].value, (newValue, oldValue) => {
console.log(newValue)
console.log(oldValue)
}, { deep: true })
})
Can anyone direct me to the right approach to take for this setup? I have a lot of these wizards in different places in the app so a component approach is really attractive, but if it comes to it I'll abandon the idea and move that layer of logic to the pages to avoid the dynamic aspect.
To handle changes in child components I'd recommend to use events. You can have the children emit an event on change or completion, and the wizard is listening to events from all children and handling them respectively.
On the wizard subscribe to the event handler of the step component, and process the data coming from each step on completion (or whatever stage you need).
This way you don't need any special data type for the steps, they can just be an array. Simply use a ref to keep track of the current step. You don't even need a v-for, if you just display one step at a time. For a wizard navigation you might still need a v-for, but it would be much simpler. Please see a rough example below.
<div>
<stepComponent step="currentStep" #step-complete="handleStepComplete"/>
<div>
<wizardNavigationItemComponent v-for="step in steps" :active="step.name === currentStep.name" />
</div>
</div>
<script setup lang="ts">
const steps = step[/*your step data here*/]
const currentStepIndex = ref(0)
const currentStep = ref(steps[currentStepIndex.value])
function handleStepComplete(data) {
/* handle the data and move to next step */
currentStepIndex.value = currentStepIndex.value + 1 % steps.length
}
</script>
In the component you just need to define the event and emit it when the data is ready, to pass along the data:
<script setup lang="ts">
const emit = defineEmits<{
(event: "stepComplete", data: <your data type>): void
}>()
/* call emit in the component when its done / filled */
emit("stepComplete", data)
</script>
I hope this helps and can provide a viable path forward for you!

ngrx create nested state by domain and ui data

I'm using ngrx entities, and ideally I would like to achieve the following state structure:
state = {
domainData: {
userEntities: {
entities: {},
ids: []
}
},
ui: {
usersView: {}
}
}
This will allow me to separate my data storage from UI related stuff. I can't seem to find a way to do this though. At the moment what I'm doing is this:
I have a separate store module. I have created a reducer for userEntities and a reducer for usersView. Then, with ActionReducerMap I have created a combined reducer like so:
const reducer: ActionReducerMap<AppState> = {
userEntities: userEntitiesReducer,
usersView: usersViewReducer
}
Then I import StoreModule.forRoot(reducer) in my store module. It works, but the structure of the resulting state is not how I would like it to be. Is there any way I can fix it?
Thanks.

Redux Selectors in Mithril

I've been tasked with implementing selectors in our redux application. Everything I'm reading online about redux selectors talks about React and how you can replace what is in mapStateToProps with a selector. What is the equivalent/where would i do this in a mithril app?
What is the equivalent/where would i do this in a mithril app?
Firstly, you don't need an equivalent, you can just use the exact same selectors that you would in a React application.
Where to call selectors?
You can call the selectors wherever you want, but I recommend calling them as close to where the data is used as possible. Don't call selectors in a component high up in the component hierarchy only to pass the data down via several components before they end up in a component that actually uses the data – unless you have a good reason to do so.
For most cases you can call the selectors inside a view-function, although you might come across cases where you need to call selectors in other lifecycle methods as well. In some applications you might also want to use selectors in m.render as well.
A couple of examples off the top of my head:
Inside the view function when creating DOM-elements
var LoggedinUserDetails = {
view: function () {
return m('', [
m('', getLoggedinUserName(store.getState())), // getLoggedinUserName is a selector
m('img', { src: getLoggedinUserImageUrl(store.getState()) }) // getLoggedinUserImageUrl is a selector
])
}
}
Inside the view function when creating Mithril components
var UserDetails = {
view: function (attrs) {
return m('', [
m('', attrs.user.name),
m('img', { src: attrs.user.imageUrl })
])
}
}
...
m(UserDetails, { user: getLoggedInUserDetails(store.getState()) }) // getLoggedInUserDetails is a selector
Inside m.render
In this example, we have a game that requires us to re-render the whole page after any change.
function onStateChange() {
var state = store.getState();
m.render(document.body, m(Game, {
map: getMap(state),
players: getPlayers(state),
visibleArea: getVisibleArea(state)
}));
}
// Receiving partial state updates from server via websockets
websocket.on('update-map', function (map) {
store.dispatch({ type: 'update-map', payload: map });
});
websocket.on('update-players', function (players) {
store.dispatch({ type: 'update-players', payload: players });
});
// Changing state based on user input
window.addEventListener('keydown', function (event) {
switch (event.key) {
case 'Enter':
store.dispatch({ type: 'move-visible-area-to-next-player' });
break;
}
});
I'm not familiar with Mithril, but Redux state selectors are independent from React. They are just functions that expect state and return a:
a slice of state
or data derived from state
For example, if I my Redux state has an entry named records containg a list of models:
{
records: [ ... ],
}
I could create a selector returning the length:
const numOfRecords = state => state.records.length
Or if my state also keeps track of a sortBy value:
const sortedRecords = state => state.records.sort(sortByFn(state.sortBy))
Selectors can be helpful to increase performance and reduce the need for updates. (reselect is a great module for this).
They are also great for developing modular pieces of code that depend on data stored in application state but don't really want to know where that data comes from.

Structuring a reducer for a simple CRUD application in redux

So I'm creating what is at it's core a very simple CRUD-style application, using React + Redux. There is a collection of (lets call them) posts, with an API, and I want to be able to list those and then when the user clicks on one, go into a detail page about that post.
So I have a posts reducer. Originally I started using the approach taken from the redux real-world example. This maintains a cache of objects via an index reducer, and when you do a "get post" it checks the cache and if it's there, it returns that, else it makes the appropriate API call. When components mount they try to get things from this cache, and if they're not there they wait (return false) until they are.
Whilst this worked OK, for various reasons I now need to make this non-caching i.e. everytime I load the /posts/:postId page I need to get the post via the API.
I realise in the non-redux world you would just do a fetch() in the componentDidMount, and then setState() on that. But I want the posts stored in a reducer as other parts of the app may call actions that modify those posts (say for example a websocket or just a complex redux-connected component).
One approach I've seen people use is an "active" item in their reducer, like this example: https://github.com/rajaraodv/react-redux-blog/blob/master/public/src/reducers/reducer_posts.js
Whilst this is OK, it necessitates that each component that loads the active post must have a componentWillUnmount action to reset the active post (see resetMe: https://github.com/rajaraodv/react-redux-blog/blob/master/public/src/containers/PostDetailsContainer.js). If it did not reset the active post, it will be left hanging around for when the next post is displayed (it will probably flash for a short time whilst the API call is made, but it's still not nice). Generally forcing every page that wants to look at a post to do a resetMe() in a componentWillUnmount fells like a bad-smell.
So does anyone have any ideas or seen a good example of this? It seems such a simple case, I'm a bit surprised I can't find any material on it.
How to do it depends on your already existing reducers, but i'll just make a new one
reducers/post.js
import { GET_ALL_POSTS } from './../actions/posts';
export default (state = {
posts: []
}, action) => {
switch (action.type) {
case GET_ALL_POSTS:
return Object.assign({}, state, { posts: action.posts });
default:
return state;
}
};
It is very easy to understand, just fire an action to get all your posts and replace your previous posts with the new ones in the reducer.
Use componentDidMount to fire the GET_ALL_POSTS action, and use a boolean flag in the state to know if the posts where loaded or not, so you don't reload them every single time, only when the component mounts.
components/posts.jsx
import React from 'react';
export default class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
firstLoad: false
};
}
componendDidMount() {
if (!this.state.firstLoad) {
this.props.onGetAll();
this.setState({
firstLoad: true
});
}
}
// See how easy it is to refresh the lists of posts
refresh() {
this.props.onGetAll();
}
render () {
...
// Render your posts here
{ this.props.posts.map( ... ) }
...
}
}
We're just missing the container to pass the posts and the events to the component
containers/posts.js
import { connect } from 'react-redux';
import { getPosts } from './../actions/posts';
import Posts from './../components/posts.jsx';
export default connect(
state => ({ posts: state.posts }),
dispatch => ({ onGetAll: () => dispatch(getPosts()) })
);
This is a very simple pattern and I've used it on many applications
If you use react-router you can take advantage of onEnter hook.

How to get createContainer & Collection working outside of main jsx

I'm a novice to Meteor/React.js applications, and while I've been able to cobble together an app with multiple components by passing the App.jsx collections around as props, I can't seem to get individual components to handle their own collections. My rational is that some components need the collection fetched in one order, while others need the collection fetched in another. It seems silly to pass around a collection and then have to manipulate it internally to get the proper effect when a new sort is what's needed. My main App.jsx has:
App.propTypes = {
workdone:PropTypes.array.isRequired,
subjects:PropTypes.array.isRequired
};
export default createContainer(() => {
return {
workdone: WorkDoneCollection.find({},{sort:{createdAt:-1}}).fetch(),
subjects: SubjectCollection.find({},{sort:{subject:1}}).fetch()
};
}, App);
and if I pass the props.workdone on to another component everything works fine. On the other hand if I try a set up an independent collection in the other component, like this:
WorkSummary.propTypes = {
workdone:PropTypes.array
};
export default createContainer(() => {
return {
workdone: WorkDoneCollection.find({},{sort:{createdAt:1}}).fetch()
};
}, WorkSummary);
the workdone property is undefined.

Resources