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);
Related
I'm trying to load points from Firebase in order to display it on the screen
I'm using Redux, because the points number can be updated but I can not put this.props.Points.updatePoint inside Firebase request
How can I update it?
Home.js :
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount = async () => {
const pointsRef=firebase.database().ref("Users").child(firebase.auth().currentUser.uid).orderByChild("Points").once('value',function(snapshot){
const Points=snapshot.val().Points
});
this.props.Points.updatePoints(Points)
render(){
return(
<Text>{this.props.Points}</Text>
)}
}
const mapStateToProps = (state) => {
return {
Points:state.Points};
};
const mapDispatchToProps = (dispatch) => {
return {
updatePoints:(Points)=>dispatch({ type: "UPDATE_POINTS", payload: Points }),
};
};
PointReducer.js :
const initialState = {
Points: 0,
};
const Points = (state = initialState, action) => {
switch (action.type) {
case "UPDATE_POINTS":
return {
...state,
Points: action.payload,
};
default:
return state;
}
};
export default Points;
Your method is correct. The problem is actually with the way you're trying to access to updatePoints function in mapDispatchToProps & the place you're run the statement.
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount = async () => {
const pointsRef = firebase
.database()
.ref("Users")
.child(firebase.auth().currentUser.uid)
.orderByChild("Points")
.once("value", (snapshot) => {
const Points = snapshot.val().Points;
this.props.updatePoints(Points); // You can directly access to `updatePoints` using prop object.
}); // convert this function to an arrow function. It will fix `this` related issues.
};
render() {
return <Text>{this.props.Points}</Text>;
}
}
const mapStateToProps = (state) => {
return {
Points: state.Points,
};
};
const mapDispatchToProps = (dispatch) => {
return {
updatePoints: (Points) =>
dispatch({ type: "UPDATE_POINTS", payload: Points }),
};
};
Let me know if you need further support.
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
});
};
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
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 })
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 .