Why is the Redux state not updating with the API data? - redux

I've been following the process for making an API call and storing it in global state with Redux using this project that I got from a Medium article. So far everything seems to work alright, no errors, but when I go to retrieve the global state there is nothing there. It doesn't seem to have been updated by the action that makes the API call. The relevant bits of code are as follows:
in reducers.js:
const initialState = {
mods: [],
pagination: { pageSize: 15, numPages: 1 },
sortFilter: "mostPopular",
};
const globalState = (state = initialState, action) => {
switch (action.type) {
case SET_MOD_LIST:
return { ...state, mods: state.mods };
case SET_MOD_DETAILS:
return { ...state };
default:
return state;
}
};
const rootReducer = combineReducers({
globalState,
});
export default rootReducer;
in actions.js:
export const fetchModList = (pagination, sortFilter = "mostPopular") => {
const { pageSize = 15, numPages = 1 } = pagination ?? {};
return async (dispatch) => {
const response = await fetch(
`https://www.myapi.com/mods?page=${numPages}&pageSize=${pageSize}&sortBy=${sortFilter}`
);
const resData = await response.json();
dispatch({ type: SET_MOD_LIST, mods: resData });
};
};
in index.js (Next.js root page):
const mods = useSelector((state) => state);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchModList({pageSize:2}));
}, [dispatch]);
console.log({mods})
This is 100% a result of Redux ignorance, this is my first project using it which I'm doing for an interview. Any help would be much appreciated!

Looks like you're setting mods to its own value mods: state.mods. Did you mean to set a value from action.payload rather than state.mods?

Related

Displaying data from SWAPI

