reducer.state.props is undefined in nested actions react/redux - redux

Below are my action and reducer files - In my component state I am only seeing this.props.mainData - but others subdataOneData etc., are not being loaded in to the state - till reducer i see the right actions are being dispatched and I also see the data for sub - calls - but they are not reaching my component - I have mapStatetoprops - where I am doing
New issue: as per the updated code - when i print out payload in reducer - I see maindata with the api data but SubData [{}, {}, {}] ..?
Updated code:
import { GET_DATA_AND_SUBDATA } from '../constants/types';
export function getMainData() {
return async function getMainData(dispatch) {
const { data } = await getMainDataAPI();
const subData = data.map((item) => {
const endpoint = 'build with item.name';
return Request.get(endpoint);
});
console.log('subddd' + subData); prints -> **[object Promise],[object Promise],[object Promise]**
dispatch({
type: GET_DATA_AND_SUBDATA,
payload: { data, subData }
});
};
}
async function getMainDataAPI() {
const endpoint = 'url';
return Request.get(endpoint);
}

The problem lies on the way you dispatch the actions.
You are not providing data for mainData and subdataOneData at the same time.
export function getData() {
return async function getData(dispatch) {
const { data } = await getDataAPI();
// This will cause first re-render
dispatch({ type: GET_DATA, payload: data });
Object.keys(data).map((keyName, keyIndex) => {
const endpoint = 'ENDPOINT';
Request.get(endpoint).then((response) => {
// This will cause second re-render
dispatch({
type: GET_subdata + keyIndex,
payload: response.data });
});
return keyIndex;
});
};
}
At first render your subdataOneData is not availble yet.
You are not even specifying a default value in the reducer, therefore it will be undefined.
You can change your action thunk like this
export function getData() {
return async function getData(dispatch) {
const { data } = await getDataAPI();
const subDataResponse = await Promise.all(
Object.keys(data).map( () => {
const endpoint = 'ENDPOINT';
return Request.get(endpoint)
})
)
const subData = subDataResponse.map( response => response.data )
dispatch({
type: GET_DATA_AND_SUBDATA
payload: { data, subData }
});
};
}
And change your reducer accordingly in order to set all data at once.
export default function myReducer(state = {}, action) {
switch (action.type) {
case GET_DATA_AND_SUBDATA:
return {
...state,
mainData: action.payload.data,
subdataOneData: action.payload.subData[0],
subdataTwoData: action.payload.subData[1]
};
default:
return state;
}
}
Note: it's also a good practice to set your initial state in the reducer.
const initialState = {
mainData: // SET YOUR INITIAL DATA
subdataOneData: // SET YOUR INITIAL DATA
subdataTwoData: // SET YOUR INITIAL DATA
}
export default function myReducer(initialState, action) {

Related

I cannot understand WHY I cannot change state in Redux slice

I get the array of objects coming from backend, I get it with socket.io-client. Here we go!
//App.js
import Tickers from "./Components/TickersBoard";
import { actions as tickerActions } from "./slices/tickersSlice.js";
const socket = io.connect("http://localhost:4000");
function App() {
const dispatch = useDispatch();
useEffect(() => {
socket.on("connect", () => {
socket.emit("start");
socket.on("ticker", (quotes) => {
dispatch(tickerActions.setTickers(quotes));
});
});
}, [dispatch]);
After dispatching this array goes to Action called setTickers in the slice.
//slice.js
const tickersAdapter = createEntityAdapter();
const initialState = tickersAdapter.getInitialState();
const tickersSlice = createSlice({
name: "tickers",
initialState,
reducers: {
setTickers(state, { payload }) {
payload.forEach((ticker) => {
const tickerName = ticker.ticker;
const {
price,
exchange,
change,
change_percent,
dividend,
yeild,
last_trade_time,
} = ticker;
state.ids.push(tickerName);
const setStatus = () => {
if (ticker.yeild > state.entities[tickerName].yeild) {
return "rising";
} else if (ticker.yeild < state.entities[tickerName].yeild) {
return "falling";
} else return "noChange";
};
state.entities[tickerName] = {
status: setStatus(),
price,
exchange,
change,
change_percent,
dividend,
yeild,
last_trade_time,
};
return state;
});
return state;
},
},
});
But the state doesn't change. I tried to log state at the beginning, it's empty. After that I tried to log payload - it's ok, information is coming to action. I tried even to do so:
setTickers(state, { payload }) {
state = "debag";
console.log(state);
and I get such a stack of logs in console:
debug
debug
debug
3 debug
2 debug
and so on.

Composition API - Axios request in setup()

I am experimenting with Vue3's Composition API in a Laravel/VueJS/InertiaJS stack.
A practice that I have used a lot in Vue2 with this stack is to have 1 route that returns the Vue page component (eg. Invoices.vue) and then in the created() callback, I would trigger an axios call to an additional endpoint to fetch the actual data.
I am now trying to replicate a similar approach in Vue3 with composition API like so
export default {
components: {Loader, PageBase},
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: getInvoices(),
selectedInvoices: [],
});
async function getInvoices() {
loading.value = true;
return await axios.get(props.fetch_url).then(response => {
return response.data.data;
}).finally(() => {
loading.value = false;
})
}
function handleSelectionChange(selection) {
state.selectedInvoices = selection;
}
return {
loading,
state,
handleSelectionChange,
}
}
}
This however keeps on giving me the propise, rather than the actual data that is returned.
Changing it like so does work:
export default {
components: {Loader, PageBase},
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: [],
selectedInvoices: [],
});
axios.get(props.fetch_url).then(response => {
state.invoices = response.data.data;
}).finally(() => {
loading.value = false;
})
function handleSelectionChange(selection) {
state.selectedInvoices = selection;
}
return {
loading,
state,
handleSelectionChange,
}
}
}
I want to use function though, so I can re-use it for filtering etc.
Very curious to read how others are doing this.
I have been googling about it a bit, but cant seem to find relevant docu.
All feedback is highly welcomed.
I tried this now with async setup() and await getInvoices() and <Suspense> but it never displayed any content.
So this is how I'd do it, except I wouldn't and I'd use vuex and vuex-orm to store the invoices and fetch the state from the store.
<template>
<div>loading:{{ loading }}</div>
<div>state:{{ state }}</div>
</template>
<script>
import {defineComponent, ref, reactive} from "vue";
import axios from "axios";
export default defineComponent({
name: 'HelloWorld',
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: []
})
async function getInvoices() {
loading.value = true;
await axios.get(props.fetch_url).then(response => {
state.invoices = response.data;
}).finally(() => {
loading.value = false;
})
}
return {
getInvoices,
loading,
state,
}
},
async created() {
await this.getInvoices()
}
})
</script>
<style scoped>
</style>
This is of course similar to what you're doing in option 2.

