calling async code in useState throwing null of undefined error - reactjs

I am pretty new to react and redux. I am trying to call get method API in useEffect hook but the function I am calling isn't getting invoked at all. the same function is invoked when I tried calling outside the useEffect and the state is also updated but I still got that error Cannot read property 'map' of null. so in both the cases, the common thing is getting the error and my code seems good to me. all other functions and states are working except this and I am not able to figure out what I am missing. any help very much is appreciated. thank you.
Orders.js
const orders = (props) => {
// getting the orders
useEffect(() => {
props.fetchOrders();
}, []);
let orders;
if(props.loading || props.error) {
orders = <Loading />
}
console.log(props)
orders = props.orders.map((order) => <Order
key={order.id}
ingredients={order.ingredients}
price={order.price}
customer={order.customerDetails}
/>);
return(
<div>
{orders}
</div>
)
}
const mapStateToProps = state => {
return {
orders: state.orders,
error: state.error,
loading: state.loading
}
}
const mapDispatchToProps = dispatch => {
return {
fetchOrders: () => dispatch(getOrders())
}
}
export default connect(mapStateToProps, mapDispatchToProps) (errorHandler(orders, axiosInstance));
action.js
// not invoking
const getOrders = () => {
return dispatch => {
axiosInstance.get('/orders.json').then(res => {
const ordersData = transformData(res);
dispatch(fetchOrders(ordersData));
}).catch(err => {
console.log(err)
})
}
}
const transformData = (response) => {
// simplified logic
const ordersData = [];
if(response.data) {
for (let key in response.data) {
ordersData.unshift({
...response.data[key],
id: key
})
}
}
return ordersData;
}
const fetchOrders = (ordersData) => {
return {
type: actionTypes.GET_ORDERS,
orders: ordersData
}
}
export { getOrders }
reducer.js
const reducer = (state = intialState, action) => {
switch (action.type) {
.
.
case actionType.GET_ORDERS:
return setOrders(state, action);
.
.
default:
return state;
}
}
const setOrders = (state, action) => {
return {
...state,
orders: action.orders.concat(),
error: false,
loading: false
}
}

Related

Why my dispatch action doesn't work in use effect after request?

I need help. I don't understand why my dispatch action doesn't work. I've redux store currency list and current currency.
My reducer:
export const currencyReducer = (
state: typeState = initialState,
action: TypeActionCurrency
): typeState => {
switch (action.type) {
case types.CURRENCY_FILL_LIST:
return { ...state, list: action.payload }
case types.CURRENCY_SET_CURRENT:
return {
...state,
current:
state.list.find(currency => currency._id === action.payload) ||
({} as ICurrency),
}
default:
return state
}
}
My actions:
export const setCurrencyList = (currencies: ICurrency[]) => ({
type: types.CURRENCY_FILL_LIST,
payload: currencies,
})
export const setCurrentCurrency = (_id: string) => ({
type: types.CURRENCY_SET_CURRENT,
payload: _id,
})
My useEffect:
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
}
}
fetchCurrencies()
}
}, [])
I want make request when load page and write currency list to Redux store, if we don't have current currency we write default currency from data.
There is one more strange thing, my redux extension shows that the state has changed, but when I receive it via the log or useSelector, it is empty
enter image description here
Thanks!
I am not 100% sure but it should work.
const [loader, setLoader] = useState(false);
const list = useSelector(state => state.list)
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
setLoader(true)
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
} finally {
setLoader(false)
}
}
fetchCurrencies()
}
}, [])
useEffect(() => {
console.log(list);
}, [loader])

Why my action result data is null when I try console the state using redux and react js

