ReactJS: Child component updates before parent state is set - reactjs-flux

I have parent and child components where the state of the child (openWhen is essentially a shouldChildRender) is controlled by the parent. I'm running into an issue where the child is updated (receives props) before the parent state is updated after calling the parent's setState() so the child receives stale state until the second pass through of this flow. In the second pass through the child receives the updated state and renders correctly.
Parent:
openDeactivateWarning: function (resource) {
this.setState({
openDeactivateModal: true,
workingResource: resource
});
console.log('this.state.openDeactivateModal is still false here', this.state);
},
render: function () {
return (
<Modal openWhen={this.state.openDeactivateModal} openCallback={this.openDeactivateWarning}>
<p>Some Modal content</p>
</Modal>
)
}
Child:
componentWillReceiveProps: function () {
console.log('This is fired before parent state changes and openModal is not called');
if (this.props.openWhen) {
this.openModal();
}
},
I know that setState() does not immediately result in a state change but I was under the impression that children would still be re-rendered after state is actually changed. Is there a more preferable way of accomplishing this?

you should use nextProps which is updated. this.props.openWhen in componentWillReceiveProps is still the old one when the parent component updates it's state.
componentWillReceiveProps: function (nextProps) {
console.log('This is fired before parent state changes and openModal is not called');
if (nextProps.openWhen) { <-- updated props
this.openModal();
}
}

Related

rtk Query getting Called twice, because of UseEffect

I am having a parent component in which there is a button which on click sets a state variable to true/false, based on this stateVariable I am rendering the child component.
Inside the Child component I have a rtkQuery to get a list of Items, and based on the size of the data from the get request, I am changing the state of the variable inside the useEffect.
Issue: When I click the link/button on parent component, the api gets called and the relevant popup is to be opened based on data, which is getting done correctly, but as soon as the popup gets closed, the api in parent component gets called which is fine, but api in the child component also gets called, though child component is not rendered(conditional rendering).
//rtk query
getItems: builder.query<IItemsResponse[], string>({
query: (itemNumber) =>
`/application/reassign/${itemNumber}/users`,
providesTags: ["Item"],
keepUnusedDataFor: 0,
}),
export const {useGetItemsQuery} =apiSlice;
//rtk-query call in child Component
const { data, isLoading } = useGetReassignUsersQuery(itemNumber);
//Parent Component Calling Child Component Using
{callModalState && (
<ItemModal
setModalState={setModalState}
itemNumber={itemNumber}
onCancel={() => {
setCallModalState(false);
}}
onItemSuccess={() => {
setModalState(false);
onApplicationSuccess;
}}
customToastForError={() => {
setModalState(false);
customToastForError;
}}
/>
)}
</div>
//State in Child Component
const [modalState, setModalState] =
useState<ModalState>({
showNoItemModal: false,
showModal: false,
});
//useEffect in child Component
useEffect(() => {
if (data && data.length > 0) {
setModalState({
...modalState,
showModal: true,
showNoUsersModal: false,
});
} else if (data && data.length == 0) {
setModalState({
...ModalState,
showModal: false,
showNoItemModal: true,
});
}
}, [data]);
The query in child component gets called once before child component is rendered, once after the child component is closed, however I do not want the rtk querty in the child component to be called if i close the popup.

How to trigger useEffect when passing props back to parent component

