How to dispatch an action from a sibling component in react/redux - redux

I have two sibling components and they are not related. For one sibling, we are getting one dispatch function as a prop and I am able to call the action to update the state. For another component, I need to update the state using the action, but I am not getting the dispatch as props. How I can update the state for the other sibling. Here I have attached the code for both siblings.
The common class called by two siblings:
loadLayerComponentConfig = async () => {
const { styleConfig, statusState, datasetState } = this.state;
const {
layer,
workSpace,
componentConfigUpdated,
} = this.props;
let layerComponentConfig = {};
layerComponentConfig.status = statusState;
layerComponentConfig.dataset = datasetState[statusState];
if (this.data !== undefined && JSON.stringify(this.data) !== "{}" && this.data.workspaceLayer !== undefined) { // calling from functional component
componentConfigUpdated(layerComponentConfig, layer.defaultStyle.id)
} else {
componentConfigUpdated(layerComponentConfig, layer.defaultStyle.id); // calling from another sibling and it's getting componentConfigUpdated as props
componentConfigUpdated is for updating the state globally.
Now this componentConfigUpdated is an action and it's passing as a prop in one sibling(component class) but for another sibling (which is a stateless functional component), it is not passing componentConfigUpdated but we need to call this componentConfigUpdated from stateless functional component. How can I do that.
Thanks in advance

Related

VurRouter transitions on more than one router

I prepared a boiled-down example on stackblitz:
https://stackblitz.com/edit/quasarframework-vy4eiw?file=README.md
The problem I try to resolve is this:
A quasar 2 app build with vite and vue 3 (and GSAP) uses layouts
Currently there are 2 layouts: StartpageLayout for the startpage at route ´/´and MainpageLayout for all the other pages at route ´/main´ and any children of it (/main/:child´`)
The MainpageLayout also contains the navigation menu
The navigation menu should be created (later on with an animation) when any route starting with ´/main´ is hit and destroyed, when there is a change to any other route
While navigating through any ´/main[/:child]´ route, the nav menu shall remain "stable" (not rebuild or anything like that)
The app uses 2 router-views for this, one in App.vue, one in MainLayout.vue. Changes between those states should mainly be handled in onBeforeRouteLeave and onBeforeRouteUpdate
To check, whether the app is in a "layout context", the routes have a meta.layoutKey, which is used in router guards to check, whether sth changed or not:
// Example: src/layouts/MainLayout.vue
onBeforeRouteUpdate((to, from, next) => {
console.log('%cMAIN_LAYOUT: onBeforeRouteUpdate invoked', consColRouter);
// compare meta.layoutKeys of routes
if (from.meta.layoutKey !== to.meta.layoutKey) {
console.warn(' YES, invoke router guard onBeforeRouteUpdate - animate!');
next() // this would be actually be called form an onComplete animation callback
} else {
console.log(' NOPE, do not invoke router guard onBeforeRouteUpdate');
next() // invoked as written
}
})
A pinia store manages state that (should) remember(s) activateMenu:
// Pinia store "pageTransitions.js" (composition API)
import { ref, reactive, computed } from 'vue'
import { defineStore } from 'pinia'
export const usePageTransitionsStore = defineStore('pageTransitions', () => {
// Pinia state in composition API
const pageTransitions = ref({
parent: false,
activateMenu: false
})
const setPageTransitions = (level, bool) => {
switch(level) {
case 'parent': {
pageTransitions.value.parent = bool
break
}
default: { console.log('default... must never happen!'); }
}
}
const setActivateMenu = (bool) => {
pageTransitions.value.activateMenu = bool
}
return {
pageTransitions,
setPageTransitions,
setActivateMenu
}
})
If store.pageTransitions.activateMenu is true, show the menu, if false, remove it. It is imported in MainLayout in order to use the activateMenu constant to manage the state of the nav menu. The onMount method sets this store variable to true. And it should be set to false in a ònBeforeRouteLeave`... (not yet implemented)
While the change from the startpage at ´/´to the MainPage at ´/main´ and vice versa works fine (even with animation, due to the store variable store.pageTransitions.parent), I keep having troubles with changes from ´/main´ to any child route ´/main/:child´ and vice versa. E.g. when the app is at /main and the user clicks on ´items 101´, the whole MainLayout is reloaded - also App.vue runs through its onAppear hooks again (see console) – and the nav is set to false again.
The goal is to not influence the MainLayout not its nested nav menu at all.
I wonder, why those reloads happen? MainLayout's onBeforeRoute checks against meta.layoutKey which does not change. But then I also observe that the pinia store gets loaded again, and the actiavteMenu var is set up false again...
Does anybody see my error(s)?

Is it a bad idea to dispatch actions from mapDispatchToProps, and why?

We are using react-router-redux for routing in our app.
<Route path='/users/:userId' component={SomeComponent} />
In this way, SomeComponent will receive a userId in its match params. The problem is that in order for the children of SomeComponent to receive the id we must pass it as props from parent to child. As much as possible we try to avoid passing props from parent to child, preferring to use containers and the redux store.
In order to provide the params to children, we are considering having SomeComponent dispatch an action when its props change. A convenient way to do this is:
const mapDispatchToProps = (dispatch: Dispatch<IState>, props: IPropsFromParent): IPropsFromDispatch => {
dispatch(ActionCreators.updateShowingUserId({
userId: props.match.params.userId,
}))
return {
anotherAction: () => {
dispatch(ActionCreators.doOtherStuff())
}
}
}
This way, whenever the component receives a new userId from the router, it will update the value in the store. Components which render deeper in the tree but which are not themselves connected to the router can use this value for conditional rendering, etc...
However, we are concerned that this may be bad practice for reasons that we are not yet aware of. It is known that calling setState in a react component's render function is forbidden. I wonder if there is some similar concrete restriction on calling dispatch in mapDispatchToProps.
Is it a bad idea to dispatch actions in mapDispatchToProps, and if so, why?
Since the dispatch will be run every time the redux store was updated it (and the props of the component change) wouldn't be a good idea to dispatch something inside the mapStateToProps, mapDispatchToProps or mergeProps functions, since you would overwrite the previous value with a default one. You should add the value you're dispatching to the initial store when possible or dispatch the defaults from the parent which controls the component.
Documentation for ReactRedux.connect

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.

Dispatch redux action after apollo-react query

I want to dispatch a redux action, right after a query finishes. – where would be the right place to do this?
here I am keeping a reference to the refetch function, so that I can easily update the view with the most recent data at a later point.
export default graphql(
allFilesQuery,
{
props: ({ ownProps, data }) => {
const { dispatch } = ownProps;
dispatch(
setRefetchAllFiles(data.refetch)
);
return {
data,
...ownProps,
};
}
}
)(FileListComponent);
while this works, I also get a warning, saying:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
The props function is supposed to be pure and return props to inject in component without performing any kind of side effects. Actually you might be able to dispatch in it, by wrapping your dispatch in a setTimeout, but this would be a very bad idea, because the props function is run everytime your component re-renders and will likely trigger many unwanted dispatches. It could even lead to infinite loops if your dispatch makes the component re-renders.
The correct place to do what you want is in your component. You can use componentWillReceiveProps (or other lifecycle), and compare previous props to next props trigger a dispatch when appropriate. You can use data.networkStatus or data.loading for that.

VueJs child component props not updating instantly

I have a parent/child component setup where the parent is loading data from the server and passing it down to children via props. In the child I would like to instantiate a jQuery calendar with some of the data it receives from the parent.
In order to wait for the data before setting up the calendar, I broadcast an event in the parent that I have an event listener setup for in the child.
The listener is being fired in the child but if I this.$log('theProp'), it's undefined. However, if I inspect the components with the VueJs devtools, the parent/child relationship is there and the child has received the prop in the meantime.
The prop is defined on the child as a dynamic prop :the-prop="theProp". Since the child does receive the prop in the end, I'm assuming my setup is correct but there seems to be some sort of delay. The parent sets the props in the return function of the ajax call and again: it's working, just with a slight delay it seems.
I also tried registering a watch listener on the prop in the child so I could setup the calendar then and be sure that the prop is there. However, the watch listener fires, but this.$log('theProp') is still undefined.
If I pass the data along with the the broadcast call, like this.$broadcast('dataLoaded', theData) the child receives it just fine. But it seems wrong to do it that way as I'm basically building my own prop handler.
I'm not posting any code because the components are rather large and the VueJs devtools are telling me the parent/child situation is working.
Am I missing some information? Is there a slight delay between setting a value in the parent and the child receiving it? What would be the proper way to wait for parent data in the child?
Normally, when you're just rendering the data out into the template, the timing doesn't matter so much since the data is bound to the template. But in this case, I really need the data to be there to setup the calendar or it will be wrong.
Thanks.
edit 1: here's a jsfiddle: https://jsfiddle.net/dr3djo0u/1/
It seems to confirm that the data is not available immediately after the broadcast. However, the watcher does work, though I could almost swear that sometimes this.$log('someData') returned undefined when I setup that testcase.
But I guess my problem might be somewhere else, I'll have a look tonight, don't have the project with me right now.
edit 2: did some more tests. My problem was that a) event listeners do not seem to receive the data instantly and b) I was also trying to init the calendar in the route.data callback if someData was already around (e.g. when coming from parent), but that route callback is called before the component is ready, so it wasn't working there either.
My solution is now this:
// works when the child route is loaded directly and parent finishes loading someData
watch: {
someData() {
this.initCalendar();
}
},
// works when navigating from parent (data already loaded)
ready() {
if (this.someData && this.someData.length) {
this.initCalendar()
}
}
As far as I know, you should not need events to pass data from parent to child.
All you need is, in the child component: props: ['theProp']
And when using the child component in the parent: <child :theProp="someData"></child>
Now, wherever in the parent you change someData, the child component will react accordingly.
You don't need events, you don't need "watch", you don't need "ready".
For example: after an AJAX call, in the parent's "ready", you load some data:
// at the parent component
data: function () {
return {
someData: {}
}
},
ready: function () {
var vm = this;
$.get(url, function(response) {
vm.someData = response;
});
}
Now, you do not need anything else to pass the data to the child. It is already in the child as theProp!
What you really need to do is to have, in the child, something which reacts to data changes on its own theProp property.
Either in the interface:
<div v-if="theProp.id > 0">
Loaded!
</div>
Or in JavaScript code:
// at the child component
computed: {
// using a computed property based on theProp's value
awesomeDate: function() {
if (!this.theProp || (this.theProp.length === 0)) {
return false;
}
if (!this.initialized) {
this.initCalendar();
}
return this.theProp.someThing;
}
}
Update 1
You can also, in the parent, render the child conditionally:
<child v-if="dataLoaded" :theProp="someData"></child>
Only set dataLoaded to true when the data is available.
Update 2
Or maybe your issue is related to a change detection caveat
Maybe you're creating a new property in an object...
vm.someObject.someProperty = someValue
...when you should do...
vm.$set('someObject.someProperty', someValue)
...among other "caveats".
Update 3
In VueJS 2 you are not restricted to templates. You can use a render function and code the most complex rendering logic you want.
Update 4 (regarding OP's edit 2)
Maybe you can drop ready and use immediate option, so your initialization is in a single place:
watch: {
someData: {
handler: function (someData) {
// check someData and eventually call
this.initCalendar();
},
immediate: true
}
}
It's because tricky behavior in Vue Parent and Child lifecycle hooks.
Usually parent component fire created() hook and then mount() hook, but when there are child components it's not exactly that way: Parent fires created() and then his childs fire created(), then mount() and only after child's mount() hooks are loaded, parent loads his mount() as explained here. And that's why the prop in child component isn't loaded.
Use mounted() hook instead created()
like that https://jsfiddle.net/stanimirsp5/xnwcvL59/1/
Vue 3
Ok so I've spent like 1.5h trying to find out how to pass prop from parent to child:
Child
<!-- Template -->
<template>
<input type="hidden" name="_csrf_token" :value="csrfToken">
<span>
{{ csrfToken }}
</span>
</template>
<!-- Script -->
<script>
export default {
props: [
"csrfToken"
]
}
</script>
Parent
<!-- Template -->
<template>
<form #submit.prevent="submitTestMailForm" v-bind:action="formActionUrl" ref="form" method="POST">
...
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
...
</form>
</template>
<!-- Script -->
<script>
...
export default {
data(){
return {
...
csrfToken : "",
}
},
methods: {
/**
* #description will handle submission of the form
*/
submitTestMailForm(){
let csrfRequestPromise = this.getCsrfToken();
let ajaxFormData = {
receiver : this.emailInput,
messageTitle : this.titleInput,
messageBody : this.bodyTextArea,
_csrf_token : this.csrfToken,
};
csrfRequestPromise.then( (response) => {
let csrfTokenResponseDto = CsrfTokenResponseDto.fromAxiosResponse(response);
this.csrfToken = csrfTokenResponseDto.csrToken;
this.axios({
method : "POST",
url : SymfonyRoutes.SEND_TEST_MAIL,
data : ajaxFormData,
}).then( (response) => {
// handle with some popover
})
});
},
/**
* #description will return the csrf token which is required upon submitting the form (Internal Symfony Validation Logic)
*/
getCsrfToken(){
...
return promise;
}
},
components: {
CsrfTokenInputComponent
}
}
</script>
Long story short
This is how You need to pass down the prop to child
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
NOT like this
<CsrfTokenInputComponent csrf-token="csrfToken"/>
Even if my IDE keep me telling me yeap i can navigate with that prop to child - vue could not bind it.
solution (testing ok)
In child component just using the props data, no need to re-assignment props's values to data, it will be cause update bug!
vue child component props update bug & solution
https://forum.vuejs.org/t/child-component-is-not-updated-when-parent-component-model-changes/18283?u=xgqfrms
The problem is not how to pass data with props, but rather how to do two things at almost the same time.
I have an user account component that can edit users (with an user id) and add users (without id).
A child component shows checkboxes for user<->company assignments, and needs the user id to prepare API calls when the user account is saved.
It is important that the child component shows before saving the user account, so that things can be selected before the user is saved and gets an id.
So it has no user id at first: the id is passed to the child component as 'null'.
It updates when the user is stored and gets an id.
But at this point, it takes a very short time for the child to get the new id into its model.
If you call a function in the child component that relies on data that was just changing, it might happen that the function executes before the data is updated.
For cases like this, nextTick() is your friend.
import { nextTick } from 'vue';
...
saveAccount() {
axios.post(URL, this.userModel).then((result)) {
// our model gets an id when persisted
this.userModel.id=result.data.id;
nextTick( () => {
this.$refs.childComponent.doSomething();
});
}
}

Resources