NGRX - Subscription from child component to app.component - ngrx

I have to show footercomponet on app-component conditionally based on child component data. I am subscribing from app component global store update and when child component is updating the store I am not seeing the app component subscription getting fired. I am using angular 6 below is the prototype of code on app component.
Code- AppComponent - >
public ngOnInit(): void {
this.subscriptions.push(this.store.pipe(select(showFooterFun)).subscribe(showfooter => {
this.showLegalFooter = showfooter;
}));
}
Child component - I have child component updating the global store with boolean value to show Hide footer. Is it possible to invoke app.component subscription from child by updating global store.

Please don't subscribe, this is often the case some things do not work.
Instead, use Angular's built-in async pipe.
Also make sure you're modifying your state inside reducers in a pure way.
// in typescript
showFooter$ = this.store.pipe(select(showFooterFun);
// in html
<div *ngIf="showFooter$ | async">
Footer
</div>

Thankyou timdeschryver for your response/suggestion much appreciated well I haven't tried the approach you have suggested but on further debugging I observed that in my case due to exception in app component ngOnInit the subscription was getting unsubscribe, by putting null check I observed it got resolved.
this.subscriptions.push(this.store.pipe(select(showLegalFooter)).subscribe(showfooter => {
if (showfooter) { //null check resolved the issue
this.showFooter = showfooter.showFooter; //earlier when showfooter was null //which lead to exception and the suscription got unsuscribe.
}
}));

Related

Prevent React Native remount of component on context value change

In a chat app I am building I want to deduct credits from a user's account, whenever the users sends a message and when a chat is initiated.
The user account is accessible in the app as a context and uses a snapshot listener on a firestore document to update whenever something changes in the user account document. (See code samples 1. and 2. at the bottom)
Now whenever anything in the userAccount object changes, all of the context providers children (NavigationStructure and all its subcomponents) are re-rendered as per React's documentation.
This, however causes huge problems on the chat screen that also uses this context:
The states that are defined there get re-initalized whenever something in the context changes. For example, I have a flag that indicates whether a modal is visible, default value is visible. When I go onto the chat screen, hide the modal, change a value manually in the firestore database (e.g. deduct credits) the chat screen is rerendered and the modal is visible again. (See code sample 3.)
I am very lost what the best way to solve this issue is, any ideas?
Solutions that I have thought about:
Move the credits counter to a different firestore document and deduct the credits once per day, but that feels like a weird workaround.
From Googling it seems to be possible to do something with useCallback or React.memo, but I am very unsure how.
Give up and become a wood worker...seems like running away from the problem though.
Maybe it has something to the nested react-navigation stack and tab navigators I'm using within NavigationStructure?
Desperate things I have tried:
Wrap all sub-components of NavigationStructrue in "React.memo(..)"
Make sure I don't define a component within another component's body.
Look at loads of stack overflow posts and try to fix things, none have worked.
Code Samples
App setup with context
function App() {
const userData = useUserData();
...
return (
<>
<UserContext.Provider value={{ ...userData }}>
<NavigationStructure />
</UserContext.Provider>
</>
}
useUserData Hook with firestore snapshot listener
export const useUserData = () => {
const [user, loading] = useAuthState(authFB);
const [userAccount, setUserAccount] = useState<userAccount | null>();
const [userLoading, setUserLoading] = useState(true);
useEffect(() => {
...
unsubscribe = onSnapshot(
doc(getFirestore(), firebaseCollection.userAccount, user.uid),
(doc) => {
if (doc.exists()) {
const data = doc.data() as userAccount & firebaseRequirement;
//STACK OVERFLOW COMMENT: data CONTAINES 'credits' FIELD
...
setUserAccount(data);
}
...
}
);
}, [user, loading]);
...
return {
user,
userAccount,
userLoading: userLoading || loading,
};
};
Code Sample: Chat screen with modal
export const Chat = ({ route, navigation }: ChatScreenProps): JSX.Element => {
const ctx = useContext(UserContext);
const userAccount = ctx.userAccount as userAccount;
...
//modal visibility
const [modalVisible, setModalVisible] = useState(true);
// STACK OVERFLOW COMMENT: ISSUE IS HERE.
// FOR SOME REASON THIS STATE GET'S RE-INITALIZED (AS true) WHENEVER
// SOMETHING IN THE userAccount CHANGES.
...
return (
<>
...
<Modal
title={t(tPrefix, 'tasklistModal.title')}
visible={ModalVisible}
onClose={() => setModalVisible(false)}
footer={
...
}
>
....
</Modal>
...
</>)
}
Any change to the context does indeed rerender all consumer components whether they use the changed property or not.
But it will not unmount and mount the component which is the reason why your local state gets initialized to the default value.
So the problem is not the in the rerenders (rarely the case) but rather <Chat ... /> or one of it's parent component unmounting due to changes in the context.
It is hard to tell from the partial code examples given but I would suggest looking at how you use loading. Something like loading ? <div>loading..</div> : <Chat ... /> would cause this behaviour.
As an example here is a codesandbox which illustrates the points made.
This is a characteristic of React Context - any change in value to a context results in a re-render in all of the context's consumers. This is briefly touched on in the Caveats section in their docs, but is expanded on in third-party blogs like this one: How to destroy your app's performance with React Context.
You've already tried the author's suggestion of memoization. Memoizing your components won't prevent re-initialization, since the values in the component do change when you change your user object.
The solution is to use a third-party state management solution that relies not on Context but on its own diffing. Redux, Zustand, and other popular libraries do their own comparison so that only affected components re-render.
Context is really only recommended for values that change infrequently and would require full-app re-renders anyway, like theme changes or language selection. Try replacing it with a "real" state management solution instead.

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!

$emit doesn't trigger child events

For a VueJS 2.0 project I have the following on the parent component
<template>
<child></child>
<button #click="$emit('childEvent)"></button>
</template>
on the child component I have:
{
events: { 'childEvent' : function(){.... },
ready() { this.$on('childEvent',...) },
methods: { childEvent() {....} }
}
Nothing seems to work on button click. Is it that I need to create a parent method that would then emit to the child? I was using vuejs 1. but now I'm confused as to the ways parent to child communications work
While the other answers are correct and it is usually possible to use a data driven approach.
I'm going to add this for anyone looking for an answer to this question who need a way other than props. I ran into a similar problem when trying to set focus on a particular input inside a custom form component. To do this I had to give the custom component a ref then do this.
this.$refs.customInput.$emit('focus');
//or
this.$refs.customInput.directlyCallMethod();
This access the vue instance of the child and then you can emit an event that is heard by that component.
As par the documentation:
In Vue.js, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events. Let’s see how they work next.
So in latest Vue, you can not send events from parent to child, you can pass props to child, and send event from child to parent.
You can use a custom Vue instance for that.
// Globally
const eventBus = new Vue()
// In your child
eventBus.$on('eventName', () => {
// Do something
});
// In your parent
eventBus.$emit('eventName')
Events from parent to child are done with $broadcast() in Vue 1.0 and are not possible at all in Vue 2.0.
And you possibly don't need that, there usually is a better solution than doesn't need events, but that depends on your usecase.
I needed to do this for a popover where several could exist and props for each one are inappropriate.
To dispatch global events it is possible to add event listeners to the $root Vue instance.
// Child:
created() {
this.$root.$on('popover', (e) => {
// Determine if popover should close, etc.
this.close()
})
},
// Parent:
this.$emit('popover', 'arg1', 'argn')
Remember to call $off in destroyed as well.

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();
});
}
}