This question is for React Navigation 5 in React Native app. When passing a props back to parent screen, how to setup trigger in parent screen for useEffect? Here is an example:
Child Screen
_onPress = (country, country_code, calling_code) => {
const { navigation, route } = this.props;
navigation.navigate("parent"
{select:{
country_name: country,
country_code: country_code,
calling_code: calling_code
});
};
on parent component. useEffect is used to do something after the props select is passed back from the child screen:
useEffect(()=> {
if (route.params?.select?) {
//do something after select is passed back
}
}, [what is the trigger?]) //<<==how to setup trigger for useEffect?. [] did not work.
How to setup trigger in parent component for useEffect? Tried to use [] which shall trigger with any reload but it did not work. Shall the same props select be passed from parent component to child screen and passed back to parent component again after being updated in child screen? Is select a trigger?
Well, since you are using React Navigation 5, what you can do is to replace useEffect with useFocusEffect, and your functions will be called every time when the user arrives/returns to the parent screen.
First import useFocusEffect at the top of your file like this:
import { useFocusEffect } from "#react-navigation/native"
then replace useEffect with useFocusEffect like this:
useFocusEffect(
React.useCallback(() => {
if (route.params?.select?) {
//do something after select is passed back
}
return () => {
// Here you can do something when the screen is unfocused such as clearing event listners but you can also leave it empty if you want
};
}, [])
);
One more thing you can do is to set the navigation state params when you are moving to the child screen from Parent Screen like this.
props.navigation.navigate('ChildScreen', {onNewCreate});
In the child component just called this
select:{
country_name: country,
country_code: country_code,
calling_code: calling_code
}
props.navigation.state.params.onNewCreate(select);
And in the Parent component just define onNewCreate() and you can do what you want.
const onNewCreate = async (select) => {
//You can do what you want here with select}

Vue: Event Methods- Confusion

I have a parent Vue which enables or disables "edit" mode. In non-edit mode all components are read only.
I've implemented this via a data object and all works fine.
I've split out some of the components in child components.
From the parent an $emit message is sent with the new edit mode state:
methods: {
toggleMode () {
this.editMode = !this.editMode
this.$emit('edit-mode-change', this.editMode)
}
Using Vue DevTools I can see the message is emitted.
However, I can't seem to receive it in my child component!I've looked a the docs, but none of the examples match this case. This is what I have currently (in the child component):
methods: {
onEditModeChange: function (mode) {
console.log('mode is', mode)
this.editMode = mode
}
Also tried:
events: {
onEditModeChange: function (mode) {
console.log('mode is', mode)
this.editMode = mode
}
Plus I'm getting an browser console error as follows:
[Vue warn]: Invalid handler for event "edit-mode-change": got false
(found in <Dimensions> at /home/anthony/Projects/Towers-Vue/src/components/assets/Dimensions.vue)
I'm sure I'm doing something basic wrong, but the docs don't reference the events: {} block, yet I've seen it on other code. Nor does it show how to implement a listener.
Thanks for taking the time to view this post, if you can point me in the right direction, it's much appreciated.
In Vue 2, events only flow laterally or up, not down.
What you really want is to simply pass a prop into your components.
In the parent JS:
toggleMode () {
this.editMode = ! this.editMode;
}
In the parent template:
<child-component-1 :editMode="editMode"></child-component-1>
...same for others...
Then simply accept editMode as a prop in each of your child components:
{
props: ['editMode']
}
You can now use editMode within your child's template. It'll track the parent's editMode, so no need for manual events/watchers.
The way vue2 works is by having a one-direction flow of the data, from parent to child, so in your parent component you can have
<template>
<child-component :isEditing="editMode"></child-component>
</template>
<script>
export default {
methods: {
toggleMode () {
this.editMode = !this.editMode
this.$emit('edit-mode-change', this.editMode)
}
}
}
and in child component you use props to get the data
Vue.component('child-component', {
props: ['isEditing'],
template: '<span>edit mode: {{ isEditing }}</span>'
})
we have cover the edit mode for the child. now if you want to send data from child to parent, then child needs to "emit" a signal to the parent, as props are "read only"
in child component you do at any point
someMethod() {
this.$emit('editDone', dataEdited);
}
and in your parent component you "intercept" the message using on:
<template>
<child-component
:isEditing="editMode"
#editDone="someParentMethod"></child-component>
</template>
Greetings!

update reactjs context after ajax request finished with flux architecture

I need to update the context after an ajax request has finished. I'm using the flux architecture and everything works to the point that when my component is notified about the updated I need to set the new context.
A simple demostration:
I have a parent component which generates the context by calling a store. The store gets the data after an ajax request is initialized somewhere else. Like this:
RowAPI.ajaxGetAllRows();
Then I have my component which holds the context:
let ParentComponent = React.createClass({
childContextTypes: {
rows: React.PropTypes.object
},
getChildContext: function() {
return {
rows: RowStore.getAllRows(),
};
},
componentDidMount: function() {
RowStore.addChangeListener(this._onRowsChanged);
},
componentWillUnmount: function() {
RowStore.removeChangeListener(this._onRowsChanged);
},
render() {
return (
<ChildComponent />
);
},
_onRowsChanged: function() {
//Now we need to update context
}
});
Now since we are listening for row changes, we will get an update when our ajax request has finished and put the data into our store. Now we need to get that data and set it as context. That is the problem.
This is my child component that uses the context. I know that I just can pass the rows as a props to my child but this is just an example and in my real scenario I have many children which would need to pass the props.
let ChildComponent = React.createClass({
contextTypes: {
rows: React.PropTypes.object
},
render() {
return (
<div style={styles.wrapper}>
{this.context.rows}
</div>
);
},
});
Thanks in advance!
I would change the getChildContext in ParentComponent to refer to the state instead of a function call to the RowStore.
getChildContext: function() {
return {
rows: this.state.rows,
};
}
Then, whenever a row changes, and the _onRowsChanged callback it called, it can set this.state.rows accordingly.
I believe that the issue with the original method of calling RowStore.getAllRows() inside getChildContext is that it is only called once. Nothing is forcing it to call RowStore.getAllRows() on every change.
However, by using a state, you can use Flux concepts to "force" a change in state on every update, and that will be reflected in the context.

Encapsulation with React child components

How should one access state (just state, not the React State) of child components in React?
I've built a small React UI. In it, at one point, I have a Component displaying a list of selected options and a button to allow them to be edited. Clicking the button opens a Modal with a bunch of checkboxes in, one for each option. The Modal is it's own React component. The top level component showing the selected options and the button to edit them owns the state, the Modal renders with props instead. Once the Modal is dismissed I want to get the state of the checkboxes to update the state of the parent object. I am doing this by using refs to call a function on the child object 'getSelectedOptions' which returns some JSON for me identifying those options selected. So when the Modal is selected it calls a callback function passed in from the parent which then asks the Modal for the new set of options selected.
Here's a simplified version of my code
OptionsChooser = React.createClass({
//function passed to Modal, called when user "OK's" their new selection
optionsSelected: function() {
var optsSelected = this.refs.modal.getOptionsSelected();
//setState locally and save to server...
},
render: function() {
return (
<UneditableOptions />
<button onClick={this.showModal}>Select options</button>
<div>
<Modal
ref="modal"
options={this.state.options}
optionsSelected={this.optionsSelected}
/>
</div>
);
}
});
Modal = React.createClass({
getOptionsSelected: function() {
return $(React.findDOMNode(this.refs.optionsselector))
.find('input[type="checkbox"]:checked').map(function(i, input){
return {
normalisedName: input.value
};
}
);
},
render: function() {
return (
//Modal with list of checkboxes, dismissing calls optionsSelected function passed in
);
}
});
This keeps the implementation details of the UI of the Modal hidden from the parent, which seems to me to be a good coding practice. I have however been advised that using refs in this manner may be incorrect and I should be passing state around somehow else, or indeed having the parent component access the checkboxes itself. I'm still relatively new to React so was wondering if there is a better approach in this situation?
Yeah, you don't want to use refs like this really. Instead, one way would be to pass a callback to the Modal:
OptionsChooser = React.createClass({
onOptionSelect: function(data) {
},
render: function() {
return <Modal onClose={this.onOptionSelect} />
}
});
Modal = React.createClass({
onClose: function() {
var selectedOptions = this.state.selectedOptions;
this.props.onClose(selectedOptions);
},
render: function() {
return ();
}
});
I.e., the child calls a function that is passed in via props. Also the way you're getting the selected options looks over-fussy. Instead you could have a function that runs when the checkboxes are ticked and store the selections in the Modal state.
Another solution to this problem could be to use the Flux pattern, where your child component fires off an action with data and relays it to a store, which your top-level component would listen to. It's a bit out of scope of this question though.

Resources