Here is a simple flow of what I want to achieve:
And here is the components executions:
onClick function:
this.props.FetchQueryData({dateRange: this.props.dateData.dateRange, selections: selections});
Redux FetchQueryData function:
export const fetchSidebBatData = (sideBarData) => {
return dispatch => {
dispatch(fetchSidebBatDataBegin()); //For turning on isLoading
console.log("Started");
//For fetching the data
Object.keys(data).forEach(element => { //Here Data is an object to get the right endpoint
axios.post(`${ApiEndPoints.getDropDownHeadersByAdvertiserId}${element}`, sideBarData)
.then(response => {
data[element] = response.data;
//Once the action is completed turning off isLoading
dispatch(fetchSidebBatDataEnd());
})
.catch(e => {
console.log(e);
//If there's an error turning off isLoading
dispatch(fetchSidebBatDataEnd());
});
},console.log(data));
console.log("Ended");
};
};
Here is my reducer:
const reducer = ( state = initState, action ) => {
switch ( action.type ) {
case actionTypes.FETCH_SIDEBAR_DATA: return fetchSideBarData(state, action);
case actionTypes.FETCH_SIDEBAR_DATA_START: return fetchSideBarDataBegin(state);
case actionTypes.FETCH_SIDEBAR_DATA_END: return fetchSideBarDataEnd(state);
default: return state;
}
};
and here is my fetchSideBarDataBegin and fetchSideBarDataEnd functions:
const fetchSideBarDataBegin = (state) => {
const newState = state;
newState.isFetching = true;
return newState;
};
const fetchSideBarDataEnd = (state) => {
const newState = state;
newState.isFetching = false;
return newState;
};
I know I am missing something critical as I am not at all dispatching the data but I am totally new to redux and don't have a good context of how do you dispatch multiple actions within a reducer. Your help is highly appreciated. If you feel this is kind of complex example you can pick your own and just demonstrate the flow as I have shared in image which would be extremely helpful for me in understanding what to do next.
Note my major problem is that I want to set isLoading to true before fetching the data and that change should reflect in component and once the process is over then I want to turn it back to false.
Thanks in Advance
You can update the reducer with the terminology that is quite common in redux:
const initState = {
data: [],
error: false,
isLoading: false
};
const reducer = ( state = initState, action ) => {
switch ( action.type ) {
case actionTypes.FETCH_SIDEBAR_DATA_REQUEST: {
return {
...state,
data: [], // reset data if previous
error: false, // clear previous error if any
isLoading: true,
}
};
case actionTypes.FETCH_SIDEBAR_DATA_SUCCESS: {
return {
...state,
data: action.data,
isLoading: false,
}
};
case actionTypes.FETCH_SIDEBAR_DATA_ERROR: {
return {
...state,
error: true,
isLoading: false,
}
};
default: return state;
}
};
Notice to have three different action types: FETCH_SIDEBAR_DATA_REQUEST, FETCH_SIDEBAR_DATA_SUCCESS, and FETCH_SIDEBAR_DATA_ERROR
Edit: It seems you are doing multiple request in the thunk, you can handle that as:
export const fetchSidebBatData = (sideBarData) => {
return dispatch => {
dispatch(fetchSidebBatDataRequest());
const requests = Object.keys(data).map(element => {
return axios.post(`${ApiEndPoints.getDropDownHeadersByAdvertiserId}${element}`, sideBarData)
.then(response => {
return { [element]: response.data };
});
};
Promise.all(requests).then(data => {
dispatch(fetchSidebBatDataSuccess(data));
}).catch(error) {
dispatch(fetchSidebBatDataError(error));
}
};
};
Related
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 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,
};
I have a redux store as follows:
const initState = {
data: {},
isFetching: false,
};
I have a async function to fetch the data for the state:
const requestSideBarData = async(state, actions) => {
const request = actions.request
let newState = state;
const promises = [];
Object.keys(newState.data).forEach(element => {
promises.push(axios.post(`${ApiEndPoints.getDropDownHeaders}${element}`,request))
});
const results = await Promise.all(promises);
var index = 0;
Object.keys(newState.data).forEach(element => {
newState.data[element] = results[index++].data;
})
return newState;
};
And here are my begin and end reducers:
const fetchSideBarDataBegin = (state) => {
return {
...state,
isFetching: true,
}
};
const fetchSideBarDataEnd = (state) => {
return {
...state,
isFetching: false,
}
};
here's my actions function:
export const fetchSidebBatData = (dateData,selections) => {
return {
type: actions.FETCH_SIDEBAR_DATA_BEGIN, //Here there should be some combine action
dateData: dateData,
selections: selections,
requests: {...dateData,...selections}
}
};
And finally here's my reducer:
const reducer = ( state = initState, action ) => {
switch ( action.type ) {
case actionTypes.FETCH_SIDEBAR_DATA_REQUEST: return fetchSideBarData(state, action);
case actionTypes.FETCH_SIDEBAR_DATA_BEGIN: return fetchSideBarDataBegin(state);
case actionTypes.FETCH_SIDEBAR_DATA_END: return fetchSideBarDataEnd(state);
default: return state;
}
};
What I intend to do is to combine these three reducers into 1: so basically:
FETCH_SIDEBAR_DATA_BEGIN
FETCH_SIDEBAR_DATA_REQUEST
and finally
FETCH_SIDEBAR_DATA_END
what is the right way to perform this operation?
I am new to redux and I am trying to make it work with my application, but I have problems with understanding how to work with async actions in it. I have action that is api call. This action should be called as soon as my other state is not empty. I do not get any mistakes but do not think that my action is called since the data is empty. Can anybody help to understand what I am doing wrong?
Here is my actions.js. The wordsFetchData is the action I need to call:
export function wordsFetchDataSuccess(items){
return{
type: 'WORDS_FETCH_DATA_SUCCESS',
items
};
}
export function wordsAreFetching(bool){
return{
type: 'WORDS_ARE_FETCHING',
areFetching: bool
}
}
export function wordsHasErrored(bool) {
return {
type: 'WORDS_HAS_ERRORED',
hasErrored: bool
};
}
export function wordsFetchData(parsed) {
return (dispatch) => {
dispatch(wordsAreFetching(true));
fetch('URL', {
method: "POST",
headers: {
"Content-type": "application/json"
},body: JSON.stringify({
words: parsed
})
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(wordsAreFetching(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(wordsFetchDataSuccess(items)))
.catch(() => dispatch(wordsHasErrored(true)));
};
}
Here are my reducers:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
export function wordsAreFetching(state = false, action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return action.areFetching;
default:
return state;
}
}
export function wordsFetchHasErrored(state = false, action) {
switch (action.type) {
case 'WORDS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
This is my componentDidMount function:
componentDidMount = (state) => {
this.props.fetchData(state);
};
This is the function after terminating which the action should be called:
parseInput = async () => {
console.log(this.state.textInput);
let tempArray = this.state.textInput.split(" "); // `convert
string into array`
let newArray = tempArray.filter(word => word.endsWith("*"));
let filterArray = newArray.map(word => word.replace('*', ''));
await this.setState({filterArray: filterArray});
await this.props.updateData(this.state.filterArray);
if (this.state.projectID === "" && this.state.entity === "")
this.dialog.current.handleClickOpen();
else
if (this.state.filterArray.length !== 0)
this.componentDidMount(this.state.filterArray);
};
These are the mapStateToProps and mapDispatchToProps functions.
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.wordsFetchHasErrored,
areFetching: state.wordsAreFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: wordsFetchData
};
};
You only need one action for executing fetching (i.e WORDS_ARE_FETCHING), the rest of the cases (i.e WORDS_HAS_ERRORED & WORDS_FETCH_DATA_SUCCESS) can be handled inside your reducer.
Your action:
export function wordsAreFetching(){
return{
type: 'WORDS_ARE_FETCHING',
}
}
Your new reducer:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return {...state, error: false, areFetching: true};
case 'WORDS_FETCH_DATA_SUCCESS':
return {...state, items: action.payload , areFetching: false};
case 'WORDS_HAS_ERRORED':
return {...state, error: true, areFetching: false};
default:
return state;
}
Then you can trigger WORDS_FETCH_DATA_SUCCESS after you get the data from here:
export function wordsFetchData() {
try {
const response = await axios.get(YOUR_URL);
return dispatch({ type: WORDS_FETCH_DATA_SUCCESS, payload: response.data });
} catch (err) {
return dispatch({ type: WORDS_HAS_ERRORED });
}
}
Take a look at this example, it uses axios that can help you with async calls.
A couple of things:
No need to pass state into your componentDidMount, your mapDispatchToProps is not using it.
Here is a suggestion to structure those functions. They are a bit more concise and readable.
const mapStateToProps = ({items, wordsAreFetching, wordsFetchHasError}) => ({
items,
hasErrored: wordsFetchHasErrored,
areFetching: wordsAreFetching,
});
const mapDispatchToProps = () => ({
fetchData: wordsFetchData(),
});
Other notes and helpful things:
If you're using thunk, you'll have access to your entire redux store in here as a second argument. For example:
return (dispatch, getState) => {
dispatch(wordsAreFetching(true));
console.log('getState', getState());
const { words } = getState().items;
// This is a great place to do some checks to see if you _need_ to fetch any data!
// Maybe you already have it in your state?
if (!words.length) {
fetch('URL', {
method: "POST",
headers: {
......
}
})
I hope this helps, if you need anything else feel free to ask.
I am trying to execute the submitRecord action that POSTs data to a server. It is supposed to activate the SubmitRecordSuccess upon doing it so the data is posted to the server but is the SubmitRecordFail action that is executed instead as if the app wouldn't had posted the data to the server. However, when I check the data is online. What I'm a doing wrong so the wrong action is executed?
This is the action:
export const submitRecordSuccess = ( id, recordData ) => {
return {
type: actionTypes.SUBMIT_RECORD_SUCCESS,
recordId: id,
recordData: recordData
};
};
export const submitRecordFail = ( error ) => {
return {
type: actionTypes.SUBMIT_RECORD_FAIL,
error: error
};
};
export const submitRecordStart = () => {
return {
type: actionTypes.SUBMIT_RECORD_START
};
};
export const submitRecord = ( recordData ) =>{
return dispatch => {
dispatch(submitRecordStart());
axios.post( '/medicalRecords.json', recordData ).then( response => {
dispatch(submitRecordSuccess( response.data, recordData ));
} ).catch( error => {
dispatch(submitRecordFail( error ));
})
}
}
And this is the function that dispatches the action:
const mapDispatchToProps = dispatch => {
return {
onSubmitRecord: (recordData) => dispatch(actions.submitRecord(recordData))
};
};
Finally, this is the reducer:
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SUBMIT_RECORD_START:
return {
...state,
loading: true
};
case actionTypes.SUBMIT_RECORD_SUCCESS:
const newRecord = {
...action.recordData,
id: action.recordId
}
return {
...state,
medicalRecords: state.orders.concat(newRecord),
loading: false,
};
case actionTypes.SUBMIT_RECORD_FAIL:
return {
...state,
loading: false
};
default:
return state;
}
};
I console.log the error inside the .catch() and get
TypeError: Cannot read property 'concat' of undefined
at reducer (recordBuilder.js:24)
Initially, you state is initialised as:
state = {
medicalRecords: [],
loading: false,
}
You are getting the error because of this line.
medicalRecords: state.orders.concat(newRecord),
You are trying to access state.orders which is undefined and hence the error.
Please either add order: [] in initialState or use
medicalRecords: state.medicalRecords.concat(newRecord),
you can use spread operator for destructuring array also.
medicalRecords: [ ...state.medicalRecords, newRecord ],