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 };
Related
I've been trying to update state when fetching data from an API using the use reducer hook, but it's not updating even though my API request was successful, and I can see the data. Is there something I'm doing wrong? I've checked my console, and it doesn't give any errors. I've also crosschecked for misspellings
function Provider (props) {
let trackState = {
trackList: [],
heading: "Top 10 Tracks"
}
const reducer = (state, action) => {
switch (action.type) {
case "SEARCH TRACKS":
return {
...state,
trackList: action.payload,
heading: 'Search results'
}
case "NOT_SEARCHING":
return {
...state,
trackList: action.payload,
heading: 'TOP 1O Tracks',
}
default:
return state
}
}
const [state, dispatch] = useReducer(reducer, trackState)
React.useEffect(() => {
fetch(`https://thingproxy.freeboard.io/fetch/${API.url}${API.tracks}&apikey=${API.key}`)
.then(res => {
if(res.ok) {
return res.json()
}
else {
throw res;
}
})
.then(result => {
dispatch({ type: "NOT_SEARCHING", payload: result.message.body.track_list })
console.log(state)
}
)
}, [])
return (
<Context.Provider value={{trackState, API, dispatch}}>
{props.children}
</Context.Provider>
)
}
export {Provider, Context}
I think it's all looking fine. You are trying to log the state before it gets updated. Try to log in a different useEffect to see if the state is updated.
React.useEffect(() => {
console.log('State is changed!!',state)
}, [state])
hope this helps!!
I created a custom useFetch() hook so I can make my code more dynamic and less repetitive. The problem is that I can't display my data in App.js.
I get these errors:
Cannot read properties of undefined (reading 'map').
react-dom.development.js:67 Warning: Can't perform a React state
update on an unmounted component. This is a no-op, but it indicates a
memory leak in your application. To fix, cancel all subscriptions and
asynchronous tasks in a useEffect cleanup function.
I did a console.log(genres) to see if there are any errors from my custom hook, but it works fine, logs all the genres. The problem is caused as soon as I try to display my data using the map method.
CodeSandbox link
useFetch.js
import { useReducer, useEffect } from "react";
import axios from "axios";
const ACTIONS = {
API_REQUEST: "api-request",
FETCH_DATA: "fetch-data",
ERROR: "error",
};
const initialState = {
data: [],
loading: false,
error: null,
};
function reducer(state, { type, payload }) {
console.log(payload);
switch (type) {
case ACTIONS.API_REQUEST:
return { ...state, data: [], loading: true };
case ACTIONS.FETCH_DATA:
return { ...state, data: payload, loading: false };
case ACTIONS.ERROR:
return { ...state, data: [], error: payload };
default:
return state;
}
}
function useFetch(url) {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({ type: ACTIONS.API_REQUEST });
axios
.get(url)
.then((res) => {
dispatch({ type: ACTIONS.FETCH_DATA, payload: res.data });
})
.catch((e) => {
dispatch({ type: ACTIONS.ERROR, payload: e.error });
});
}, [url]);
return state;
}
export default useFetch;
App.js
import "./styles.css";
import useFetch from "./useFetch";
export default function App() {
const BASE_URL =
"https://api.themoviedb.org/3/genre/movie/list?api_key=${API_KEY}";
const { data: genres, loading, error } = useFetch(BASE_URL);
console.log(genres);
return (
<div className="App">
{genres.genres.map((genre) => (
<div key={genre.id}>{genre.name}</div>
))}
</div>
);
}
Your initial state has data as an array:
const initialState = {
data: [],
loading: false,
error: null,
};
And your App component is trying to read the property genres on that array as soon as it loads. There is no property on an array with that name, so genres.genres is undefined, and the map call on it will throw an error.
I would initialise initialState.data as {genres: []}, by passing the data container as another argument to your hook rather than hardcoding it into the hook file.
function useFetch(url, data) {
const [state, dispatch] = useReducer(reducer, {...initialState, data});
...
}
const { data: genres, loading, error } = useFetch(BASE_URL, {genres: []});
Don't know if I followed a wrong tutorial.
But when I try to launch a fetch action it is always return null array on the first few seconds.(based on my initial state) probably because Endpoint provided a response a little delayed.
or I'm not calling the stored valued correctly
Here the logs, you can see upon firing a OnPressIN two times and calling a function
LOG Running "uCon" with {"rootTag":1}
LOG [] <--- First onPress
LOG [] <--- Second onPress
LOG [{"member_id": 14987, "number": "(03) xxxx x495", "status_id": 3}, {"member_id": 14988, "number": "(03) xxxx x123", "status_id": 3}, {"member_id": 14990, "number": "(03) xxxx x125", "status_id": 3}] <--- Third onPress
Here's the action
export const setEmail = email => dispatch => {
dispatch({
type: SET_EMAIL,
payload: email,
});
};
export const getMemberEmail = (emailParam) => {
//It probably return first before waiting for the dispatch below to finish?
return (dispatch) => {
fetch(API_URL + "/query-email", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: emailParam
})
})
.then((res) => {
// If response was successful parse the json and dispatch an update
if (res.ok) {
res.json().then((emailCheck) => {
dispatch({
type: GET_MEMBER_EMAIL,
payload: emailCheck
});
});
} else {
// response wasn't successful so dispatch an error
console.log("Unable to fetch")
}
})
.catch((err) => {
// Runs if there is a general JavaScript error.
console.log(err);
});
};
};
Here's the reducer
import { SET_EMAIL, GET_MEMBER_EMAIL, SET_LOGIN_WARNING_1, SET_LOGIN_WARNING_2, SET_EMAIL_MODAL, SET_SERVICES_MODAL } from "../actions";
const initialState = {
memberEmailCheck: [],
email: '',
loginWarning1: false,
loginWarning2: false,
emailModal: true,
servicesModal: false,
}
function loginReducer(state = initialState, action) {
switch (action.type) {
case GET_MEMBER_EMAIL:
return { ...state, memberEmailCheck: action.payload };
case SET_EMAIL:
return { ...state, email: action.payload };
case SET_LOGIN_WARNING_1:
return { ...state, loginWarning1: action.payload }
case SET_LOGIN_WARNING_2:
return { ...state, loginWarning2: action.payload }
case SET_EMAIL_MODAL:
return { ...state, emailModal: action.payload }
case SET_SERVICES_MODAL:
return { ...state, servicesModal: action.payload }
default:
return state;
}
}
export default loginReducer
and lastly here'sthe store
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import modalReducer from "./reducers/modalReducers";
import loginReducer from "./reducers/loginReducers";
const rootReducer = combineReducers({ modalReducer, loginReducer });
export const Store = createStore(rootReducer, applyMiddleware(thunk));
Now the component
import { getMemberEmail, setEmail, setLoginWarning1, setLoginWarning2, setEmailModal, setServicesModal } from '../redux/actions';
import { useDispatch, useSelector } from 'react-redux';
export default function LogIn({ navigation, route }) {
const backgroundImage = "../../assets/images/background.png";
const logoHeader = "../../assets/images/uConnectedHeader.png";
const { memberEmailCheck, email, loginWarning1, loginWarning2, emailModal, servicesModal } = useSelector(state => state.loginReducer);
const dispatch = useDispatch();
// const [welcomeModal, setWelcomeModal] = useState(true);
const checkEMail = async () => {
await dispatch(getMemberEmail(email));
console.log(memberEmailCheck); <-- Here's the console that I used to track the result
// if (memberEmailCheck.message === 'The email address provided was not found to match a service') {
// dispatch(setLoginWarning1(true));
// } else {
// // dispatch(setEmailModal(false));
// // dispatch(setServicesModal(true));
// validateMemberEmail();
// console.log(memberEmailCheck);
// }
};
and this function is called via an onPressIn
<TextInput
style={styles.modalInput}
placeholder="Email"
onChangeText={(value) => dispatch(setEmail(value))} />
<Pressable
style={({ pressed }) => [
{
backgroundColor: "#FD6B89",
borderRadius: 10,
margin: 10,
opacity: pressed
? 0.5
: 1,
}]}
onPressIn={() => { checkEMail() }}
>
I highly suspect that I'm calling the state when it is not being stored properly.
But I exhausted on trying to find a way make the action to wait for dispatch GET_MEMBER_EMAIL to finish before returning to the component.
I tried async await, promise or I'm really knowledgeable on this one.
Hope you can help me.
Thank you!
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 am using redux promise middleware. I am trying to pass the value in Propsx to state. Props comes empty in useEffect. How can I transfer the contents of the props to state. Props value comes next.
action:
export function fetchBasket() {
return dispatch => {
dispatch({
type: 'GET_BASKET',
payload: axios.get('url', {
})
.then(response => response.data)
});
};
}
reducer:
const initialState = {
fetching: false,
error: {},
basket: []
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.GET_BASKET_PENDING:
return {
fetching: true
};
case types.GET_BASKET_FULFILLED:
return {
...state,
fetching: false,
basket: payload.result,
};
case types.GET_BASKET_REJECTED:
return {
fetching: false,
error: payload.result
};
default:
return state;
}
};
use in Component
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
[enter link description here][1]If you want to have values in your first run(Mount). fetch here ==> useLayoutEffect and this will gives the values in useEffect()[]. [uselayouteffect]: https://reactjs.org/docs/hooks-reference.html#uselayouteffect
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
Your props will update only in the next event loop cycle, to use react hooks data updation inside useEffect you need to useReducer https://reactjs.org/docs/hooks-reference.html#usereducer