I'm struggling with react-redux variable for hours...hope someone can help.
The conditional returns to me that the variable order.name is not defined, although everything goes as it should in the reducer and action.
When isLoading === true, it continues rendering {order.name} and I know it is not defined at that point because it takes some time. At that time i set Loader to do it's job..
So it’s not clear to me why he continues to render even though there’s a conditional one that shouldn’t allow it... until isLoading === false.
Here is console.log of orderDetails
import { getOrderDetailsAction } from "../actions/orderAction";
const OrderScreen = ({ match }) => {
const orderId = match.params.id;
const dispatch = useDispatch();
useEffect(() => {
dispatch(getOrderDetailsAction(orderId));
}, [dispatch, orderId]);
const orderDetails = useSelector((state) => state.orderDetails);
const { order, isLoading } = orderDetails;
return isLoading ? <Loader /> : <>{order.name}</>;
};
export default OrderScreen;
Reducer
export const orderDetailsReducers = (
state = { isLoading: true, orderItems: [], shippingAddress: {} },
action
) => {
switch (action.type) {
case ORDER_DETAILS_REQUEST:
return {
...state,
isLoading: true,
};
case ORDER_DETAILS_SUCCESS:
return {
isLoading: false,
order: action.payload,
};
case ORDER_DETAILS_FAILED:
return {
isLoading: false,
error: action.payload,
};
default:
return { state };
}
};
Action
export const getOrderDetailsAction = (id) => async (dispatch, getState) => {
try {
dispatch({
type: ORDER_DETAILS_REQUEST,
});
//Getting TOKEN
const {
userLogin: { userInfo },
} = getState();
//Passing TOKEN
const config = {
headers: {
"auth-token": `${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/orders/${id}`, config);
dispatch({
type: ORDER_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ORDER_DETAILS_FAILED,
payload: error.response.data.msg,
});
}
};
Check redux action for how isLoading state is changed from redux dev tools
Check reducer name -> state.orderDetails (does this exists ?)
const orderDetails = useSelector((state) => state.orderDetails);
Also, we can correct this
state = { isLoading: true, orderItems: [], shippingAddress: {}, order: {} }
// return entire state and not just isLoading and order
case ORDER_DETAILS_SUCCESS:
return {
...state, <--------
isLoading: false,
order: action.payload,
};
case ORDER_DETAILS_FAILED:
return {
...state, <---------
isLoading: false,
error: action.payload,
};
Related
I'm trying to update the component after a user inputs new data.
Currently on componetDidMount() I call my reducer to fetch data from an API and return it to the component. That works. But when the user updates add a new form and it gets saved in the API, I call the API and the store updates (both redux and console log confirmed this) but the component does not update.
I'm think this could be an aysnc problem but I'm not certain.
Store:
type KnownAction = RecievedInvoicesAction | RequestInvoicesAction | RefreshInvoices;
export const actionCreators = {
requestInvoices: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
const appState = getState();
if (appState && appState.invoices && appState.invoices.isLoading) {
fetch('https://localhost:44304/api/invoices')
.then((response) => response.json())
.then((data) => {
dispatch({
type: 'RECIEVED_INVOICES',
invoices: data,
isLoading: false,
});
toast.success('Invoices loaded 👍', {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
})
});
dispatch({ type: 'REQUEST_INVOICES', isLoading: true});
}
},
refreshInvoices: (): AppThunkAction<KnownAction> => (dispatch) => {
fetch('https://localhost:44304/api/invoices')
.then((response) => response.json())
.then((data) => {
console.log(data);
dispatch({
type: 'REFRESH_INVOICES',
invoices: data,
isLoading: false,
});
});
dispatch({ type: 'REQUEST_INVOICES', isLoading: true});
}
};
// REDUCER
const unloadedState: InvoiceState = { isLoading: true, invoices: [] };
export const reducer: Reducer<InvoiceState> = (
state: InvoiceState | undefined,
incomingAction: Action
): InvoiceState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'REQUEST_INVOICES' :
return Object.assign({}, state, {
isLoading: action.isLoading
})
case 'RECIEVED_INVOICES':
return Object.assign({}, state, {
invoices: action.invoices,
isLoading: action.isLoading
})
case 'REFRESH_INVOICES':
return Object.assign({}, state, {
invoices: action.invoices,
isLoading: action.isLoading
})
default:
return state;
}
};
Main Component:
class Home extends React.Component<HomeProps, State> {
constructor(SearchInvoiceProps : HomeProps) {
super(SearchInvoiceProps);
this.state = {
queryText : '',
filterBy : 'all',
orderBy : 'asc',
order : 'invoiceDate',
error : '',
invoicesArr : []
}
}
componentDidMount() {
this.ensureDataFetched();
this.setState({
invoicesArr : this.props.invoices
})
}
ensureDataFetched = () => {
this.props.requestInvoices();
}
...
}
export default connect(
(state: ApplicationState) => state.invoices,
InvoiceStore.actionCreators
)(Home as any);
I'm trying to render the data from the following object of data which is coming from an API.
{
"code": 0,
"c": "verified",
"d": "verified",
"leaseInfo": {
"infoId": 6
},
"cpfPrice": "500.00",
"carCurrentLocation": {
"id": 1,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
"n": "verified",
"p": "false",
"ownerCarInfo": {
"brand": "Ferrari",
"model": "0"
},
"serviceFeeRate": 0.10,
"depositPrice": "100.00",
"pics": [
{
"picid": 49,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
],
"items": {
"itemid": 5,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
}
}
I'm using react-redux to dispatch an action, where I will be provided with the data under a state named 'carDetails'.
However, when I try to access the data, if my component is refreshed, carDetails becomes undefined and hence gives "Cannot read property ownerCarInfo of undefined."
I'm obtaining and de-structuring the data of carDetails like this in my React component:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(match.params.id));
}, [dispatch, match]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
);
)
}
As long as the component or the React application is not refreshed, it retrieves data and displays it correctly. The carDetails becomes an empty array as soon as the page is refreshed.
This is the getCarDetails() action:
export const getCarDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: CAR_DETAILS_REQUEST,
});
const { userLogin } = getState();
const { userInfo } = userLogin;
const config = {
headers: {
Authorization: userInfo.token,
'Content-Type': 'application/json',
},
};
const { data } = await axios.get(
`${BASE_API}/car/info/getDetails/${id}/${userInfo.bscId}`,
config
);
dispatch({
type: CAR_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: CAR_DETAILS_FAIL,
payload:
error.response && error.response.data.msg
? error.response.data.msg
: error.msg,
});
}
};
This is my reducer:
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { loading: true };
case CAR_DETAILS_SUCCESS:
return { loading: false, carDetails: action.payload };
case CAR_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
This is how I declare carDetails in the redux store.
const reducer = combineReducers({
carDetails: carsDetailsReducer,
});
What is the cause for carDetails becoming undefined and the useEffect not running on page refresh?
If you are using axios your action should look like this with async function and await while you are calling API.
If you are passing API car id in the api link then pass the id in the parameters:
import axios from "axios";
export const loadData = (id) => async (dispatch) => {
dispatch({
type: "CAR_DETAILS_REQUEST",
});
const detailData = await axios.get("http:\\****/id");
dispatch({
type: "CAR_DETAILS_SUCCESS",
payload: {
success: detailData.data,
},
});
};
Reducer:
const initailState = { carDetails: [], loading: true };
export const carsDetailsReducer = (state = initailState, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { ...state,
loading: true
};
case CAR_DETAILS_SUCCESS:
return {...state,
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return { ...state,
loading: false,
error: action.payload };
default:
return ...state;
}
};
Your useEffect should only work when data is fetched:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(id));
}, [dispatch]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
You can also use it without a useEffect by making an onclick() function like this:
const loadDetailHandler = () => {
dispatch(getCarDetails(id));
};
return (
<div onClick={loadDetailHandler} >
</div>
If carDetails initial state is an array, then why are you destructuring object properties from it in your UI? Question for another time...
If after reloading the page the state reverts back to the initial state, an empty array is still a defined object. You need to track down what is causing your state.carDetails.carDetails to become undefined. If you examine your reducer notice that your CAR_DETAILS_REQUEST case wipes the carDetails state out and it becomes undefined. Honestly I'm surprised you aren't seeing this issue when your code runs normally without a page reload.
You need to hold on to that state. For good measure, you should always shallow copy the existing state when computing the next state object unless you've good reason to omit parts of state.
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
case CAR_DETAILS_SUCCESS:
return {
...state, // <-- shallow copy existing state
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return {
...state, // <-- shallow copy existing state
loading: false,
error: action.payload,
};
default:
return state;
}
};
for me, I think you should save the state in the
`case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
`
to be able to use it before o when you want to use a reducer you should each case
have the old state the reducer return the same sharp of initial state that put it you also used is loading and that not found in the initial state
so try to make the shape of the state
state={
isloading:false,
carDetails: []
}
also try each time to same the state by {...state ,is loading:true}
The problem is in CAR_DETAILS_REQUEST. You only return { loading: true }; so carDetails will be lost and become undefined.
Just update your reducer like this:
case CAR_DETAILS_REQUEST:
return { ...state, loading: true };
I'm trying to implement an isFetching flag that indicates when my data is ready for rendering. But even if the flag works, i.e. jumps from isFetching = true to isFetching = false after the data has been successfully requested, there is still an error when I try to access data: cannot read property 'username' of null
Profile Component
class Profile extends React.Component {
render() {
const (isFetching, profile) = this.props.profile
console.log (isFetching)
console.log (profile)
return <h1>Hello, {isFetching = "false"? profile[0].username : null}</h1>;
}
}
function mapStateToProps(state, ownProps) {
const profile= state.profile
return { profile }
};
export default connect(
mapStateToProps,
{ logout }
)(Profile);
Action
export const getProfile = () => (dispatch, getState) => {
// Profile Loading
dispatch({ type: GET_PROFILE_REQUEST });
axios
.get(apiBase + "/profile/", tokenConfig(getState))
.then(res => {
dispatch({
type: GET_PROFILE_SUCCESS,
payload: res.data
});
})
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
dispatch({
type: GET_PROFILE_FAILURE,
});
});
};
Reducer
const initialState = {
isFetching: false,
profile: null
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_PROFILE_REQUEST:
return {
...state,
isFetching: true
};
case GET_PROFILE_SUCCESS:
return {
...state,
profile: action.payload,
isFetching: false
};
case GET_PROFILE_FAILURE:
return {
...state,
profile: action.payload,
isFetching: false
};
default:
return state;
}
}
Redux log for GET_PROFILE_SUCCESS
profile
isFetching: false
profile[
{
"username": "Daniel",
"id": 1,
"profile": {
"image": "Test",
"bio": "Test"
}
}
]
I'm happy for every clarification.
You have a small error in your code.
return <h1>Hello, {isFetching = "false"? profile.username : null}</h1>;
You are not checking for the value of isFetching but rather setting it again. Also, since profile is an array, you need to get the first element.Replace it with
return <h1>Hello, {!isFetching? profile[0].username : null}</h1>;
and it should work.
The problem is that when on of my reducer updates its own state, it also updates the state of another reducer.
//authActions.js
export const authActions = {
login: (props) => dispatch => {
// This will make sure the loading spinner will appear.
dispatch({
type: POST_LOGIN_PENDING,
payload: null
})
// make request to login user
axios.post(LOGIN_ENDPOINT, {
email: props.email,
password: props.password
}).then(res => dispatch({
type: POST_LOGIN_FULFILLED,
payload: res.data
})
).catch( () => dispatch({
type: POST_LOGIN_REJECTED,
payload: null
}))
},
logout: () => dispatch => {
dispatch({
type: LOGOUT,
payload: null
})
},
// authReducer.js
export const initialState = {
token: "",
userRole: "",
isLoading: false,
loginFailed: false,
isAuthenticated: false,
}
export function authReducer(state = initialState, action) {
switch (action.type) {
case POST_LOGIN_PENDING:
return {
...state,
isLoading: true,
}
case POST_LOGIN_FULFILLED:
return {
...state,
token: action.payload.token,
userRole: action.payload.userRole,
loginFailed: false,
isAuthenticated: true,
isLoading: false,
}
case POST_LOGIN_REJECTED:
return {
...state,
loginFailed: true,
isLoading: false,
}
// studentActions.js
export const studentActions = {
getAllStudents: props => dispatch => {
dispatch({
type: GET_ALL_STUDENTS_PENDING,
payload: null,
})
axios.get(STUDENTS_ENDPOINT, {
headers: {
'Authorization': `Bearer ${props.token}`
}
})
.then(res =>
dispatch({
type: GET_ALL_STUDENTS_FULFILLED,
payload: res.data
}))
.catch(err => dispatch({
type: GET_ALL_STUDENTS_FULFILLED,
payload: err
}))
},
// studentReducer.js
export const initialState = {
students: [],
err: "",
isLoading: false,
}
export function studentReducer(state = initialState, action) {
switch (action.type) {
case GET_ALL_STUDENTS_PENDING:
return {
...state,
isLoading: true,
}
case GET_ALL_STUDENTS_FULFILLED:
return {
...state,
students: action.payload,
isLoading: false,
}
case GET_ALL_STUDENTS_REJECTED:
return {
...state,
err: action.payload,
isLoading: false,
}
case DELETE_STUDENT_BY_ID_FULFILLED:
return state
default:
return state
}
}
When a user logs in and the POST_LOGIN_FULFILLED applies. I would expect only the initialstate of the authReducer to be updated, but when inspect with the redux devtools I can see that that the array "studens" which is part of the initialstate of the studentReducer also is updated. From what I understand this should not be possible.
After the user has logged in the students array is filled: (From redux devtools)
student: {
students: [] => {....some stuff}
isLoading: true => false
}
By reading you comments it looks like that GET_ALL_STUDENTS_FULFILLED refers to POST_LOGIN_FULFILLED . This must be the reason why your students array is updated. Change
export const GET_ALL_STUDENTS_PENDING = 'POST_LOGIN_PENDING';
export const GET_ALL_STUDENTS_REJECTED = 'POST_LOGIN_REJECTED';
export const GET_ALL_STUDENTS_FULFILLED = 'POST_LOGIN_FULFILLED';
to
export const GET_ALL_STUDENTS_PENDING = 'GET_ALL_STUDENTS_PENDING ';
export const GET_ALL_STUDENTS_REJECTED = 'GET_ALL_STUDENTS_REJECTED ';
export const GET_ALL_STUDENTS_FULFILLED = 'GET_ALL_STUDENTS_FULFILLED ';
Action types should be unique or else it might get triggered by some other action
In the below jhipster generated code, how the pending, success and failure of actions are being triggered? For each action type we use, it is being appended with _PENDING or _SUCCESS or _FAILURE and I'm not able to figure out where and how it happens.
As I see pending, success and failure states are being handled by the reducer I don't understand when and where those actions are being triggered.
For example in the code below, the first action has type ACTION_TYPES.FETCH_MEDICINE_LIST = 'medicine/FETCH_MEDICINE_LIST'.
The actions that actually gets triggered are medicine/FETCH_MEDICINE_LIST_PENDING, medicine/FETCH_MEDICINE_LIST_SUCCESS, medicine/FETCH_MEDICINE_LIST_FAILURE when medicine/FETCH_MEDICINE_LIST action gets trigger. Where and how the Api state actions are being triggered?
import { ICrudGetAction, ICrudGetAllAction, ICrudPutAction, ICrudDeleteAction } from 'react-jhipster';
import { cleanEntity } from 'app/shared/util/entity-utils';
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
import { IMedicine, defaultValue } from 'app/shared/model/medicine.model';
export const ACTION_TYPES = {
FETCH_MEDICINE_LIST: 'medicine/FETCH_MEDICINE_LIST',
FETCH_MEDICINE: 'medicine/FETCH_MEDICINE',
CREATE_MEDICINE: 'medicine/CREATE_MEDICINE',
UPDATE_MEDICINE: 'medicine/UPDATE_MEDICINE',
DELETE_MEDICINE: 'medicine/DELETE_MEDICINE',
RESET: 'medicine/RESET'
};
const initialState = {
loading: false,
errorMessage: null,
entities: [] as ReadonlyArray<IMedicine>,
entity: defaultValue,
updating: false,
updateSuccess: false
};
export type MedicineState = Readonly<typeof initialState>;
// Reducer
export default (state: MedicineState = initialState, action): MedicineState => {
switch (action.type) {
case REQUEST(ACTION_TYPES.FETCH_MEDICINE_LIST):
case REQUEST(ACTION_TYPES.FETCH_MEDICINE):
return {
...state,
errorMessage: null,
updateSuccess: false,
loading: true
};
case REQUEST(ACTION_TYPES.CREATE_MEDICINE):
case REQUEST(ACTION_TYPES.UPDATE_MEDICINE):
case REQUEST(ACTION_TYPES.DELETE_MEDICINE):
return {
...state,
errorMessage: null,
updateSuccess: false,
updating: true
};
case FAILURE(ACTION_TYPES.FETCH_MEDICINE_LIST):
case FAILURE(ACTION_TYPES.FETCH_MEDICINE):
case FAILURE(ACTION_TYPES.CREATE_MEDICINE):
case FAILURE(ACTION_TYPES.UPDATE_MEDICINE):
case FAILURE(ACTION_TYPES.DELETE_MEDICINE):
return {
...state,
loading: false,
updating: false,
updateSuccess: false,
errorMessage: action.payload
};
case SUCCESS(ACTION_TYPES.FETCH_MEDICINE_LIST):
return {
...state,
loading: false,
entities: action.payload.data
};
case SUCCESS(ACTION_TYPES.FETCH_MEDICINE):
return {
...state,
loading: false,
entity: action.payload.data
};
case SUCCESS(ACTION_TYPES.CREATE_MEDICINE):
case SUCCESS(ACTION_TYPES.UPDATE_MEDICINE):
return {
...state,
updating: false,
updateSuccess: true,
entity: action.payload.data
};
case SUCCESS(ACTION_TYPES.DELETE_MEDICINE):
return {
...state,
updating: false,
updateSuccess: true,
entity: {}
};
case ACTION_TYPES.RESET:
return {
...initialState
};
default:
return state;
}
};
const apiUrl = 'api/medicines';
// Actions
export const getEntities: ICrudGetAllAction<IMedicine> = (page, size, sort) => ({
type: ACTION_TYPES.FETCH_MEDICINE_LIST,
payload: axios.get<IMedicine>(`${apiUrl}?cacheBuster=${new Date().getTime()}`)
});
export const getEntity: ICrudGetAction<IMedicine> = id => {
const requestUrl = `${apiUrl}/${id}`;
return {
type: ACTION_TYPES.FETCH_MEDICINE,
payload: axios.get<IMedicine>(requestUrl)
};
};
export const createEntity: ICrudPutAction<IMedicine> = entity => async dispatch => {
const result = await dispatch({
type: ACTION_TYPES.CREATE_MEDICINE,
payload: axios.post(apiUrl, cleanEntity(entity))
});
dispatch(getEntities());
return result;
};
export const updateEntity: ICrudPutAction<IMedicine> = entity => async dispatch => {
const result = await dispatch({
type: ACTION_TYPES.UPDATE_MEDICINE,
payload: axios.put(apiUrl, cleanEntity(entity))
});
dispatch(getEntities());
return result;
};
export const deleteEntity: ICrudDeleteAction<IMedicine> = id => async dispatch => {
const requestUrl = `${apiUrl}/${id}`;
const result = await dispatch({
type: ACTION_TYPES.DELETE_MEDICINE,
payload: axios.delete(requestUrl)
});
dispatch(getEntities());
return result;
};
export const reset = () => ({
type: ACTION_TYPES.RESET
});
The actions are triggered by redux-promise-middleware.
For an action FOO with an asynchronous payload, redux-promise-middleware will dispatch 3 actions:
FOO_PENDING, immediately
FOO_FULFILLED, once the promise is settled
FOO_REJECTED, if the promise is rejected
REQUEST, SUCCESS and FAILURE are just 3 simple functions in JHispter to facilitate the use of redux-promise-middleware.
export const REQUEST = actionType => `${actionType}_PENDING`;
export const SUCCESS = actionType => `${actionType}_FULFILLED`;
export const FAILURE = actionType => `${actionType}_REJECTED`;