Redux not updating this.props in componentDidMount with async method - react-native

My redux is not updating the props.
My component:
...
import { connect } from 'react-redux';
import { getTenantByID, updateTenant } from '../actions';
...
constructor(props) {
super(props);
this.state = {
tenantData: {}
};
}
componentDidMount() {
this.getTenant();
}
onChangeText = (text, input) => {
const obj = { ...this.state.tenantData };
obj[input] = text;
this.setState({
tenantData: obj
});
};
onChangeNumberFormat = (text, input) => {
const obj = { ...this.state.tenantData };
let value = parseFloat(text);
if (isNaN(value)) {
value = 0;
}
value = parseFloat(value).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
obj[input] = value;
this.setState({
tenantData: obj
});
};
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
this.setState({
tenantData: this.props.tenantData
});
};
...
const mapStateToProps = ({ tenants }) => {
const { error, tenantData, saving } = tenants;
return { error, tenantData, saving };
};
export default connect(mapStateToProps, {
getTenantByID, updateTenant
})(TenantDetails);
In my action, I export the method:
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '', RentalAmount: '1000.50', MoveInDate: toDate('2019-01-01'),
MoveOutDate: toDate('2019-12-01'), LeaseStartDate: toDate('2019-01-01'), LeaseEndDate: toDate('2019-12-01'),
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
And I use the reducer to return the data.
...
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
saving: false,
};
...
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
If I do a console.log in the GET_TENANT_DATA in my reducer, I can see that the action.payload has data. But if I do console.log(this.state.tenantData) in my render() method, it is empty. Why is it happening?
Thanks
I include logs in the componentDidMount and render. It display in the following order
call render
this.props.tenantData is empty
Call componentDidMount
this.props.tenantData is empty
call render
this.props.tenantData has value
call render
this.props.tenantData has value
It is never setting state.tenantData. Why is it calling render() after componentDidMount()?

The problem is here, in getTenant function.
getTenant should not be async function becuase you are not returning a promise
componentDidUpdate(prevProps){
if (prevProps.tenantData.Email !== this.props.tenantData.Email) {// you need a unique value to check for changes in props
this.setTenantData();
}
}
setTenantData = () => this.setState({ tenantData: this.props.tenantData });
getTenant = () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
const tenantData = this.props.getTenantByID(tenantID);
};
And this should be your action.
export const getTenantByID = ({ tenantID }) => {
const tenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '', RentalAmount: '1000.50', MoveInDate: toDate('2019-01-01'),
MoveOutDate: toDate('2019-12-01'), LeaseStartDate: toDate('2019-01-01'), LeaseEndDate: toDate('2019-12-01'),
};
return {
type: GET_TENANT_DATA,
payload: tenant
};
};
So you can see tenantData under the console.log in componentDidUpdate.
And the reason for setState not working under getTenant is because the component takes time to update after the redux action

Related

How to re-run useQuery and FlatList?

I use FlatList with useState.
const [state, setState] = useState(route);
<FlatList
keyboardDismissMode={true}
showsVerticalScrollIndicator={false}
data={state}
keyExtractor={(comment) => "" + comment.id}
renderItem={renderComment}
/>
When I change the datㅁ which is contained in state, I want to re-run Flatlist with new data.
So after I mutate my data, I try to rerun useQuery first in order to change state. I put refetch module here.
1)
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: route?.params?.photoId,
},
});
If I put button, this onValid function will executed.
<ConfirmButton onPress={handleSubmit(onValid)}>
onValid function changes data and after all finished, as you can see I put refetch().
=> all this process is for that if I add comment and press confirm button, UI (flatlist) should be changed.
const onValid = async ({ comments }) => {
await createCommentMutation({
variables: {
photoId: route?.params?.photoId,
payload: comments,
},
});
await refetch();
console.log(updatePhoto);
};
But when I console.log data after all, it doesnt' contain added data..
what is the problem here?
If you need more explanation, I can answer in real time.
please help me.
add full code
export default function Comments({ route }) {
const { data: userData } = useMe();
const { register, handleSubmit, setValue, getValues } = useForm();
const [state, setState] = useState(route);
const [update, setUpdate] = useState(false);
const navigation = useNavigation();
useEffect(() => {
setState(route?.params?.comments);
}, [state, route]);
const renderComment = ({ item: comments }) => {
return <CommentRow comments={comments} photoId={route?.params?.photoId} />;
};
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: route?.params?.photoId,
},
});
const createCommentUpdate = (cache, result) => {
const { comments } = getValues();
const {
data: {
createComment: { ok, id, error },
},
} = result;
if (ok) {
const newComment = {
__typename: "Comment",
createdAt: Date.now() + "",
id,
isMine: true,
payload: comments,
user: {
__typename: "User",
avatar: userData?.me?.avatar,
username: userData?.me?.username,
},
};
const newCacheComment = cache.writeFragment({
data: newComment,
fragment: gql`
fragment BSName on Comment {
id
createdAt
isMine
payload
user {
username
avatar
}
}
`,
});
cache.modify({
id: `Photo:${route?.params?.photoId}`,
fields: {
comments(prev) {
return [...prev, newCacheComment];
},
commentNumber(prev) {
return prev + 1;
},
},
});
}
};
const [createCommentMutation] = useMutation(CREATE_COMMENT_MUTATION, {
update: createCommentUpdate,
});
const onValid = async ({ comments }) => {
await createCommentMutation({
variables: {
photoId: route?.params?.photoId,
payload: comments,
},
});
await refetch();
console.log(updatePhoto);
};

