Merging to two API calls into the same object with Redux - javascript

I am making a table of data that has users. One of the details in each row is their online availability. To do this I need to call two APIs (one for users and one for online availability). The online availability call needs to be called continuously at setintervals to make it update live. I am very confused about how and where to do this. I have merged the two API's by matching their Id's, but I do not know how to constantly call second API without calling the first continuously, as it would use alot of memory. Also I dont know where to do this within redux. Do i do it in the action? reducer? and why? I want this API state to be stored in the store so I can access this merged API across all components of the APP.
// action files
export const getUsers = ()=> async dispatch =>{
const usersResponse = await axios.get(users);
const userData = usersResponse.data.result;
dispatch({
type: GET_USERS,
payload: userData
})
}
export const getAvailability = () => async dispatch =>{
const availabilityResponse = await axios.get(availability);
const availabilityData = availabilityResponse.data.result;
dispatch({
type: GET_AVAILABILITY,
payload: availabilityData
})
}
// reducers
initial state = {
getUsers: [],
getAvailability: []
}
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return {
...state,
getUsers: action.payload
};
case GET_AVAILABILITY:
return {
...state,
getAvailability: action.payload
};
default:
return state;
}
}
//component
async componentDidMount() {
await this.props.getUsers();
await this.props.getAvailability();
let availabilityObject = {};
this.props.availability.map(availability => {
availabilityObject[availability.uuid] = availability;
});
if (typeof this.props.users != "undefined") {
this.props.users.map(user => {
user.availability = availabilityObject[user.uuid];
});
}
console.log(this.props.users)
//this doesn't show a merged Object of users and availability
}
const mapStateToProps = (state) => ({
users: state.getUsers,
status: state.getAvailability
});

Related

Redux thunk returns value before fetching data

I'm trying to fetch data from firebase Realtime Database and put it into the store.
Fetching data from Firebase has no problem. It works fine.
The problem is that the thunk is fulfilled even before data being fetched.
I first tried the code below but the returned 'result' was undefined because it had been assigned even before the onValue was finished.
export const fetchJournals = createAsyncThunk(
'journals/fetchJournals',
(userId) => {
let result;
const query = ref(db, `users/${userId}/journals`);
onValue(query, (snapshot) => {
result = snapshot.val();
})
return result;
}
);
So I tried another code like below using then but got this error code : Object(...)(...).then is not a function
const initialState = { posts: [], status: 'idle', error: null };
export const fetchJournals = createAsyncThunk(
'journals/fetchJournals',
(userId) => {
let result;
const query = ref(db, `users/${userId}/journals`);
onValue(query, (snapshot) => {
return snapshot.val();
}).then((res) => (result = res));
return result;
}
);
export const journalsSlice = createSlice({
name: 'journals',
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchJournals.fulfilled, (state, action) => {
state.posts = action.payload;
})
},
});
As far as I know (and I tried and failed), you can't change the state in creatAsyncThunk and should update it with addCase, I'm stuck and need help to update state after all the fetching to be finished.

How to use custom react query hook twice in the same component?

