Struggling to Get Array Data From Redux Store - reactjs

I have not been able to access items in an array that I am retrieving with redux. When I do console logs in the action itself, I am able to access array elements individually. But once that data makes its way to the component for display following the dispatch of actions, I have been unable to parse the data structure without error.
ListingActions.js:
If I do a console log here, I can parse through the different indices of the variable data without issue
export const getListings = () => async (dispatch) => {
try {
dispatch({ type: LISTING_REQUEST })
const { data } = await axios.get('/gmk')
// I can access the elements of the array here without a problem
dispatch({ type: LISTING_SUCCESS, payload: data })
} catch (error) {
dispatch({
type: LISTING_FAIL,
payload: error.response && error.response.data.message ? error.response.data.message : error.message,
})
}
ListingReducers.js:
export const listingReducer = (state = { itemListings: [] }, action) => {
switch (action.type) {
case LISTING_REQUEST:
return { loading: true, itemListings: [] }
case LISTING_SUCCESS:
return { loading: false, itemListings: action.payload }
case LISTING_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
Snippet from store.js:
const initialState = {
itemListings: [],
nope: { nopeItems: nopeItemsFromStorage },
keep: { keepItems: keepItemsFromStorage },
HomeScreen.js:
function HomeScreen() {
const dispatch = useDispatch()
const freshItemListings = useSelector((state) => state.itemListings)
const { loading, error, itemListings } = freshItemListings
useEffect(() => {
dispatch(getListings())
}, [dispatch])
return <div>{loading ? <p>Loading</p> : error ? <p>{error}</p> : itemListings.length}</div>
You'll see that I am trying to just access the length of itemListings. When I do so, I get an error: TypeError: Cannot read properties of undefined (reading 'length'). I have done other things like itemListings[0], and other methods just to see what my options are, but each has resulted in an error.

I believe this has to do with how you are updating itemListings in ListingReducers.js
case LISTING_SUCCESS:
return { loading: false, itemListings: action.payload }
You must update itemListing using the spread (...) operator to preserve the current state described here.
case LISTING_SUCCESS:
return {
...state,
loading: false,
itemListings: action.payload
}
In your code, the initial state is never actually getting updated in your reducer.
See Redux Documentation
They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values.
WARNING
In Redux, our reducers are never allowed to mutate the original / current state values!
// ❌ Illegal - by default, this will mutate the state!
state.value = 123
TIP
Reducers can only make copies of the original values, and then they can mutate the copies.
// ✅ This is safe, because we made a copy
return {
...state,
value: 123
}

Currently you have two itemListings in your app, which i think is causing some confusion.
In here :
const initialState = {
itemListings: [],
nope: { nopeItems: nopeItemsFromStorage },
keep: { keepItems: keepItemsFromStorage },
the default itemListings state from the store is an array.
Then in here:
const freshItemListings = useSelector((state) => state.itemListings)
freshItemListings is the same array and you are receiving it in the component
then in this line:
const { loading, error, itemListings } = freshItemListings
you are extracting a itemListings property from an array, which results to an error.
You have to define itemListings in the initial state as an object:
const initialState = {
itemListings: {itemListings:[]},
nope: { nopeItems: nopeItemsFromStorage },
keep: { keepItems: keepItemsFromStorage },
Another solution is as you are already defining a initial state for itemListings in its reducer, omit the itemListings property in initialState object,
const initialState = {
nope: { nopeItems: nopeItemsFromStorage },
keep: { keepItems: keepItemsFromStorage },

I discovered that the issue was with properly setting up the initial state in my store. I did change some of the variable names as suggested to make readability easier as well.
Changes to store.js:
const initialState = {
listingData: { itemListings: [], loading: true },
nope: { nopeItems: nopeItemsFromStorage },
keep: { keepItems: keepItemsFromStorage },
Setting both the loading and itemListing portions to the proper initial state was ultimately the answer.

Related

React Redux Reducers and Immutable Updates

In a previous scenario to Update Profile values, i created a new store slice (updatedProfileDetails) and stored a new object there, but i think this isn't the best practice to solve my problem (since i have now two slices profileDetails & updateProfileDetails),
Now I found in redux documentation that we can make immutable update reducers to change data in the same slice without mutate states but in this scenario (set conversation unseen to false) , the API don't send an object as a response but just a success message,
SO, I'm trying to passe my conversation ID from Action To reducer to check it and change a single value of this conversation[id]
but i'm unable to make it correctly
My code:
1- Action: where I'm supposed to send id after action success
export const updateSeenConversation = (id) => async (dispatch, getState) => {
try {
dispatch({
type: SEEN_CONVERSATIONS_REQUEST,
})
const {
userLogin: { userInfo },
} = getState()
const config = {
headers: {
// headers
},
}
const { data } = await axios.put(
`${process.env.REACT_APP_API_KEY}/conversation/unseen/${id}`,
"",
config
)
// if success data : {success:true , message:"..."}
dispatch({
type: SEEN_CONVERSATIONS_SUCCESS,
payload: id, // usually i send data sent from API as dispatch payload,, to check for the exact conversation to update i tried to send id
})
} catch (error) { //error...})
}
}
2- my reducer:
export const conversationsListReducer = (
state = { conversations: {} },
action
) => {
// get conversations works Fine and return a slice conversations{..} that i need to update
switch (action.type) {
case GET_CONVERSATIONS_REQUEST:
return { ...state, loading: true }
case GET_CONVERSATIONS_SUCCESS:
return { loading: false, conversations: action.payload }
case GET_CONVERSATIONS_FAIL:
return { loading: false, error: action.payload }
// here ERROR
// i cant access the exact value i want "unseen" **NB:CHECK store in IMAGE BELLOW**
case SEEN_CONVERSATIONS_REQUEST:
return { ...state, loading: true }
case SEEN_CONVERSATIONS_SUCCESS:
return {state.conversations.conversation.map((conversation) => {
if (conversation._id !== action.payload) {
return conversation // return without change if !== myid
}
return {
...conversation, // make a copy
unseen: false, // change this value to false/true
}
})}
case SEEN_CONVERSATIONS_FAIL:
return { loading: false, error: action.payload }
case USER_LOGOUT:
return { conversations: {} }
default:
return state
}
}
Redux store Slice Image Link
Thank You.
Ok, I still don't see where in your post you say what isn't working but I'm gathering it is something in the SEEN_CONVERSATIONS_SUCCESS reducer case since that's where you focused some comments.
From what I can tell in this reducer case, you are mutating the state invariant from { conversations: {} } to { [] } since mapping returns an array, and isn't a valid object. When updating state you need to shallow copy the state object and also any nested state being updated.
case SEEN_CONVERSATIONS_SUCCESS:
return {
...state, // <-- shallow copy state
conversations: {
...state.conversations // <-- shallow copy state.conversations
conversation: state.conversations.conversation.map( // <-- shallow copy conversation array
conversation => conversation._id === action.payload
? {
...conversation, // <-- shallow copy conversation
unseen: false
}
: conversation
),
}
};
Since this reducer case is accessing nested state properties that are possibly undefined, you should also fully declare your state.
const initialState = {
conversations: {
conversation: [], // <--- now access this deep won't throw error
},
loading: false,
error: null,
};
export const conversationsListReducer = (
state = initialState,
action
) => {
...
case USER_LOGOUT:
return initialState; // <-- reset to initial state

React Redux - How to make a double dispatch

I'm fetch some data from my API and it correctly works. But when a double dispatch on the same page the API doesn't work anymore. It's better code to explain it:
Server:
router.get("/", (req, res) => {
let sql = "SELECT * FROM design_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
router.get("/", (req, res) => {
let sql = "SELECT * FROM food_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
They work.
action.js
export const fetchDesignCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/designcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_DESIGN_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
export const fetchFoodCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/foodcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_FOOD_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
Both of them work perfectly.
reducer.js
const initalState = {
db: [],
loading: true,
designcat: [],
foodcat: [],
}
export default (state = initalState, action) => {
switch (action.type) {
// different cases
case FETCH_DESIGN_CAT:
return {
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
food: action.payload,
loading: false,
}
}
The reducer updates the states perfectly.
Page settings.js
const Settings = ({ designcat, foodcat, loading }) => {
const dispatch = useDispatch()
// ... code
useEffect(() => {
dispatch(fetchDesignCat()) // imported action
dispatch(fetchFoodCat()) // imported action
// eslint-disable-next-line
}, [])
// ... code that renders
const mapStateToProps = state => ({
designcat: state.appDb.designcat,
foodcat: state.appDb.foodcat,
loading: state.appDb.loading,
})
export default connect(mapStateToProps, { fetchDesignCat, fetchFoodCat })(
Settings
)
Now there's the problem. If I use just one dispatch it's fine I get one or the other. But if I use the both of them look like the if the second overrides the first. This sounds strange to me.
From my ReduxDevTools
For sure I'm mistaking somewhere. Any idea?
Thanks!
Your reducer does not merge the existing state with the new state, which is why each of the actions just replace the previous state. You'll want to copy over the other properties of the state and only replace the ones your specific action should replace. Here I'm using object spread to do a shallow copy of the previous state:
export default (state = initalState, action) => {
switch (action.type) {
case FETCH_DESIGN_CAT:
return {
...state, // <----
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
...state, // <----
food: action.payload,
loading: false,
}
}
}
Since the code is abbreviated, I'm assuming you're handling the default case correctly.
As an additional note, since you're using connect with the Settings component, you don't need to useDispatch and can just use the already connected action creators provided via props by connect:
const Settings = ({
designcat,
foodcat,
loading,
fetchDesignCat,
fetchFoodCat,
}) => {
// ... code
useEffect(() => {
fetchDesignCat();
fetchFoodCat();
}, [fetchDesignCat, fetchFoodCat]);
// ... code that renders
};
There's also a race condition in the code which may or may not be a problem to you. Since you start both FETCH_DESIGN_CAT and FETCH_FOOD_CAT at the same time and both of them set loading: false after finishing, when the first of them finishes, loading will be false but the other action will still be loading its data. If this case is known and handled in code (i.e., you don't trust that both items will be present in the state if loading is false) that's fine as well.
The solution to that would be either to combine the fetching of both of these categories into one thunk, or create separate sub-reducers for them with their own loading state properties. Or of course, you could manually set and unset loading.

How to update useReducer states in React?

I am trying to get the item from localStorage and pass it too useReducer State.
When i switch from one button to another button states is not updating. My initialState did update but states does not update
my initialStates and states on console.log both the values differ. states always stores the previous values of selected button Genid item value and initialStates stores the current selected values of selected button Genid item value but the main problem is did not update to useReducer states part
const Played = ( {Genid} ) => {
console.log("Played page ")
console.log("*************************************************")
console.log(Genid)
console.log(JSON.parse(localStorage.getItem(Genid)))
const initialState = {
DATA: JSON.parse(localStorage.getItem(Genid)) || [],
isFetching: localStorage.getItem(Genid) ? true : false,
hasError: false,
}
console.log(initialState);
const [states, dispatch] = useReducer(reducer, initialState)
console.log(states)
So the issue in your code was simple, the initialState wasn't populated in reducer because the displayid was undefined initially and then was set to ditto
The reason there is this behavior is because in Veritcal component you setDisplayId and that effect run ater initial render and by that time the undefined displayId is already being used by the Horizontal component
Now, there were other issues with your code too. You were updating sessionStorage with response.data.payload but there was no key as payload in response.data
Also Movement component is mapping on state.data but that is an object. Instead you need to map on state.data.abilities
Changed code snippets below
Movement.js
const Movement = ({ states }) => {
console.log(states);
return (
<div>
{states.data &&
states.data.abilities.map(ability => <div>{ability.ability.name}</div>)}
</div>
);
};
export default Movement;
Horizontal.js
console.log(displayid);
const initialState = {
// data: displayid? JSON.parse(sessionStorage.getItem(displayid)): [],
// isFetching: displayid && sessionStorage.getItem(displayid) ? true : false,
// hasError: false
};
const [states, dispatch] = useReducer(reducer, initialState);
// console.log(states);
useEffect(() => {
if (displayid && !sessionStorage.getItem(displayid)) {
console.log(displayid);
axios
.get(`https://pokeapi.co/api/v2/pokemon/${displayid}/`)
.then(response => {
console.log(response);
console.log(response.data);
dispatch({
type: "First",
payload: response.data
});
console.log(response.data);
sessionStorage.setItem(displayid, JSON.stringify(response.data));
})
.catch(error => {
console.log(error);
dispatch({
type: "Second"
});
});
} else if (displayid && sessionStorage.getItem(displayid)) {
// populate data from localStorage
const localData = {
data: JSON.parse(sessionStorage.getItem(displayid)) || [],
isFetching: sessionStorage.getItem(displayid) ? true : false,
hasError: false
};
dispatch({ type: "POPULATE", payload: localData });
}
}, [displayid]);
Working demo

How do I avoid using separate _PENDING _FULFILLED and _REJECTED actions with redux thunk?

I am writing my actions and reducers with thunks that dispatch _PENDING, _FULFILLED, and _REJECTED actions. However, I am wanting a better solution to avoid the boilerplate. I am migrating to Typescript which doubles this boilerplate by requiring an interface for each _PENDING, _FULFILLED, and _REJECTED action. It is just getting out of hand. Is there a way to get the same/similar functionality of my code without having three action types per thunk?
localUserReducer.js
const initialState = {
fetching: false,
fetched: false,
user: undefined,
errors: undefined,
};
export default function (state = initialState, action) {
switch (action.type) {
case 'GET_USER_PENDING':
return {
...state,
fetching: true,
};
case 'GET_USER_FULFILLED':
return {
...state,
fetching: false,
fetched: true,
user: action.payload,
};
case 'GET_USER_REJECTED':
return {
...state,
fetching: false,
errors: action.payload,
};
default:
return state;
}
}
localUserActions.js
import axios from 'axios';
export const getUser = () => async (dispatch) => {
dispatch({ type: 'GET_USER_PENDING' });
try {
const { data } = await axios.get('/api/auth/local/current');
dispatch({ type: 'GET_USER_FULFILLED', payload: data });
} catch (err) {
dispatch({ type: 'GET_USER_REJECTED', payload: err.response.data });
}
};
I may have a huge misunderstand of redux-thunk as I am a newbie. I don't understand how I can send _REJECTED actions if I use the implementation of Typescript and redux-thunk documented here: https://redux.js.org/recipes/usage-with-typescript#usage-with-redux-thunk
There is a way to get the similar functionality without having three action types per thunk, but it will have some impact on the rendering logic.
I'd recommend pushing the transient aspect of the async calls down to the data. So rather than marking your actions as _PENDING, _FULFILLED, and _REJECTED, mark your data that way, and have a single action.
localUser.js (new file for the user type)
// Use a discriminated union here to keep inapplicable states isolated
type User =
{ status: 'ABSENT' } |
{ status: 'PENDING' } |
{ status: 'FULLFILLED', data: { fullName: string } } |
{ status: 'REJECTED', error: string };
// a couple of constructors for the fullfilled and rejected data
function dataFulFilled(data: { fullName: string }) {
return ({ status: 'FULLFILLED', data });
}
function dataRejected(error: string) {
return ({ status: 'REJECTED', error });
}
localUserReducer.js
const initialState: { user: User } = { user: { status: 'ABSENT' } };
export default function (state = initialState, action): { user: User } {
switch (action.type) {
case 'USER_CHANGED':
return {
...state,
user: action.payload
};
default:
return state;
}
}
localUserActions.js
import axios from 'axios';
export const getUser = () => async (dispatch) => {
dispatch({ type: 'USER_CHANGED', payload: { status: 'PENDING' } });
try {
const { data } = await axios.get('/api/auth/local/current');
dispatch({ type: 'USER_CHANGED', payload: dataFulFilled(data) });
} catch (err) {
dispatch({ type: 'USER_CHANGED', payload: dataRejected(err.response.data) });
}
};
This will also remove the need for the multiple boolean fields (fetching and fetched) and isolate the various data states from accidental modification.
The changes to the render logic will be necessary, but will likely be an improvement. Rather than combinations of nested if-else statements using the booleans, a single switch can be used to handle the four cases of the data state.
Then you can invoke something like this from your render function...
function userElement(user: User) {
switch (user.status) {
case 'ABSENT':
return <></>;
case 'PENDING':
return <div>Fetching user information...Please be patient...</div>;
case 'FULLFILLED':
return <div>{user.data.fullName}</div>;
case 'REJECTED':
return <h1>The error is: {user.error}</h1>
}
}
I hope that helps. Good luck!

Redux - why loading everything in state at root

I am trying to understand Redux and having some difficulty.
I understand the concept of combineReducer, ie ....
var reducer = combineReducers({
user: userReducer,
products: productsReducer
})
But what if I have thousands of products, only available on the products page. I do not understand why I need to load them at root; to me this will slow the initial start up of the app for something that will not be needed unless the user goes to the products page.
Is this just the way it is with redux?
In Redux apps, you always build your entire state at the start. With Redux you have one store and one state - everything should trickle down from that one state to props on your components. However, that does not mean you actually need to load all the data into the state at launch, only that the structure needs to be there. This is why you should set up an initial state object for each reducer.
Let's say you have thousands of product records that you load from the database. In your products reducer you could do something like this:
const initialState = {
data: []
};
//use ES6 default parameters
function productsReducer (state = initialState, action) {
switch (action.type) {
case 'GET_PRODUCTS':
//return data from action
return {
data: action.result
};
default:
return state;
}
}
This means that when you start your app, if you use the full reducer you declared in your post, your application state will look like this:
{
user: {},
products: {
data: []
}
}
products.data will be an empty array until you fire an action that actually requires you to load the products data (i.e. you go to the Products page in your app or something). It's true that the products data will remain in your state if you then go elsewhere in your app, but this is a great thing - the next time you render the Products page you will already have the data at your disposal without having to do a database lookup.
In our app, we made an API for the products and it has limit of 15 per page. So our reducer goes like this.
collection: {
"total": 0,
"per_page": 0,
"current_page": 0,
"last_page": 0,
"from": 0,
"to": 0,
data: []
},
isFetching: false,
isFetchingError: false
on the first load we fetched limited amount of products, then we made a pagination out of it.. using selectors in redux https://github.com/rackt/reselect
Loading a thousands of data will get your app very slow.
const paginated = (state = initialState, action) => {
switch (action.type) {
case FETCH_PAGINATED_PRODUCTS:
return {
...state,
isFetching: true,
isFetchingError: false
};
case FETCH_PAGINATED_PRODUCTS_SUCCESS:
return {
...state,
collection: action.payload,
isFetching: false
};
case FETCH_PAGINATED_PRODUCTS_ERROR:
return {
...state,
isFetching: false,
isFetchingError: true
};
default:
return state
we have used axios for request:
https://github.com/mzabriskie/axios
Here's how we implement axios in redux-async
export function getAll(page = 1) {
return (dispatch, getState) => {
const state = getState();
const { filters } = state.products.paginated;
if ( state.products.paginated.isFetching ) {
return;
}
dispatch({ type: FETCH_PAGINATED_PRODUCTS });
return axios
.get(`products?page=${page}&limit=16&filters=${JSON.stringify(filters)}`)
.then((res) => dispatch({
type: FETCH_PAGINATED_PRODUCTS_SUCCESS,
payload: res.data
}))
.catch((res) => dispatch({
type: FETCH_PAGINATED_PRODUCTS_ERROR,
/*payload: res.data.error,*/
error: true
}));
}
}
export function get(id) {
return (dispatch, getState) => {
const state = getState();
if ( state.products.resource.isFetching ) {
return;
}
dispatch({ type: FETCH_PRODUCT });
return axios
.get(`products/${id}`)
.then((res) => dispatch({
type: FETCH_PRODUCT_SUCCESS,
payload: res.data.data
}))
.catch((res) => dispatch({
type: FETCH_PRODUCT_ERROR,
/*payload: new Error(res.data.error),*/
error: true
}));
}

Resources