React Native navigate not updating props

I have one page with a list of "tenants". When I select one tenant if shows the data for this specific tenant. It is working. However, when I navigate back to the tenant list and select another tenant, it does not update the this.props with the new tenant data.
My Tenant Details Page
constructor(props) {
super(props);
this.state = {
tenantData: {}
};
}
componentDidMount() {
this.getTenantID();
}
componentDidUpdate(prevProps) {
// needs to be a unique value
if (prevProps.tenantData.Email !== this.props.tenantData.Email) {
this.getTenantID();
}
}
getTenantID = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
this.setState({
tenantData: this.props.tenantData
});
};
My action:
export const getTenantByID = (tID) => {
return (dispatch) => {
axios.get('http://myirent.com/rent/components/iRentApp.cfc', {
params: {
method: 'getTenantByTenant',
tenantID: tID
}
}).then((response) => {
const tenant = response.data.DATA[0];
console.log(tenant);
const getTenant = {
FirstName: tenant[1],
LastName: tenant[2],
Email: tenant[5],
Phone: tenant[6],
Unit: tenant[11],
MiddleName: tenant[3],
RentalAmount: tenant[4],
MoveInDate: getFormattedDate(tenant[7]),
MoveOutDate: getFormattedDate(tenant[8]),
LeaseStartDate: getFormattedDate(tenant[9]),
LeaseEndDate: getFormattedDate(tenant[10])
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
});
};
};
The tenantID is being updated and the action response data too. It looks like that the page is loading before updating the this.props.tenantData
The componentDidUpdate() is called immediately after the update. This method is not called in the first rendering.
componentDidUpdate(prevProps) {
// typical use cases (don't forget the props comparison)
if (prevProps.navigation !== this.props.navigation) {
const data = this.props.navigation.getParam('tenantID', '0')
this.getTenantID(data);
}
}
getTenantID = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
const tenantdata = await this.props.getTenantByID(tenantID); // Wait for action to complete
this.setState({
tenantData: tenantdata,
updateid : tenantID
});
};

React native mapDispatchToProps not working

