How to trigger useEffect when passing props back to parent component - react-navigation

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}

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.

Setting state with values inside an object

Im basically trying to set the state with the City value but when I set thee state and console log the state its always empty. Im even console logging along the way.
sample city object from first console.log:
{_id: '625a495ae4bea1099502f824',
City: 'Dakhla Oasis'
Country: 'Egypt'
}
import React from "react";
import { useState } from "react";
export default function Cities() {
const [singlecity, setSingleCity] = useState('');
function GetSingleCity() {
console.log(city)
setSingleCity(city.City);
console.log(singlecity);
}
setState function in react is asynchronous, so if you console.log right after the set state, you will not see the updated value immediately. However if yo try to render the state, you will see the rendered html will be updated. If you want to console.log the latest value of singleCity, you could either move the console.log outside GetSingleCity, so console.log will run on every re-render, or put console.log inside a useEffect:
useEffect(() => {console.log(singleCity)}, [singleCity])
useEffect will run the callback every time the state singleCity has changed, so you will see the updated value

Jest store state and RTL rendered component's onClick event handler different states

I'm using the following code to test a state-dependent react component using jest and rtl:
test("render author, date and image correctly after going next post", async () => {
const store = configureStore({
reducer: {
data: dataReducer
}
});
const Wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
render(<Post />, { wrapper: Wrapper });
const getSpy = jest.spyOn(axios, 'get').mockReturnValue(mockPostJSON);
await store.dispatch(FETCH_POSTS());
expect(getSpy).toBeCalledWith('https://www.reddit.com/r/EarthPorn/.json');
const beforeClick = await screen.findByTestId('authorAndDate');
expect(beforeClick.innerHTML.toString()).toBe(mockPostsList[0].author + ' - ' + mockPostsList[0].date);
fireEvent.click(screen.getByText('Next post'));
const afterClick = await screen.findByTestId('authorAndDate');
expect(afterClick.innerHTML.toString()).toBe(mockPostsList[1].author + ' - ' + mockPostsList[1].date);
})
The problem I'm having is that before the click everything in the store is set up correctly and the authorAndDate element displays the first item in the array of posts. But after the click is fired the store goes back to the initial state it had before loading the mock data. I checked within the component's event handler and right before it does anything the state has been reset. The code is as follows:
const handleNextClick = () => {
store.dispatch(GO_NEXT_POST());
store.dispatch(FETCH_COMMENTS());
}
I've been an hour over the code trying to find something that would reset the state and found nothing. I'm guessing it's some kind of interaction between jest and rtl but I can't figure out why the store in the test has one state and the store in the component's event handler has another :S
Well, figured it out. Can't use store.dispatch directly as it's accessing a stale state. Needed to use the useDispatch hook. Hope this serves anybody who faces the same problem in the future.

react-redux together with components status

in a react UI I have a table component. You can edit one row of the table by clicking a edit button or you can add a new record by clicking a "new-record-button". When clicking the edit button an redux-action is triggered which takes the row and sets a visible property of a modal dialog. When the "new-record-button" is clicked an action is triggered which creates a new empty data item and the same modal dialog is triggered.
In the modal dialog I have several text components with onChange method.
in this onChange-method the data-item is written.
When to user clicks a save-button the edited dataItem is saved to the database.
So my code looks like:
const mapStateToProps = (state) => ({
dataItem: state.datItemToEdit || {},
...
});
...
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
dataItem.carId = text;
break;
}
...
}
this.forceUpdate();
}
...
<TextField
...
onChange={event => this.handleTextChange(event)}
/>
I have several question regarding this approach. First I do not understand why in handleTextChange we can write to dataItem. It does work apparently.
dataItem.carId is set in the example code but I thought
const {dataItem} = this.props;
gives us a local read-only variable dataItem just to read from the props...
Next thing I think is a poor design. After reading in a book about react I think we should not write to props but only set a state.
In my example I get the the dataItem from the redux-state. The mapStateToProps maps it to the (read-only) props of the component, right?!. But I want to EDIT it. So I would have to copy it to the state of my component?
But where to do it?
Once in the state of my component I could simply call this.setState for the various text-fields and the component would render and I could abstain from forceUpdate(), right?!
Can someone explain how the redux status plays together with the component status and props for this example?
In redux or react, you shouldn't write to the props directly because you should keep your props as immutable. Redux forces us to use immutable state because state is a source of truth for the application. If the reference to state changes then only your app should render. If you'll mutate your state (objects) then the references don't get changed and your app doesn't know whether some state has been changed or not. React/Redux doesn't give you read-only objects automatically. You can mutate them anytime but as I told you, it can cause problems that Your app won't know when to re-render. If you want to have this read-only property inherently, you should probably use immutable.js
About your second question that you'll have to copy the props to the component's state and where you should do it. You should do it in the constructor of the component and you should use immutibility helper
import React from React;
import update from 'immutibility-helper';
class Modal extends React.Component {
constructor(props){
this.state = {
dataItem: dataItem,
};
}
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.state.dataItem, text); //fire a redux action to update state in redux
this.setState(update(this.state, {
dataItem: {
carId: {$set: text},
}
});
break;
}
...
}
}
}
You wouldn't have to do forceUpdate in such case because the reference to state will change and the component will re-render itself.
Also, you can use forceUpdate in your application but personally I don't find it a great idea because when React/Redux is giving you the flow of state, by using forceUpdate, you're breaking the flow.
The last question is how redux and react state plays together. That is also a matter of choice. If I have a app level state, e.g., in your case you've some app level data, you should put that in your redux state and if you have a component level things, such as opening a modal or opening a third pane. That's the convention I follow but that can really depend on how you want to exploit react and redux state.
Also, in above code, I put the redux state in component state too (because you asked where to put that) but Ideally you should fire a redux action and update in redux state. In this way, you will restrict yourself from state duplication in react and redux.
import React from React;
import {updateItem} from './actions';
class Modal extends React.Component {
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.props.dataItem, text); //fire a redux action to update state in redux
break;
}
...
}
}
}
const mapStateToProps = (state) => ({
dataItem: getDataItem(state), //get Data Item gets Data from redux state
});
export default connect(mapStateToProps, {updateItem: updateItem})(Modal);
in Actions:
updateItem = (dataItem, text) => dispatch => {
dispatch({type: 'UPDATE_ITEM', payLoad: {dataItem, text});
};
in Reducer:
export default (state = {}, action) => {
switch(action){
case 'UPDATE_ITEM': {
return {
...state,
dataItem: {
...action.dataItem,
carId: action.text,
}
};
}
}
}
In this way, your state will be pure and you don't have to worry about immutibility.
EDIT:
As constructor will be called only once, you should probably use componentWillReceiveProps so that whenever you render the component, you get the next updated props of the component. You can check whether the carId of dataItem is same or not and then update the state.
componentWillReceiveProps(nextProps){
if(nextProps.dataItem.carId !== this.props.dataItem.carId){
this.setState({dataItem: nextProps.dataItem});
}
}
You should only use redux when you want different, unrelated components in your app to know and share the specific state.
e.g. - When a user logs in to your app, you might want all components to know that user so you'll connect your different containers to the user reducer and then propagate the user to the components.
Sounds like in this case you have a classic use case for using the inner state.
You can use the parent of all TextFields to maintain all rows, edit them by index, etc.
Once you start using redux, it's really easy to make the mistake of transferring the entire state of the components to the reducers, I've been there and stopped doing it a while ago :)

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