I have a custom hook like so for getting data using useQuery. The hook works fine, no problem there.
const getData = async (url) => {
try{
return await axios(url)
} catch(error){
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData', () => getData(url), {onSuccess})
}
However, if I call this hook twice in my component it will only fetch data from the first call even with a different URL. (Ignore the comments typo, that's intentional)
The call in my component:
const { data: commentss, isLoading: commentsIsLoading } = useGetData(`/comments/${params.id}`)
const { data: forumPost, isLoading: forumPostIsLoading } = useGetData(`/forum_posts/${params.id}`)
When I console.log forumPost in this case, it is the array of comments and not the forum post even though I am passing in a different endpoint.
How can I use this hook twice to get different data? Is it possible? I know I can just call parallel queries but I would like to use my hook if possible.
Since useQuery caches based on the queryKey, use the URL in that name
const getData = async(url) => {
try {
return await axios(url)
} catch (error) {
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData' + url, () => getData(url), {
onSuccess
})
}
//........
const {
data: commentss,
isLoading: commentsIsLoading
} = useGetData(`/comments/${params.id}`)
const {
data: forumPost,
isLoading: forumPostIsLoading
} = useGetData(`/forum_posts/${params.id}`)

dispatching redux action in middleware cause unexpected behavior

I am trying to understand how redux middleware works, during my experiment I have noticed that dispatching an action from redux middleware may result in an unexpected behavior.
I will try to explain the problem by simulating file upload as follow:
we have 3 actions:
const setProgress = (progress) => ({ type: SET_UPLOAD_PROGRESS, progress });
const setThumbnail = (thumbnail) => ({ type: SET_THUMBNAIL, thumbnail });
const calculateTotal = () => ({ type: CALCULATE_TOTAL });
Middleware to calculate total:
export const testMiddleware = (store) => (next) => (action) => {
if (action.type === 'CALCULATE_TOTAL') {
return next(action);
}
const result = next(action);
store.dispatch(calculateTotal());
return result;
};
Reducer:
const initialState = {
progress: 0,
total: 0,
thumbnail: ''
};
export function uploadReducer(state = initialState, action) {
switch (action.type) {
case SET_UPLOAD_PROGRESS:
state.progress = action.progress;
return { ...state };
case SET_THUMBNAIL:
state.thumbnail = action.thumbnail;
return { ...state };
case CALCULATE_TOTAL:
state.total += state.progress * 5;
return { ...state };
default:
return state;
}
}
here is the code for simulating file upload:
let cnt = 0;
// simulate upload progress
const setNewProgress = () => {
cnt += 2;
if (cnt > 5) return;
setTimeout(() => {
store.dispatch(setProgress(cnt * 2));
setNewProgress();
}, 1000);
};
setNewProgress();
// simulate thumbnail generating
setTimeout(() => {
store.dispatch(setThumbnail('blob:http://thumbnail.jpg'));
}, 2500);
Here is the sequence of events:
the first action works as intended and sets the progress value:
the problem starts from here; thumbnail suppose to be set by 'setThumbnail' but devtools shows that it has been set by 'calculateTotal', and every dispatch after that is mismatched:
What am I doing wrong here? is it by design? how can I dispatch an action in middleware without causing above problem?
This unexpected behavior may be cause by your uploadReducer not being pure, i.e. it is directly operating on your state (e.g. state.progress = action.progress;). Reducers should only return the new state and not modify existing state injected into your reducer by redux. hence, your reducer needs to look like this:
export function uploadReducer(state = initialState, action) {
switch (action.type) {
case SET_UPLOAD_PROGRESS:
return { ...state, progress: action.progress };
case SET_THUMBNAIL:
return { ...state, thumbnail: action.thumbnail };
case CALCULATE_TOTAL:
return { ...state, total: state.total + state.progress * 5 };
default:
return state;
}
}
how can I dispatch an action in middleware without causing above problem?
Your middleware looks fine (you are correctly preventing recursion and also returning the next() result (which is not needed in your example but still makes sense in a real application). Your actions look good as well (a style remark: You could wrap your action's payload in a payload property, which is a common convention).

javascript array contains values called empty

Not sure what is going on but when I update my store state by deleting items, it's saying that the items are empty.
see screenshot
and therefore the array is still of the same length?
these are my functions:
export const deleteUserFromFirebase = ({ id, name, username, email }) => {
return firebase
.database()
.ref("/users/" + id)
.set({
id: null
});
};
export const deleteUserFromStoreThenUpdateFirebase = user => {
return (dispatch, getState) => {
return dispatch(deleteUser(user)).then(() => {
return deleteUserFromFirebase(user);
});
};
};
export const deleteUser = user => {
return async dispatch => {
dispatch({ type: DELETE_USER, user: user });
};
};
and I call deleteUserFromStoreThenUpdateFirebase from a button click
why would it be doing this??
also my reducer does this:
case DELETE_USER: {
return {
...state,
users: state.users.filter(u => u.id !== action.user.id)
};
}
well confused right now
also firebase is showing the correct amount of nodes and when i refresh it's suppose to be pulling from firebase but it pulls the empty ones too

Redux-logic subscription cancel for given subscriber

I am trying to implement subscriptions with redux-logic middleware.
The idea is following: when data is fetched from server, to call callback for each subscriber passing fetched data as arguments.
// logic/subscriptions.js
const fetchLatestLogic = createLogic({
type: FETCH_LATEST_DATA,
latest: true,
process({getState, action}, dispatch, done) {
const {seriesType, nextUpdateTime} = action.payload;
const callbacks = getState()[seriesType][nextUpdateTime].callbacks
apiFetch(seriesType)
.then(data => {
callbacks.forEach(callback => callback(seriesType, data));
done()
})
}
})
const subscribeLogic = createLogic({
type: SUBSCRIPTIONS_SUBSCRIBE,
cancelType: SUBSCRIPTIONS_REMOVE,
process({getState, action, cancelled$}, dispatch) {
const {seriesType, nextUpdateTime, updateInterval, subscriberId, callback} = action.payload;
const interval = setInterval(() => {
dispatch(fetchLatestData(seriesType, nextUpdateTime))
}, updateInterval);
cancelled$.subscribe(() => {
clearInterval(interval)
})
}
})
// reducers/subscriptions.js
import update from 'immutability-helper';
update.extend('$autoArray', (value, object) => (object ? update(object, value) : update([], value)));
const initialState = {
'SERIESTYPE1': {}
'SERIESTYPE2': {}
}
// state modifications using 'immutable-helpers'
const serieAddSubscriberForTime = (seriesSubscriptions, time, subscriber) =>
update(seriesSubscriptions, {
[time]: {
$autoArray: {
$push: [subscriber]
}
}
});
// state modifications using 'immutable-helpers'
const serieRemoveSubscriberForTime = (seriesSubscriptions, subscriptionTime, subscriber) => {
const subscriptions = seriesSubscriptions[subscriptionTime].filter(s => s.subscriberId !== subscriber.subscriberId);
if (subscriptions.length === 0) {
return update(seriesSubscriptions, { $unset: [subscriptionTime] });
}
return { ...seriesSubscriptions, ...{ [subscriptionTime]: subscriptions }
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case SUBSCRIPTIONS_SUBSCRIBE: {
const { seriesType, nextUpdateTime, subscriber} = action.payload;
const newSubscriptionAdded = serieAddSubscriberForTime(state[seriesType], nextUpdateTime, subscriber);
const oldSubscriptionRemoved = serieRemoveSubscriberForTime(state[seriesType], nextUpdateTime, subscriber);
return update(state, { [seriesType]: { ...oldSubscriptionRemoved, ...newSubscriptionAdded } });
}
default:
return state;
}
}
How would it be possible to cancel running interval for given subscriber only? *(Without dispatching intervalID to reducer and saving it in state?)
Because by just dispatching action
cancelType: SUBSCRIPTIONS_REMOVE
will remove all intervals for all subscriptions with my current implementation.
UPDATE: actually there is much more smooth way to do the cancellation logic.
cancelled$
is an observable, and the RxJS .subscribe() accepts three functions as arguments:
[onNext] (Function): Function to invoke for each element in the observable sequence.
[onError] (Function): Function to invoke upon exceptional termination of the observable sequence.
[onCompleted] (Function): Function to invoke upon graceful termination of the observable sequence.
so the argument of onNext function is an emited value, and since in our case its the SUBSCRIPTIONS_REMOVE action, we can access its payload and do the cancellation depending on that payload:
cancelled$.subscribe(cancellAction => {
if (cancellAction.payload.subscriberId === subscriberId &&
cancellAction.payload.seriesType === seriesType) {
clearTimeout(runningTimeout);
}
})

Categories