I can't get my mapDispatchToProps to work properly.
I export a combineReducers:
export default combineReducers({
auth: AuthReducer,
tenants: TenantsReducer
});
The tenants reducer:
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TENANTS_DATA:
return { ...state, error: false, data: action.payload };
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
default:
return state;
}
};
Then I have getTenantByID method in my action
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '',
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
Finally, I tried to use it in my component.
import { connect } from 'react-redux';
import { getTenantByID } from '../actions';
...
componentDidMount() {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
console.log(this.props);
this.state = {
tenantData: this.props.tenantData
};
}
const mapStateToProps = ({ tenants }) => {
return {
error: tenants.error,
tenantData: tenants.tenantData
};
};
const mapDispatchToProps = () => {
return {
getTenantByID
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TenantDetails);
In my componentDidMount, the console.log(this.props) is returning a empty object for tenantData. What am I doing wrong?
Initial state is showing as the component already mounted, which is empty object {}
this.props.getTenantByID(tenantId);
this action triggers actually, but the data is not available in componentDidMount lifecycle.
try putting log in render like this
componentDidMount(){
this.props.getTenantByID(2);
}
render() {
console.log(this.props.tenantData); // 1st render => {}, 2nd render=> desired data
return (
<div/>
);
}
use componentDidUpdate to check if value is changed,
componentDidUpdate(prevProps){
if(prevProps.tenantData !== this.props.tenantData){ console.log(prevProps.tenantData, this.props.tenantData) }
}
remember to receive the dispatch parameter in your mapDispatchToProps method
const mapDispatchToProps = (dispatch) => {
return {
getTenantByID: (tenantID ) => {
dispatch(getTenantByID({tenantID }));
};
};
};
call for
this.props.getTenantByID({ tenantID: 10 })

React Native Reducer data not being sent to my page

I have an action data that is being sending to the reducer, but not ti my page constructor.
Action Method:
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '',
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
Then, in my reducer
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TENANTS_DATA:
return { ...state, error: false, data: action.payload };
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
default:
return state;
}
};
If I do a console.log(action) after the case GET_TENANT_DATA, I can see that data for the payload, so it is working in the reducer.
My page:
constructor(props) {
super(props);
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
console.log(this.props); // this show tenantData as a empty object
this.state = {
tenantData: this.props.tenantData
};
}
...
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
Seems like you are using thunk and it's asynchronous, so you need to await your action so that you can get the updated state after you fire the action. Otherwise, you can remove thunk if it's not necessary. You may want to fire the action in componentDidMount instead of constructor too
componentDidMount() {
this.getTenant();
}
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
console.log(this.props); // Get updated props here
this.state = {
tenantData: this.props.tenantData
};
}
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
Or you can capture the update via componentDidUpdate
componentDidMount() {
this.getTenant();
}
componentDidUpdate(previousProps) {
if (this.props.tenantData !== previousProps.tenantData) {
console.log(this.props); // Get updated props here
this.state = {
tenantData: this.props.tenantData
};
}
}
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
}
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);

store.getstate() evaluate to undefined

in a react-native project,I keep hitting in this error:
state is undefined, evaluating store.getstate()
//store.js
const composeEnhancers =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));
const Store = createStore(
combineReducers(
{
form: formReducer,
appointmentsReducer
},
enhancer
)
);
console.log(Store.getState());
export default Store;`
//reducer.js
import {
FETCH_APPOINTMENTS_BEGIN,
FETCH_APPOINTMENTS_SUCCESS,
FETCH_APPOINTMENTS_FAILURE
} from '../actions/appointmentsAction';
const initialState = {
data: [],
loading: false,
error: null
};
export default function appointmentsReducer(state = initialState, action) {
switch(action.type) {
case FETCH_APPOINTMENTS_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_APPOINTMENTS_SUCCESS:
return {
...state,
loading: false,
data: action.payload.appointments
};
case FETCH_APPOINTMENTS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
data: []
};
default:
return state;
}
}
//actions.js
import { listUrl } from "../cst";
export const FETCH_APPOINTMENTS_BEGIN = "FETCH_APPOINTMENTS_BEGIN";
export const FETCH_APPOINTMENTS_SUCCESS = "FETCH_APPOINTMENTS_SUCCESS";
export const FETCH_APPOINTMENTS_FAILURE = "FETCH_PRODUCTS_FAILURE";
export const fetchAppointmentsBegin = () => ({
type: FETCH_APPOINTMENTS_BEGIN
});
export const fetchAppointmentsSuccess = appointments => ({
type: FETCH_APPOINTMENTS_SUCCESS,
payload: { appointments }
});
export const fetchAppointmentsFailure = error => ({
type: FETCH_APPOINTMENTS_FAILURE,
payload: { error }
});
export function fetchAppointments() {
return dispatch => {
dispatch(fetchAppointmentsBegin());
return fetch(listUrl)
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchApointmentsSuccess(json.appointment));
return json.appointment;
})
.catch(error => dispatch(fetchAppointmentsFailure(error)));
};
}
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
// app.js
export default function App() {
return (
<Provider store={Store}>
<Navigation />
</Provider>
);
}
//list rendrer component :
const mapStateToProps = state => ({
data: state.appointments.data,
loading: state.loading,
error: state.error
});
the console.log of store.getstate() gives :
Object {
"appointmentsReducer": Object {
"data": Array [],
"error": null,
"loading": false,
},
"form": Object {},
I'm not sure where the problem is.
Is it due to the asynchronous call not being handled properly?
If I use saga to handle the fetch, will it resolve the problem?
Any help would be appreciated .