Hi developers I am currently studying react js with redux for frontend and I want to implement state management (Redux) to my sample project. My Backend I use laravel. Now I already set the Action, Services, Reducers. When I try to console log the props state to my Component it shows that my action data response is null.
Problem: The action data response is null only.
Here is my MapState & mapDisPatch
const mapStateToProps = (state) => {
return {
filterChartRes: state.dashboard.filterChartRes,
}
}
const mapDisPatchToProps = (dispatch) => {
return {
loadFilterChartData: (selectionRange) => dispatch(loadFilterChartData(selectionRange)),
}
}
My Action:
export const loadFilterChartData = (selectionRange) => {
return (dispatch) => {
getFilterChartData(selectionRange).then((res) => {
console.log(res)
dispatch({ type: 'FILTER_CHART_RESPONSE', res })
},
error => {
dispatch({ type: 'FILTER_CHART_ERROR', error });
}
)
}
}
My Services:
export const getFilterChartData = (selectionRange) => {
const http = new HttpService();
//let filterData = selectionRange !== "" ? selectionRange : null;
let url = "auth/filter_chart";
return http.getData(url)
.then(data => {
return data;
})
}
My Reducers:
const initState = {
filterChartRes: null,
filterChartErr: null
};
const DashboardReducer = (state = initState, action) => {
switch (action.type) {
case 'FILTER_CHART_RESPONSE':
return {
...state,
filterChartRes: action.res.data
}
case 'FILTER_CHART_ERROR':
return {
...state,
filterChartErr: 'action.error'
}
default:
return state
}
}
export default DashboardReducer;
My Render:
const {filterChartRes } = this.props
console.log(filterChartRes, "My Filter");
My Work Output:
Back End Controller:
public function filter_chart() {
return 'Sample Data';
}
Hope Someone help on my problem
To solved this issue:
You must call the props inside the DidMount
componentDidMount = () => {
this.props.loadFilterChartData()
}

Set value in useState after redux dispatch

My axios transaction is all done in the redux actions so that I can re-use the function. The issue is that, I need to fetch the data first which is done by redux and then re-assign the value in a state, but the data cannot be populated in the state. Below is how my code looks like.
Setting.js
...
import { getUserDetail } from './redux/actions/settingActions';
export default function Setting() {
const dispatch = useDispatch()
const { user } = useSelector(state => state.settingReducer)
const [userDetail, setUserDetail] = useState()
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user) // I want to set the user here
}, [])
...
}
settingActions.js
export const getUserDetail = () => (dispatch, getState) => {
axios.get('url-goes-here')
.then(res => {
dispatch({
type: SET_USER_DETAIL,
payload: { res.data }
})
})
.catch(error => {
throw error;
})
}
settingReducer
function initialState() {
return {
...
user: {}
}
}
export default function (state = initialState(), action) {
const { type, payload } = action;
switch (type) {
case SET_USER_DETAIL:
return {
...state,
user: payload
}
default:
return state
}
}
My purpose of doing this is because I want to do some user details update but I want it to be done within the same file.
put user and dispatch as dependency in useEffect
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user)
}, [user,dispatch])

React : retrieve info async with useReducer and useContext

