I want to use the error value as an alert in my application. my server response also gave me the correct response. but when used in reacts error value shows undefined.
<b>Response Getting from API request</b>
{"success":false,"error":"Product Not Found"}
<b>code in react file</b>
const alert = useAlert();
const dispatch = useDispatch();
const { loading, error, products } = useSelector((state) => state.products);
useEffect(() => {
if (error) {
alert.error(error);
}
dispatch(getProduct());
}, [dispatch, error, alert]);
<b>Reducer</b>
export const productReducer = (state = { products: [] }, action) => {
switch (action.type) {
case ALL_PRODUCT_REQUEST:
return {
loading: true,
products: [],
};
case ALL_PRODUCT_SUCCESS:
return {
loading: false,
products: action.payload.products,
productsCount: action.payload.productsCount,
};
case ALL_PRODUCT_FAIL:
return {
loading: false,
error: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
<b>action</b>
export const getProduct = () => async (dispatch) => {
try {
dispatch({ type: ALL_PRODUCT_REQUEST });
const { data } = await axios.get("/api/v1/products");
dispatch({
type: ALL_PRODUCT_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ALL_PRODUCT_FAIL,
payload: error.response.data.message,
});
}
};
<b>store file</b>
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import {
productReducer,
productDetailsReducer,
} from "./reducers/productReducer";
const reducer = combineReducers({
products: productReducer,
productDetails: productDetailsReducer,
});
let initialState = {};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
Not able to see this function works alert. error(error).when I console this error it shows undefined. I want to show an alert "Product Not Found" when getting an error.
Not sure what is your redux structure is, but it seems like you are extracting out your state from the redux store incorrectly. So maybe try to replace this
const { loading, error, products } = useSelector((state) => state.products);
with
const { loading, error, products } = useSelector((state) => {
return {
loading: state.loading,
products: state.products,
error:state.error
});
EDIT:
State present in the useSelector is your root reducer which means it contains all the reducers in it, just for example, your product reducer, your cart reducer, user reducer and so on. Since, productReducer is being used in your root reducer, you should first access the productReducer and then its properties which should look like below (adjust the variables as per your variable names)
const { loading, error, products } = useSelector((state) => {
return {
loading: state.products.loading,
products: state.products.products,
error:state.products.error
});
This is because useSelector actually accesses data from the state (managed by Redux) on the frontend. This is data after fetching from the API so this is purely the data part from the API and not error or loading
Here, you have destructured fields of whatever gets returned when you try to access the products slice of your state. Which will not have fields like loading or error but an array of product objects.
To fix this, you should store error and loading responses from your API in your Redux state in a structured and only then can you access them in each of your React components accessing this state.
eg code :
const { loading, error, products } = useSelector((state) => {
// Curly braces with return should not be on next line
return {
loading: state.loading,
products: state.products,
error:state.error
});
Also, I would suggest using packages like react-query to address such use cases well
Alternative
Write your own hook for handling such async tasks
Code for hook
import { useCallback, useEffect, useState } from "react"
export default function useAsync(callback, dependencies = []) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState()
const [value, setValue] = useState()
// Simply manage 3 different states and update them as per the results of a Promise's resolution
// Here, we define a callback
const callbackMemoized = useCallback(() => {
setLoading(true)
setError(undefined)
setValue(undefined)
callback()
// ON SUCCESS -> Set the data from promise as "value"
.then(setValue)
// ON FAILURE -> Set the err from promise as "error"
.catch(setError)
// Irresp of fail or success, loading must stop after promise has ran
.finally(() => setLoading(false))
// This function runs everytime some dependency changes
}, dependencies)
// To run the callback function each time it itself changes i.e. when its dependencies change
useEffect(() => {
callbackMemoized()
}, [callbackMemoized])
return { loading, error, value }
}
Using this hook in the component
import useAsync from "./useAsync"
export default function AsyncComponent() {
const { loading, error, value } = useAsync(() => {
// 3 states and their updated versions are returned while the promise is getting resolved
return new Promise((resolve, reject) => {
const success = false
setTimeout(() => {
success ? resolve("Hi") : reject("Error")
}, 1000)
})
})
return (
<div>
<div>Loading: {loading.toString()}</div>
<div>{error}</div>
<div>{value}</div>
</div>
)
}
Related
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: []});
I fetched some data from my api by react-redux. My problem is that, since it is async I have to wait for the state to update its inital value in order to use them in the app. For example I have to use
products && products.length && products[n].img
syntax not to get undefined error when I try to access the fetched data. But when I use them at the first render just as
products[n].img
the app gives undefined as it should because redux fetches the data asynchronously. How can I bypass these steps so that I can use my desired state immediately?
React code
import React, { useEffect } from "react";
import {useDispatch, useSelector} from 'react-redux'
import { listPoduct } from "../actions/productActions";
const Examples = () => {
const dispatch = useDispatch()
const productList = useSelector(state => state.productList)
const {loading, error, products} = productList
useEffect(()=>{
dispatch(listPoduct())
},[dispatch])
console.log(products && products.length && products[0].img)
return(
<div>
...
</div>
)
}
export default Examples
Action
export function listPoduct() {
return (dispatch) => {
const baseUrl = "/api/images"
fetch(`${baseUrl}`)
.then(res => res.json())
.then(res => {
dispatch({
type: PRODUCT_LIST_SUCCESS,
payload: res
})
})
}
}
Reducer
export const productListReducer = (state = { products: [] }, action) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return {loading:true, products:[]}
case PRODUCT_LIST_SUCCESS:
return {loading:false, products: action.payload}
case PRODUCT_LIST_FAIL:
return {loading:false, error: action.payload}
default:
return state
}
}
Store
import {createStore, combineReducers, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import {productListReducer, productDetailsReducer} from './reducers/productReducer'
const reducer = combineReducers({
productList: productListReducer,
productDetails: productDetailsReducer
})
const initialState = {}
const middleware = [thunk]
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
)
export default store
The short answer is that you cannot. Sadly.
Your request is asynchronous, so there's just no data available immediately.
In your particular case, my advice would be to render some kind of spinner-loader conditionally (if loading is set to true) and only the loader.
In this case, if you have loading set to true, you will not reach the place where you can actually read the data (you will render-and-return before). And once loading switches back to false, you can now display the data safely as the request is finished and the data is in the right place.
The same applies to the failure state (as there's also no data available if the request failed).
Here's your modified code (as an example):
const Examples = () => {
const dispatch = useDispatch()
const productList = useSelector(state => state.productList)
const {loading, error, products} = productList
useEffect(()=>{
dispatch(listPoduct())
},[dispatch]);
if (loading) {
return (<div>Loading...</div>);
}
if (error) {
return (<div>Error: {error}</div>);
}
console.log(products && products.length && products[0].img)
return(
<div>
...
</div>
)
}
using nextjs for server-side-rendering trying to get the state from redux store in getServerSideProps(). but getting emtpy value.
getting data from redux in client side inside the component with const productList = useSelector(state => state.productList) const { loading, error, products } = productList works fine. but when using getServersideProps() im getting emtpy results.
index.js:
import store from '../redux/store'
export default function Home({products, error, loading}) {
const dispatch = useDispatch()
useEffect(() => {
dispatch(listProducts())
}, [dispatch])
return (
<>
<Header />
<Products loading={loading} error={error} products={products} />
<Footer />
</>
)
}
export async function getServerSideProps() {
const state = store.getState()
const { loading, error, products } = state.productList
return {props: {products: products, loading: loading, error: error}}
}
*note: even when i did console.log(store.getState()) inside the component its still returning empy array
reducer:
export const productListReducer = (state = { products: [] }, action) => {
switch (action.type) {
case 'PRODUCT_LIST_REQUEST':
return { loading: true, products: [] }
case 'PRODUCT_LIST_SUCCESS':
return { loading: false, products: action.payload }
case 'PRODUCT_LIST_FAIL':
return { loading: false, error: action.payload }
default:
return state
}
}
action:
import axios from 'axios'
export const listProducts = () => async (dispatch) => {
try {
dispatch({ type: 'PRODUCT_LIST_REQUEST' })
const { data } = await axios.get('/api/products')
dispatch({
type: 'PRODUCT_LIST_SUCCESS',
payload: data
})
} catch (error) {
dispatch({
type: 'PRODUCT_LIST_FAIL',
payload: error.response && error.response.data.message
? error.response.data.message : error.message
})
}
}
store.js:
const reducer = combineReducers({
productList: productListReducer,
categoryList: categoryListReducer,
})
const initialState = {}
const middleware = [thunk]
const store = createStore(
reducer, initialState, composeWithDevTools(applyMiddleware(...middleware))
)
export default store
try invoking getState() directly and don't forget to pass it as argument and also make sure you have passed the store to your app component
export async function getServerSideProps(getState) {
const state = getState()
const { loading, error, products } = state.productList
return {props: {products: products, loading: loading, error: error}}
}
The issue is useDispatch is a React-Redux function, but the store has not been connected to the React components.
Instead of useDispatch try store.dispatch instead:
import store from '../redux/store'
export default function Home({products, error, loading}) {
useEffect(() => {
store.dispatch(listProducts())
})
return (
<>
...
</>
)
}
Note, the array passed to useEffect controls when that effect is run, so it would not make sense to pass in the dispatch function. See this post for more details.
You could also connect the Redux store to the React components using React-Redux and keep using useDispatch.
I am in the process of cleaning up my fetching flags. By following the best practice, I am using a separate reducer to store all isFetching flags. In doing so I do not have to maintain multiple isFetchingFlags in my reducers.
Although I followed the explanation exactly, my isFetching flag does not jump from IsFetching: true (data currently being fetched) to IsFetching: false (data successfully fetched) in this new configuration. My fetching flag remains at IsFetching: false all the time. I have checked my code several times, but I cannot find my error.
Story Action:
// GET STORY
export const getStory = () => (dispatch, getState) => {
dispatch ({type: GET_STORY_REQUEST});
dispatch(showLoading());
axios.get( apiBase + "/story/retrieve/", tokenConfig(getState))
.then(res => {
dispatch({
type: GET_STORY_SUCCESS,
payload: res.data
});
dispatch(hideLoading());
})
.catch(err =>{
dispatch({
payload: returnErrors(err.response.data, err.response.status),
type: GET_STORY_FAILURE });
dispatch(hideLoading());
})
};
Loading Reducer
import {GET_STORY_SUCCESS,GET_STORY_REQUEST, GET_STORY_FAILURE} from "../actions/types.js";
const loadingReducer = (state = {}, action) => {
const { type } = action;
const matches = /(.*)_(REQUEST|SUCCESS|FAILURE)/.exec(type);
// not a *_REQUEST / *_SUCCESS / *_FAILURE actions, so we ignore them
if (!matches) return state;
const [, requestName, requestState] = matches;
return {
...state,
// Store whether a request is happening at the moment or not
// e.g. will be true when receiving GET_STORY_REQUEST
// and false when receiving GET_STORY_SUCCESS / GET_STORY_FAILURE
[requestName]: requestState === 'REQUEST',
};
Loading Selector
import _ from 'lodash';
export const createLoadingSelector = (actions) => (state) => {
// returns true only when all actions is not loading
return _(actions)
.some((action) => _.get(state, `api.loading.${action}`));
};
Story Component
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getStory} from '../../actions/story';
import { createLoadingSelector } from '../common/loading';
export class Story extends Component {
static propTypes = {
story: PropTypes.array.isRequired,
getStory: PropTypes.func.isRequired,
};
componentDidMount() {
this.props.getStory();
}
render() {
const { story } = this.props.story;
return (
<Fragment>
<h2>Stories</h2>
</Fragment>
);
}
}
const loadingSelector = createLoadingSelector(['GET_STORY']);
function mapStateToProps(state, ownProps) {
const story = state.story
const isFetching = loadingSelector(state)
console.log (isFetching)
console.log (story)
return { story, isFetching}
};
export default connect(
mapStateToProps,
{ getStory}
)(Story);
I'm happy for every clarification.
Are you using a middleware?
Remember that redux does not support asynchronous actions by default.
If not try to configure the redux-thunk middleware.
https://github.com/reduxjs/redux-thunk
I want to navigate to App screen or Auth screen, depending on the isUser prop after fetching it from the server and updating the redux store.
My first component AuthLoading.js which looks like this:
const AuthLoading = (props) => {
const isUser = useSelector((state) => state.authReducer.isUserExists);
const dispatch = useDispatch();
const fetchData = async () => {
const token = await TokensHandler.getTokenFromDevice();
dispatch(isTokenExists(token));
props.navigation.navigate(isUser ? "App" : "Auth");
};
useEffect(() => {
fetchData();
}, []);
My authActions.js looks like this:
export const isTokenExists = (token) => {
return (dispatch) => {
return HttpClient.get(ApiConfig.IDENTITY_PORT, "api/identity", {
userId: token,
}).then((response) => {
console.log(response);
dispatch({
type: IS_USER_EXISTS,
payload: response,
});
});
};
};
My authReducer.js looks like this:
const authReducer = (state = initialState, action) => {
switch (action.type) {
case IS_USER_EXISTS:
return {
...state,
isUserExists: action.payload,
};
default:
return state;
}
};
And the store:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
const configureStore = () => {
return createStore(rootReducer, applyMiddleware(thunk));
};
export default configureStore;
Unfortunately, the code inside AuthLoading.js isn't asynchronous, and isn't waiting for the updated isUser value and running the next line without it.
I've tried to .then after dispatch which still doesn't work.
I've tried changing async states and still couldn't find the problem.
I have no idea how to fix this.
Thanks for the help.
You can use another useEffect hook that runs when isUser changes and navigate inside it.
This useEffect runs once the component mounts and every time isUser changes, so someCondition can be anything that determines whether the navigation should happen or not.
useEffect(() => {
if(someCondition) {
props.navigation.navigate(isUser ? "App" : "Auth");
}
}, [isUser]); // add all required dependencies