Make api calls from dumb components with Redux

I want to implement dropdown with react and redux. Dropdown will be a part of other component, so, it it really "Dumb" component. But dropdown should call to api to fetch items, apply custom filters and etc. Api calls should be authenticated, tokens stored it global state. Should I pass tokens to component props? Or there is a better way to do this?
A dumb component, by definition, should be dumb: it means that it should take everything it needs "from the top", i.e. via the props.
Only a "Smart" ("connected" to Redux) Component, up the hierarchy, would deal with fetching the data using a new (async) Action, which would then modify the state when the API call returns, which would re-render your Dumb Component with its new props.
So in Redux:
Your Dumb component takes two props: one with the values coming from your API (which actually are part of your "state"), the other one a function that is called when your dropdown selected item changes. <MyDumbComponent data={this.props.data} onChange={this.onChange.bind(this)} />
Then a "Smart" component up the hierarchy will listen to that onChange function, and dispatch an Action (FETCH_DATA)
The (async) Action will call the API, and when receiving the data, call another Action (FETCH_DATA_SUCCESS) with the data
Then Redux, with a reducer, would update its state from the Action payload
Which will re-render your Component with its new props (new data), coming from the current state.
You might want to read this: http://redux.js.org/docs/basics/UsageWithReact.html
And regarding async actions: http://redux.js.org/docs/advanced/AsyncActions.html
Dumb component doesn't mean it can do anything like fetch updates, it means it's 'dumb' to the concept of redux, and knows nothing about your store or what library you're using. The benefit is that you can change your flux implementation and you only have the small bit of work to update the smart components.
For the type of scenario you're describing, you would give your <Menu> a function via props that would run when <Menu> wants to update the data. <Menu> knows nothing about Redux - it's just executing a function - but it's still able to fetch new data. Depending on the complexities, you could pass through the raw action creator (bound to dispatchAction) and have it run that.
import * as dataActions from './actions';
// We have dataActions.fetchItems() which is the action creater to get new items.
// Attach items, and the action creator (bound to dispatch) to props
#connect((store) => {
return {items: store.data.items}
}, dataActions)
class ItemsPage extends Component {
static propTypes = {
items: PropTypes.object, // the items from the store, from #connect
fetchItems: PropTypes.func // action dispatcher, from #connect
}
render() {
return (<Menu onChange={this.props.fetchItems} /> )
}
}
// Our 'dumb' component that doesnt know anything about Redux,
// but is still able to update data (via its smart parent)
class Menu extends Component {
static propTypes = {
onChange: PropTypes.func // same as fetchItems
}
render() {
return (<button onClick={this.props.onChange}>Update items</button>);
}
}

Resources