I am trying to display film details from this API https://swapi.dev/api/films in NextJS but I keep getting an error. It has something to do with the getStaticPath function. I am using this code: any help welcome.
import fetch from 'isomorphic-unfetch';
export const getStaticPaths = async () => {
const res = await fetch('https://swapi.dev/api/films');
const data = await res.json();
console.log("This is " + JSON.stringify(data));
const paths = data.results.map(film => {
return {
params: { id: film.episode_id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch(`https://swapi.dev/api/films` + id);
const data = await res.json();
return {
props: { film: data },
};
};
const Details = ({ film }) => {
return (
<div>
<h1>Episode {film.episode_id}</h1>
<h1>{film.title}</h1>
</div>
);
};
export default Details;
I am expecting the episode ID and title to display
enter image description here
I have tried taking 'results' out of 'data.results.map', so 'data.map' but just results in a error says data.map is not a function...I guess because data is an object not an array. But results is an array so I am still lost. I do think the issue lies here somewhere though...

Redux state doesn't update immediately

I have super simple question
Why my redux state doesn't update immediately?
const { reducer, actions } = createSlice({
name: "professionals",
initialState: {
loading: false,
lastFetchList: undefined,
list: undefined,
professional: undefined,
filters: {
virtual: false
}
},
reducers: {
professionalsListRequested: (professionals, action) => {
if (action.payload.withLoading) professionals.loading = true;
},
professionalsListRequestFailed: (professionals, action) => {
professionals.loading = false;
},
professionalsListReceived: (professionals, action) => {
professionals.lastFetchList = Date.now();
professionals.list = action.payload.data.dataArr;
professionals.loading = false;
},
virtualUpdated: (categories, action) => {
categories.filters.virtual = action.payload;
}
},
});
export const { virtualUpdated } = actions;
export default reducer;
it is my slice.
and here is code of the component :
const dispatch = useDispatch();
const filters = useSelector((state) => state.professionals.filters);
const handlePressOnVirtual = async () => {
console.log("Before" , filters.virtual)
await dispatch(virtualUpdated(!filters.virtual));
console.log("after" , filters.virtual)
};
when handlePressOnVirtual function is called the both console.log(s) print previous value of the state.
When you are still in handlePressOnVirtual function, you are still in a closure, so all the references will still be your existing filters
So you would need to wait for another re-render for useSelector to invoke again then the new values will come.
One way to see the latest changes is to put your log inside a useEffect:
useEffect(() => {
console.log("after" , filters.virtual)
},[filters.virtual]);

Dispatch function seems to run more than one time after I test on stand alone app expo-deeplink

It sounds so weird to me and I have no idea what's wrong here because everything is fine in a development environment. So the way app works are simple, user sign in, choose it's therapist then pay for it and after successful payment, booking is confirmed, but the problem is booking is being booked exactly 3 times in firebase real-time database no matter what and I don't know why... (in the development area all is fine and it's gonna book just once as the user requested)
here's my code of booking:
const bookingHandler = () => {
Linking.openURL('http://www.medicalbookingapp.cloudsite.ir/sendPay.php');
}
const handler = (e) => handleOpenUrl(e.url);
useEffect(() => {
Linking.addEventListener('url', handler)
return () => {
Linking.removeEventListener('url', handler);
}
});
const handleOpenUrl = useCallback((url) => {
const route = url.replace(/.*?:\/\/\w*:\w*\/\W/g, '') // exp://.... --> ''
const id = route.split('=')[1]
if (id == 1) {
handleDispatch();
toggleModal();
} else if (id == 0) {
console.log('purchase failed...');
toggleModal();
}
});
const handleDispatch = useCallback(() => {
dispatch(
BookingActions.addBooking(
therapistId,
therapistFirstName,
therapistLastName,
selected.title,
moment(selectedDate).format("YYYY-MMM-DD"),
selected.slots,
)
);
dispatch(
doctorActions.updateTherapists(therapistId, selected.slots, selectedDate, selected.title, selectedPlanIndex, selectedTimeIndex)
);
setBookingConfirm(true)
})
booking action:
export const addBooking = (therapistId, therapistFirstName, therapistLastName, sessionTime, sessionDate, slotTaken) => {
return async (dispatch, getState) => {
let userId = firebase.auth().currentUser.uid
const confirmDate = moment(new Date()).format("ddd DD MMMM YYYY")
const response = await fetch(
`https://mymedicalbooking.firebaseio.com/bookings/${userId}.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
userId,
therapistId,
confirmDate,
therapistFirstName,
therapistLastName,
sessionTime,
sessionDate,
slotTaken
})
}
);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const resData = await response.json();
dispatch({
type: ADD_BOOKING,
bookingData: {
userId: userId,
therapistId: therapistId,
therapistFirstName: therapistFirstName,
therapistLastName: therapistLastName,
sessionTime: sessionTime,
sessionDate: sessionDate
}
});
};
};
Booking reducer:
const initialState = {
bookings: [],
userBookings: []
};
export default (state = initialState, action) => {
switch (action.type) {
case ADD_BOOKING:
const newBooking = new Booking(
action.bookingData.id,
action.bookingData.therapistId,
action.bookingData.therapistFirstName,
action.bookingData.therapistLastName,
action.bookingData.bookingdate
);
return {
...state,
bookings: state.bookings.concat(newBooking)
};
case FETCH_BOOKING:
const userBookings = action.userBookings;
return {
...state,
userBookings: userBookings
};
}
return state;
};
also, I use expo, SDK 38, Firebase as a database.
I really need to solve this, please if you have any idea don't hesitate to leave a comment or answer all of them kindly appreciated.
UPDATE:
I commented out all deep linking functionality and test the result, it's all fine. so I think the problem is with the eventListener or how I implemented my deep linking code but I still don't figure out what's wrong with the code that does fine in expo and has a bug in stand-alone.
UPDATE 2
I tried to add dependency array as suggested but still I have same problem..
there is an issue in expo-linking which on the standalone detached android app: event url fires multiple times ISSUE
I just wrapped my handling function in lodash's debounce with 1000ms wait
install lodash like this
yarn add lodash
import _ from 'lodash';
const handleOpenUrl = _.debounce((event) => {
// here is other logic
},1000);
here is your code
just add an empty dependency array into useEffect and use useCallback like this
useEffect(() => {
Linking.addEventListener('url', handleOpenUrl)
return () => {
Linking.removeEventListener('url', handleOpenUrl);
}
},[]); //like this []
const handleOpenUrl = _.debounce((url) => {
const route = url.replace(/.*?:\/\/\w*:\w*\/\W/g, '') // exp://.... --> ''
const id = route.split('=')[1]
if (id == 1) {
handleDispatch();
toggleModal();
} else if (id == 0) {
console.log('purchase failed...');
toggleModal();
}
},1000); //like this []
const handleDispatch = useCallback(() => {
dispatch(
BookingActions.addBooking(
therapistId,
therapistFirstName,
therapistLastName,
selected.title,
moment(selectedDate).format("YYYY-MMM-DD"),
selected.slots,
)
);
dispatch(
doctorActions.updateTherapists(therapistId, selected.slots, selectedDate, selected.title, selectedPlanIndex, selectedTimeIndex)
);
setBookingConfirm(true)
},[selected])

Testing Async Action with lambda chaining

I am learning how to unit test a simple action creator as seen below and want to find out the best way to test it. I've been going off an example from the redux docs on writing tests but wonder if it is possible to test async actions with lambda chaining.
Action:
export const toggleSelect = (id, key) => dispatch => {
return dispatch({
type: TOGGLE_LIST_ITEM,
payload: { id, key },
});
};
Test (jest)
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import * as actions from '../';
import * as types from '../types';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('list actions', () => {
it('should create an action to unselect all list items', () => {
const id = '123';
const key = 'selectedProspects';
const expectedAction = {
type: types.UNSELECT_ALL_OF_TYPE,
key,
};
const store = mockStore();
return actions.toggleSelect(id, key).then(() => {
expect(store.getActions()).toEqual(expectedAction);
});
});
});
does anyone know of a good way to test this? I am not sure if this not working being indicative to writing more testable code, or if I am just missing something.
You are very close, you just need to make use of dispatch:
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('list actions', () => {
it('should create an action to unselect all list items', () => {
const id = '123';
const key = 'selectedProspects';
const expectedAction = {
type: types.UNSELECT_ALL_OF_TYPE,
key,
};
const store = mockStore();
return store.dispatch(actions.toggleSelect(id, key)).then(() => {
expect(store.getActions()).toEqual(expectedAction);
});
});
});
Your action itself doesn't need to be a thunk. You could easily write that as an action creator... i.e.
export function toggleSelect(id, key) {
return {
type: TOGGLE_LIST_ITEM,
payload: { id, key },
};
}

Reselect Cannot read property 'get' of undefined

I am using reselect and react redux. I am trying to make a selector for a basic modal implementation.
my selector is
const selectModal = (state) => state.get('modal');
which throws the error of
Cannot read property 'get' of undefined
edit: It has been requested I show how I call select modal, though it should make no difference.
const mapStateToProps = createStructuredSelector({
isVisible: selectModalIsVisible(),
});
const mapDispatchToProps = {
hideModal,
showModal
};
export default connect(mapStateToProps, mapDispatchToProps)(Modal);
I believe this means the modal state container is not being found
Perhaps I am setting up my reducer or store incorrectly. My reducer is
function modalReducer(state = initialState, action) {
switch (action.type) {
case HIDE_MODAL:
return state.set(
'isVisible', false);
case SHOW_MODAL:
return state.set(
'isVisible', true);
default:
return state;
}
}
which is combined with combine reducers into a glob
export default function createReducer(asyncReducers){
return combineReducers({
route: routeReducer,
auth: authReducer,
modal: modalReducer,
...asyncReducers
});
}
and then injected into my store
function configureStore(initialState = {}, history) {
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
]
const store = createStore(
createReducer(),
fromJS(initialState),
compose(...enhancers)
);
store.runSaga = sagaMiddleware.run;
//store.close = () => store.dispatch(END)
store.runSaga(sagas);
store.asyncReducers = {};
return store;
}
var initialState = {}
const store = configureStore(fromJS(initialState), browserHistory);
The error within reselect is at lines 73/74 params = dependencies.map
var selector = function selector(state, props) {
for (var _len4 = arguments.length, args = Array(_len4 > 2 ? _len4 - 2 : 0), _key4 = 2; _key4 < _len4; _key4++) {
args[_key4 - 2] = arguments[_key4];
}
var params = dependencies.map(function (dependency) {
return dependency.apply(undefined, [state, props].concat(args));
});
return memoizedResultFunc.apply(undefined, _toConsumableArray(params));
};
So what am I doing wrong, do I need to do something with immutableJS differently to access the modal, or is my setup for the app incorrect? Thank you in advance for your feedback.
If you're using selectModal like you're using selectModalIsVisible, then your syntax is wrong. I'm pretty sure createStructuredSelector does not understand () => (state) => state.get('modal'). It would only accept (state) => state.get('modal')
Typically, my usages of createStructuredSelector will look like either
const getThing = (state, props) => state.things[props.thingId];
const getModal = state => state.get('modal');
const mapStateToProps = createStructuredSelector({
thing: getThing, // notice no parens
modal: getModal, // notice no parens
})
OR if I need selector factories:
// just pretend this selector was more complicated and needed memoization
const makeGetThing = () => createSelector(
state => state.things,
(state, props) => props.thingId,
(things, thingId) => things[thingId]);
const getModal = state => state.get('modal');
const makeMapStateToProps = () => createStructuredSelector({
thing: makeGetThing(), // yes parens
modal: getModal, // no parens
})

Resources