Pulling Redux data with userID param error - React / Node / Express - reactjs

I am trying to pull a log of user data belonging to userID from my backend in React. The backend API is working correctly with Postman however I can't get this working with the userID. It is console logging the correctly userID in the component however I am yet to get a Thunk Redux store data working with a param passed in.
Is there anywhere obvious I am going wrong from looking at the code below? I have had my Redux working previously with data not using a param so I know it is not an issue with my store / redux index etc.
component
const userDiveLogList = useSelector(state => state.user.userDiveLogList);
const [userDiveLog, setUserDiveLog] = useState({
userDiveLogList: [],
expanded: false
})
const dispatch = useDispatch();
useEffect(() => {
dispatch(requireUserDiveLogData(user.userID));
}, []);
action
// load a users diveLog
export const requireUserDiveLogData = createAsyncThunk('diveLog/requireData',
async (userID, thunkAPI) => {
const response = await userDiveLogList(userID);
return response.data;
},
{
condition: (_, { getState }) => {
const { user } = getState();
if (user.didLoadData) {
return false;
}
}
}
)
reducer
export const userSlice = createSlice({
name: 'user',
initialState: {
userDiveLogList: [],
didLoadData: false,
},
reducers: {
[requireUserDiveLogData.pending.type]: (state) => {
state.didLoadData = true;
},
[requireUserDiveLogData.fulfilled.type]: (state, action) => {
return {
...state,
...action.payload
}
},
service
// returns list of dives per user from db
export const userDiveLogList = (userID) => {
return axios.get(API_URL + `userdiveloglist/${userID}`);
};
Update
Does this have something to do with my action call for when data gets called if it is already called? I have the getState condition const as userDiveLog when it is in the user branch and it is user/userDiveLogList in the branch, it is also named userDiveLogList when it is being sent at the backend. I can see the backend request being made for the correct user number but nothing is returning.

Related

ReactJS Redux Toolkit - Fetching data from an API based on an Id does not get the second Id if called again

I am use ReactJS and Redux Toolkit (not using RTK Query). I setup a slice to retrieve data from an API based on an Id. The first time I fetch the data everything works fine. If I try to fetch data for a different Id nothing is fetched as it appears it just returns what is there. Am I using redux incorrectly or am I missing something easy. Below is the code. Further more, I would like to use the same piece of the store for different data types but the data is shaped the same. So the API call would be different but the data put in the store is shaped the same.
export const getScoreData = createAsyncThunk(
'contentitem/scoredata',
async (params) => {
return axios.get(`${params.apiAddress}api/v2/scores/${params.scoreId}`)
.then(
res => res.data,
err => console.error(err)
);
}
);
export const scoreSlice = createSlice({
name: 'contentitem',
initialState,
reducers: {},
extraReducers: {
[getScoreData.pending]: (state) => {
state.loading = true;
state.complete = false;
},
[getScoreData.fulfilled]: (state, { payload }) => {
state.loading = false;
state.complete = true;
state.data = payload;
},
[getScoreData.rejected]: (state) => {
state.loading = false;
state.complete = true;
}
}
});
Sorry all, it was not the redux toolkit or the store it was how I was interacting with it in my react components. Problem solved by calling the dispatch at the appropriate times.

Redux subscribed async State empty

I have a Login Handler which takes the response and dispatches data to redux (userdata and json web tokens). I also do have an interval to renew JWTs every X minutes.
However the subscribed state returns undefined and I´m quite unsure why. If I track the redux store there is data written successfully to it. What do I miss here?
const App = () => {
const dispatch = useDispatch()
const logedInUser = useSelector(state => state.logedInUser.logedInUser)
const tokens = useSelector(state => state.token.tokens)
const intervalRef = useRef();
const refreshToken = useCallback(async () => {
console.log(tokens.refreshToken) //prints undefined
console.log(logedInUser.id) //prints undefined
let response = await restAPI("post", hostAPI + "/refreshToken", {token: tokens.refreshToken})
dispatch(setTokens({
accessToken: response.accessToken,
refreshToken: response.refreshToken
}))
}, [])
useEffect(() => {
const interval = setInterval(() => refreshToken(), 30000)
intervalRef.current = interval;
return () => clearInterval(interval)
}, [refreshToken])
const loginHandler = async (data) => {
let response = await restAPI("post", hostAPI + "/login", {email: data.email, password: data.password}) //Simple Fetch with Post method returns response as JSON
if(response.user.id) {
console.log("RESPONSE",response) //Prints correct Response
dispatch(setLogedInUser(response.user)) //Dispatch returned Data to redux
dispatch(setTokens({
accessToken: response.accessToken,
refreshToken: response.refreshToken
}))
}
}
TokenSlice as example (redux toolkit used):
const initialState = {
tokens: []
}
export const tokenSlice = createSlice({
name: "token",
initialState,
reducers: {
setTokens: (state, action) => {
state.tokens = action.payload
console.log("Token:", state.tokens) //Prints out correct Tokens afterDispatch
},
}
})
If I build up a Component with a button in which I Refresh the Token on click everything works as expected. I´m sure that it is just a silly little thing what I´m missing here but since I´m pretty new to Redux I can´t point out what is the issue here is.
Thanks in advance!
Edit:
I´ve noticed something else. If I change some code and Save it the next "Refresh Token Interval" does print out the correct values. Means somehow "tokens" never updates from the Initial empty state - at least in app component. Like mentoined the redux state itself holds the right values.
I think your method of fetching data from API is wrong. I'm seeing that in your code snippets. You are not using any package like Axios or API request mode. Please see any tutorial on how to fetch data from API?
Try to do this work by using :
const Axios = require("axios");
Axios.defaults.withCredentials = true;

redux next wrapper - hydrate method is loosing the previous state on page change

I am new to next-redux-wrapper and trying to use it to persist the state across pages.
On the home page, I have login action. When login succeeds, I am adding a session.loggedIn : true to redux store.
When I transition to the next page (getServerSideProps), the HYDRATE action is being called, but the action.payload is empty. When this empty object is merged with the previous state, all the previous states is getting lost.
Here is the snapshot of the state merge redux-logger:
My code in first page with getInitial Props:
//Index.getInitialProps = async(/* ctx */) => {
Index.getInitialProps = wrapper.getInitialPageProps( store => async ({pathname, req, res}) => {
try {
let promises = []
promises.push(fetch(URLMap("categoriesAPI")).then(res => res.json()))
const pageNum=1, itemsPerPage=10
let collectionsURL = URLMap('myCollectionAPI')
collectionsURL = `${collectionsURL}?pageNum=${pageNum}&itemsPerPage=${itemsPerPage}`
promises.push(fetch(collectionsURL).then(res => res.json()))
let results = await Promise.all(promises)
//console.log("Result of my collections: ",results[1])
return {categories:results[0].categories, products:results[1].products}
} catch (e) {
console.error("errr while fetching data in 'getInitialProps'", e)
//TODO: Implement a google like notification indicating network failure.
return {}
}
})
The transitioned (new page) has this code for getServerSideProps:
export default connect(state => state)(ShopProductPage)
//export async function getServerSideProps({query}) {
export const getServerSideProps = wrapper.getServerSideProps(store => async({query}) => {
console.log('Inside server side props');
let shopId = query.shopId
let productId = query.productId;
console.log("shop id:", shopId)
console.log("product id:", productId)
let promises = []
promises.push(getShopInfo(shopId));
promises.push(getProductInfo(productId));
try {
let allPromises = Promise.all(promises)
let results = await allPromises;
return {props: {
id: shopId,
shopInfo: results[0],
productInfo :results[1]
}}
} catch(e) {
console.error("Failure:", e)
return { props: {}}
}
})
I can see that every time I navigate to a new page, the hydrate payload is empty and overriding the previous state. Am I missing anything here?
EDIT
My reducer code is as follows: I realized that there is not much of a state on the server side, and if I merge it with client's state, it will clean sweep. So, I decided to return the state unmodified (without merge) in he hydrate method. Now, it works fine for my use case.
I have another issue wherein If I reload the page, or navigate to new page through browser's addressbar, it will be a new load and no state is available. It looks like I have use Redux Persist to solve this 2nd problem.
import {applyMiddleware, createStore} from 'redux';
import {createWrapper, HYDRATE} from 'next-redux-wrapper';
import thunk from "redux-thunk"
import logger from 'redux-logger'
let store
const initialState = {
//TODO: Add initial state of our app here.
//showLogin: false,
loginProcessState: 0, // 0:false (don't show), 1:showLoginDialog, 2:login submit begin, 3: login submit failed, 4:showOTPDialog,
contactInfoDisplay: false, // false:don't show; true: show
contactEditDisplay: false,
session: { loggedIn : false
/*
// If logged in, it will have these values also (sample)
name: "Ravindranath M",
mobile: "xxxxxxx982",
email: "someone#somewhere.com",
pincode: "560097"
*/
},
cart: {total: 0, products : []}, // product structure {_id:'', qty:''}
contactInfoDisplay: 0 //0:no display, 1:display, 2:edit
}
const reducer = (state=initialState, action) => {
switch (action.type) {
case HYDRATE: // important case for persisting state between client and server!
//return {...state, ...action.payload}
return {...state }
case 'SET_CATEGORIES':
return {
...state,
categories: action.categories
}
case 'SHOW_LOGIN_DLG':
console.log("Reducer executing 'SHOW_LOGIN_DLG' action...")
return { ...state, loginProcessState: 1 } // show login dialog

Extracting multiple nested objects using Hooks and copying it to a new list to use data in React App/Redux using Hooks

New to Stackoverflow and a Junior Dev who needs help pls.
We have built an application to manage Webforms. Users have multiple Accounts/Departments, and each account/department have multiple users and managers.
Upon completion of the form the user can email it directly to their manager(s) by selecting the manager in a compiled dropdown.
Question:
The issue currently is by using fetchAccountDetails I can only extract one array of managers from a specific account at a time. I can not seem to get all managers for my users from all the accounts they belong to.
I want to extract all managers from all accounts for my current user and then list them in the dropdown.
The Output I require:
The current user's Manager dropdown needs to have the managers from all Accounts listed. The Account managers details is inside the Account/${id} for each account. how can I extract the managers for each account referencing the accId as ref.
lookupStore.js
const FETCH_USERS = "FETCH_USERS";
const SET_USERS = "SET_USERS";
const FETCH_ACCOUNTS = "FETCH_ACCOUNT_DETAILS";
const SET_ACCOUNTS = "SET_ACCOUNT_DETAILS";
const initialState = { users: [], accounts: [], accountDetails: [] };
export const actionCreators = {
setUsers: data => ({ type: SET_USERS, payload: data }),
setAccountDetails: data => ({ type: SET_ACCOUNT_DETAILS, payload: data })
};
export const reducer = (state = initialState, action) => {
state = state || initialState;
switch (action.type) {
case SET_USERS: {
return {
...state, users: [...action.payload.users]
};
}
case FETCH_ACCOUNTS: {
return {
...state, accounts: [...action.payload.accounts]
};
}
case FETCH_ACCOUNT_DETAILS: {
return {
...state, accountDetails: [...action.payload.accountDetails]
};
}
default:
return state;
}
}
export const fetchUsers = (success, failure) => apiAction({
url: "Users",
onSuccess: success,
onFailure: failure,
label: FETCH_USERS
});
export const fetchAccountDetails = (id, success, failure) => apiAction({
url: `Accounts/${id}`,
onSuccess: success,
onFailure: failure,
label: FETCH_ACCOUNT_DETAILS
});
myWebform.js
import { actionCreators, fetchAccounts, fetchUsers, fetchAccounts, fetchAccountDetails } from '../../store/lookupStore';
function WebForms(props) {
//pulling from fetchUsers in lookupStore all users
const users = useSelector(state => state.lookups.users);
const userId = sessionStorage.getItem('currentUser');
useEffect(() => {
dispatch(fetchUsers(
(data) => dispatch(actionCreators.setUsers(data))
));
if (userId == null) {
dispatch(push("/")); //re-log in if null id
}
else {
dispatch(fetchAccounts(
(data) => {
dispatch(actionCreators.setAccounts(data));
}
));
dispatch(fetchAccountDetails(userId,
(data) => {
dispatch(actionCreators.setAccountDetails(data));
}
));
}
}, [dispatch]);
//filter by active users only 1 = active || 0 = deactivated
const userList = users.filter(user => user.status == 1); // status 0 is inactive users
//Complete userlist split and flattened into accounts
const userAcc = [];
usersList.map((user) => {
user.accounts.forEach((acc) => {
userAcc.push({
id: user.id,
name: user.displayName,
accId: acc.id,
accName: acc.name,
managers: acc.managers
});
});
});
const groupId = userAcc.map(acc => acc.id);
//List of managers to be in dropdown
const managersInGroup= currentUserGroup.map(approver => approver.approvers);
const [managersList, setManagersList] = useState([]);
const [managerId, setManagerId] = useState(0);
// load all details
function loadWebForm() {
dispatch(fetchWebForm(props.match.params.id,
(data) => {
setWebForm(data.webForm)
htmlImageAndCssMapper(data.webForm.html, activePage - 1).then(xhtml => {
setHtmlData(xhtml)
})
setWebFormName(data.webForm.name);
setManagerId(data.webForm.managerId);
//new code from MG
if (managersInGroup.length) {
setManagersList(managersInGroup[0]);
} else {
var manager = [];
manager.push(currentUser);
setManagersList(currentUser);
}
}
))
console.log("currentUser====>", currentUser) // output is equal to == json - users
console.log("groupId====>", groupId) // output : [2, 6]
console.log("managersInGroup====>", managersInGroup) // output only gives me managers of the last listed Account for my user
}
}
Looks like you might be passing in the userId in place of the accountId inside of the useEffect.
You'll want to loop through the data returned of the Accounts and for each of those fetch and dispatch saving the manager / account details.
In the reducer you'll then want to change it from
...state, accountDetails: [...action.payload.accountDetails]
to
...state, accountDetails: [...state.accountDetails, ...action.payload.accountDetails]
An additional note, you don't need to send the fetch functions to the dispatch. It's being handled by the success side.

How to prevent non-deterministic state updation in Redux?

When working with Redux, maintaining the shape of the initial state is crucial. The results/data of side effects like API call will change the shape of the state since we have no control over the properties. For example, consider this initial state:
const book = {
id: 0,
name: 'something'
};
And updation is made to it by the book sub-reducer as follows based on the API data:
//receives `book` part of the state
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
}
Two scenarios that could happen:
If the data sent from the API is null, then newly produced state is now {} as a result of the spread operator. If some parts of UI were to listen to the book part of the state, then it will break. Possibly access individual properties from the API data? In that case, null/undefined checks needs to be performed for properties. Is there a more elegant solution?
There could also be additional properties in the data which we may not be interested in. Possibly use an object mapper to filter unused properties?
What is the best practice to handle these kind of scenarios and prevent state becoming non-deterministic? Please share your experience on how you approached these scenarios.
Only the reducer has to be pure/deterministic, not the stuff outside of it.
To prevent your reducer from overwriting data incorrectly, write some logic between the API response and the dispatch-call to ensure the reducer always gets valid data.
For example a thunk might look like:
const createBook = (name) => {
return async dispatch => {
// suppose the api call gives back "uid" plus extra data
const { uid, ...unneededData } = await myApi.setBook(name);
// dispatch the data in the way the reducer expects it
dispatch({ type: 'SET_BOOK', id: uid, name });
}
}
In the above example, the api call gives me uid, but no name, and a bunch of extra data. Just prepare the data before dispatching it.
The best practice is the one where you prevent your app from breaking from every aspect, which means you need to check and format your data before returning from the reducer.
In your case, I would check for both data validity and map it to a necessary format.
only dispatch 'SET_BOOK' if API response has both id and book.
in order to avoid unnecessary additional properties, you can always map your data const book = {id: apiData.id, book: apiData.book} before dispatching.
In your reducer you can do like below. This way only id and name will get updated even though there are other key/values in the response. Also this will make sure that if null values are received then those values will not be updated in the state. Hope this will help in resolving the issue.
//receives `book` part of the state
const bookReducer = (state=book, action) => {
const { type, payload } = action;
switch(type) {
case 'SET_BOOK': {
return {
...state,
...(payload.id && {id: payload.id}),
...(payload.name && {name: payload.name})
};
} default:
return state;
}
}
Your redux reducer logic should not worry about that due to its deterministic nature. You handle your api call and response handling elsewhere (redux thunk or component), and then dispatch the action to set your redux. Building off of your example:
book.reducer.js
const book = {
id: 0,
name: ''
};
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
book.actions.js
const setBook = (book) => ({
type: SET_HEROES,
payload: book
});
// thunk
const findBook = name => async dispatch => {
const book = await bookService.findBook(name);
if (book) {
dispatch(setBook(book));
}
};
book.service.js
const findBook = async (name) => {
// Do your api call
const bookResponse = axios.get(`${apiUrl}/book/search/${name}`);
// Handle the response
if (!bookResponse) {
// Logic you do if book not found
return null;
}
return {id: bookResponse.id, name: bookResponse.name};
}
Now in a component you can just dispatch the findBook call
Component.js
const Component = () => {
const [search, setSearch] = useState('');
const dispatch = useDispatch();
const handleOnSearch = () => {
dispatch(findBook(search));
}
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)}/>
<button onClick={handleOnSearch}>Search</button>
</div>
);
}
If field value from API is undefined then convert it into null and store so that the code doesn't break and operatable. If API gives other params as well then de-structure the API returned object and extract the required fields. So that storing unnecessary data can be avoided.
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
const {id, name, otherParam1, otherParam2} = action.payload;
return {
id: id || null,
name: name || null,
otherParam1,
otherParam2
}
} default:
return state;
}
}
Having the value null won't break the code instead, it renders nothing
which is better than undefined which breaks the code
Hope this helps you
What I do is to have all of my logic in my action method and create reducers for when an action is correctly fulfilled and another one for when is rejected. In the fulfilled reducer, I would do the regular instructions and in the rejected reducer I would add the data to a variable called error which I always have in my state and use in the frontend to show an error message if needed.
Example
This is an action that creates a house by sending a post request to my api which returns the created object or an error if something went wrong.
export const createHouse = houseData => {
const URL = HTTP://EXAMPLE.URL
return async dispatch => {
try {
const response = await axios.post(`${URL}`, houseData);
const data = await response.data;
dispatch({ type: "CREATE_HOUSE_DRAFT_FULFILLED", data });
} catch (err) {
dispatch({ type: "CREATE_HOUSE_DRAFT_REJECTED", data: err });
}
};
};
Then I would have 2 reducer methos to recieve the fulfilled or the rejected response, like this.
case 'CREATE_HOUSE_DRAFT_FULFILLED': {
return {
houses: [action.data, ...state.houses],
house: action.data,
houseCount: state.houseCount + 1,
fetched: true,
error: null
};
}
case 'CREATE_HOUSE_DRAFT_REJECTED': {
return {
...state,
error: action.data.response.data,
fetched: false,
success: null
};
}
Hope this works for you!

Resources