I am trying to reproduce something I was doing with Reactjs/ Redux/ redux-thunk:
Show a spinner (during loading time)
Retrieve information from remote server
display information and remove spinner
The approach was to use useReducer and useContext for simulating redux as explained in this tutorial. For the async part, I was relying on redux-thunk, but I don't know if there is any alternative to it for useReducer. Here is my code:
The component itself :
const SearchForm: React.FC<unknown> = () => {
const { dispatch } = React.useContext(context);
// Fetch information when clickin on button
const getAgentsInfo = (event: React.MouseEvent<HTMLElement>) => {
const fetchData:() => Promise<void> = async () => {
fetchAgentsInfoBegin(dispatch); //show the spinner
const users = await fetchAgentsInfo(); // retrieve info
fetchAgentsInfoSuccess(dispatch, users); // show info and remove spinner
};
fetchData();
}
return (
...
)
The data fetcher file :
export const fetchAgentsInfo:any = () => {
const data = await fetch('xxxx');
return await data.json();
};
The Actions files:
export const fetchAgentsInfoBegin = (dispatch:any) => {
return dispatch({ type: 'FETCH_AGENTS_INFO_BEGIN'});
};
export const fetchAgentsInfoSuccess = (dispatch:any, users:any) => {
return dispatch({
type: 'FETCH_AGENTS_INFO_SUCCESS',
payload: users,
});
};
export const fetchAgentsInfoFailure = (dispatch:any) => {
return dispatch({
type: 'FETCH_AGENTS_INFO_FAILURE'
})
};
And my store itself :
import React, { createContext, useReducer } from 'react';
import {
ContextArgs,
ContextState,
ContextAction
} from './types';
// Reducer for updating the store based on the 'action.type'
const Reducer = (state: ContextState, action: ContextAction) => {
switch (action.type) {
case 'FETCH_AGENTS_INFO_BEGIN':
return {
...state,
isLoading:true,
};
case 'FETCH_AGENTS_INFO_SUCCESS':
return {
...state,
isLoading:false,
agentsList: action.payload,
};
case 'FETCH_AGENTS_INFO_FAILURE':
return {
...state,
isLoading:false,
agentsList: [] };
default:
return state;
}
};
const Context = createContext({} as ContextArgs);
// Initial state for the store
const initialState = {
agentsList: [],
selectedAgentId: 0,
isLoading:false,
};
export const ContextProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
const value = { state, dispatch };
Context.displayName = 'Context';
return (
<Context.Provider value={value}>{children}</Context.Provider>
);
};
export default Context;
I tried to partially reuse logic from this article but the spinner is never displayed (data are properly retrieved and displayed).
Your help will be appreciated !
Thanks
I don't see anything in the code you posted that could cause the problem you describe, maybe do console.log in the reducer to see what happends.
I do have a suggestion to change the code and move logic out of the component and into the action by using a sort of thunk action and replacing magic strings with constants:
//action types
const BEGIN = 'BEGIN',
SUCCESS = 'SUCCESS';
//kind of thunk action (cannot have getState)
const getData = () => (dispatch) => {
dispatch({ type: BEGIN });
setTimeout(() => dispatch({ type: SUCCESS }), 2000);
};
const reducer = (state, { type }) => {
if (type === BEGIN) {
return { ...state, loading: true };
}
if (type === SUCCESS) {
return { ...state, loading: false };
}
return state;
};
const DataContext = React.createContext();
const DataProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, {
loading: false,
});
//redux-thunk action would receive getState but
// cannot do that because it'll change thunkDispatch
// when state changes and could cause problems when
// used in effects as a dependency
const thunkDispatch = React.useCallback(
(action) =>
typeof action === 'function'
? action(dispatch)
: action,
[]
);
return (
<DataContext.Provider
value={{ state, dispatch: thunkDispatch }}
>
{children}
</DataContext.Provider>
);
};
const App = () => {
const { state, dispatch } = React.useContext(DataContext);
return (
<div>
<button
onClick={() => dispatch(getData())}
disabled={state.loading}
>
get data
</button>
<pre>{JSON.stringify(state, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(
<DataProvider>
<App />
</DataProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Redux dispatch data from component

How can I use the mapdispatchtoprops function correctly to dispatch to reducer? First, I get data from the server and want to send this data to the reducer. firebaseChatData function cannot be transferred to the mapdispatchtoprops because it is inside the component
Messages.js
const MessageUiBody = ( { messages, loading } ) => {
const userData = JSON.parse(localStorage.getItem("user-data"));
useEffect( () => {
const firebaseChatData = () => (dispatch) => {
firebaseDB.ref().child(API.firebaseEnv + "/messages/messageItem" + userData.account_id)
.on("value", snap => {
const firebaseChat = snap.val();
// console.log(firebaseChat)
dispatch(firebaseChatAction(firebaseChat))
});
};
}, []);
return(
<div> // code </div>
);
};
//Action
const firebaseChatAction = (firebaseChat) => ({
type: 'FIREBASE_MESSAGE',
firebaseChat
});
const mapDispatchToProps = (dispatch) => {
return {
data : () => {
dispatch(firebaseChatData())
}
}
};
export default connect(null, mapDispatchToProps)(MessageUiBody)
Reducer
export default function messages ( state = [], action = {}) {
switch (action.type) {
case 'FIREBASE_MESSAGE' :
state.data.messages.push(action.firebaseChat);
return {
...state
};
default:
return state
}
}
You'll have to change your code, because you're defining data as the prop function that will dispatch your action:
const mapDispatchToProps = dispatch => {
return {
data: (result) => dispatch(firebaseChatAction(result)),
}
}
After that change the line after the console log in your promise and use the data prop that you defined in your mapDispatch function:
const MessageUiBody = ( { data, messages, loading } ) => {
const userData = JSON.parse(localStorage.getItem("user-data"));
useEffect( () => {
const firebaseChatData = () => (dispatch) => {
firebaseDB.ref().child(API.firebaseEnv + "/messages/messageItem" + userData.account_id)
.on("value", snap => {
const firebaseChat = snap.val();
// here you call the data that will dispatch the firebaseChatAction
data(firebaseChat)
});
};
}, []);
return(
<div> // code </div>
);
};
Also is worth to notice that you don't have to push items in your state, you can't mutate the current state, so always try to generate new items instead of modifying the existing one, something like this:
export default function messages ( state = [], action = {}) {
switch (action.type) {
case 'FIREBASE_MESSAGE' :
return {
...state,
data: {
...state.data,
messages: [...state.data.messages, action.firebaseChat]
}
};
default:
return state
}
}
With the spread operator you are returning a new array that contains the original state.data.messages array and will add the firebaseChat item as well.

Resources