React component does not update after Redux store changes

I am trying to fetch some data in a react component using the useEffect hook. After the initial render, fetchItems() gets the items and updates the store. However, items is still an empty object even after the store updates.
I might be using useEffects wrong. How do you use Redux with useEffects? I want to set a loading state for the component, but since the component only dispatches an action to fetch items (instead of directly calling the API), it does not know when the data is fetched and the store is updated so it can pull it.
Can someone please help figure out how to make sure that items object is updated after the saga fetch and the subsequent store update?
import React, { useState, useEffect } from "react";
import { connect } from 'react-redux';
import { useParams } from "react-router-dom";
const ItemComponent = ({ item, fetchItem }) => {
const { itemId } = useParams();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true)
fetchItem(itemId)
setIsLoading(false)
}, []);
console.log(item) // gives empty object even after the fetch and store update
}
const mapStateToProps = (state) => {
return {
item: state.item
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchItem: (itemId) => { dispatch(fetchItemActionCreator(itemId)) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ItemComponent);
fetchItemActionCreator is an action creator that creates the action to be dispatched.
My reducer and saga work fine as I can see the store actions and updates in the console.
If I pass the items object into the dependency array for useEffect, then there will be an infinite loop and the page keeps re-rendering.
Reducer:
const itemReducer = (state={}, { type, payload }) => {
switch(type) {
case ITEM_GET_SUCCESS:
return {...state, ...payload}
default: return state
}
}
fetchItemActionCreator:
import { createAction } from '#reduxjs/toolkit';
export const fetchItemActionCreator = createAction(ITEM_GET_PENDING);
Thank you very much in advance!
I want to set a loading state for the component
/** Action */
const getItem = () => dispatch => {
dispatch({ type: 'GET_ITEM_START' });
axios
.get('your api end point')
.then(res => {
const item = res.data;
dispatch({
type: 'GET_ITEM_SUCCESS',
payload: {
item,
},
});
})
.catch(error => {
dispatch({
type: 'GET_ITEM_FAIL',
payload: error,
});
});
};
/** Reducer */
const INITIAL_STATE = {
item: null,
error: '',
loading: false,
};
const itemReducer = (state = INITIAL_STATE, { type, payload }) => {
switch (type) {
case 'GET_ITEM_START':
return { ...state, error: '', loading: true };
case 'GET_ITEM_SUCCESS':
return { ...state, ...payload, loading: false };
case 'GET_ITEM_FAIL':
return { ...state, error: payload, loading: false };
default:
return state;
}
};
Then your could handle Loading state in your component
const ItemComponent = ({ fetchItem, item, loading, error }) => {
/** ... */
/**
Check for loading and show a spinner or anything like that
*/
useEffect(() => {
fetchItem(itemId);
}, []);
if (loading) return <ActivityIndicator />;
if (item) return <View>{/* renderItem */}</View>;
return null;
};

Failed to store data in redux store

.created a store
.created actions and reducers
.connected store to my component
.failed to store data in redux store
i have to store the add events in a calendar using redux ,the values are not stored in redux .
// Actions
export const saveUserEvents=(events)=>{
return {
type:'SAVE_USER_EVENTS',
payload: events
}
}
//Reducers
export const form=(state = initialState, action) => {
switch (action.type) {
case "SAVE_USER_EVENTS":
return {...state, events: action.payload};
default:
return state
}
}
// Store
let reducers=combineReducers({
form
})
export default createStore(
reducers,
composeWithDevTools(
applyMiddleware(thunk)
)
)
// Component
Form(){
var title = document.getElementById("title").value
var description = document.getElementById("description").value
var start = document.getElementById("start").value
var end = document.getElementById("end").value
if(title!=""){
alert("Event Added Successfully")
this.props
.form({
variables: {
title:title,
description:description,
start:start,
end: end,
user_id: localStorage.getItem("id")
}
})
.then(({ data }) => {
alert("data is "+JSON.stringify(data.form))
if(data.form.message == "Event saved"){
this.props.saveUserEvents(data.form);
//alert("success")
//this.props.history.push('/Calender')
window.location.href="/Calendar";
return true;
}else {
alert("failure")
document.getElementById("messagegeneralNumber").innerHTML = '';
}
})
.catch(error => {
alert("data is "+JSON.stringify(error))
})
}
else{
alert("data not loading")
}
}
//connected to store-redux. using mapstatetoprops and mapdispatchtoprops
const mapStateToProps=(state, ownProps)=>{
return {
form: state
}
}
const mapDispatchToProps={
saveUserEvents
}
const Event = compose(
graphql(mutations.FORM,{name:'form'})
)(AddEvent);
export default withRouter(connect(mapStateToProps
,mapDispatchToProps)
(Event));
Your mapDispatchToProps should look like this:
const mapDispatchToProps = (dispatch) => {
saveUserEvents: (events)=>{
dispatch(saveUserEvents(events)); <--- here is your action creator that have to be dispatched
}
}

redux not picking up an object dispatched via actions

I created a rootSaga in sagas.js as
function* fetchStuff(action) {
try {
yield put({type: 'INCREMENT'})
yield call(delay, 1000)
yield put({type: 'DECREMENT'})
const highlights = yield call(API.getStuff, action.data.myObject);
} catch (e) {
yield put({type: 'FETCH_STUFF_FAILED', message: e});
}
}
export default function* rootSaga() {
yield takeEvery('INIT_LOAD', fetchStuff);
}
I am calling the INIT_LOAD after thirdParty.method:
class myClass extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.load();
}
load = () => {
this.init = () => {
this.myObject = thirdParty.method(event => {
const action = {
type: 'INIT_LOAD',
payload: {
myObject: this.myObject
}
};
store.dispatch(action);
});
};
this.init();
};
render() {
return (
<div id="render-here" />
);
}
Passing the this.myObject in the action that is dispatched does not trigger the saga. If I change the action payload to a string, like the following, the saga is triggered.
const action = {
type: 'INIT_LOAD',
payload: {
myObject: 'this.myObject'
}
};
Why am I unable to pass this.myObject but a string is ok?
UPDATE: It is not a saga issue. I replicated the same issue with just plain redux. The rootReducer as
export default function rootReducer(state = initialState, action) {
switch (action.type) {
case 'INIT_LOAD':
return Object.assign({}, state, { myObject: action.payload.myObject });
default:
return state;
}
}
As I mentioned in the comment below, assigning it to an object Obj does not change the issue
let Obj = {};
...
load = () => {
this.init = () => {
Obj.myObject = thirdParty.method(event => {
const action = {
type: 'INIT_LOAD',
payload: {
myObj: Obj
}
};
store.dispatch(action);
});
};
this.init();
};
UPDATE2
I cleaned the code up & simply dispatched an action in the component that triggers the saga. Inside the saga is where I do the init(). I ran into another issue where the object that I was trying to save in the redux store has active socket sessions (which were given me cross-domain issues). Although I didn't solve my original problem, not storing a socket object made my problem go away.

Resources