React and Redux: Payload Not Appearing Within Reducer State Object - javascript

In the following reducer file, when a user logs in the state is updated with the payload coming from the actions file. In my state, I have a user object in which I want to store the payload. However, in my redux dev tool, I noticed that the payload is appearing outside of the user object instead of within it as a result user object shows as the default value of null. Cannot figure out what I am doing wrong.
//Login Reducer Fille
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: false,
isLoading: false,
user: null
};
export default function(state = initialState, action){
switch(action.type){
case LOGIN_SUCCESS:
localStorage.setItem('token', action.payload.token);
return{
...state,
...action.payload,
isAuthenticated: true,
isLoading: false
};
default:
return state;
}
}
//Login Action File
export const login = ({email, password, history}) => dispatch =>{
const config = {
headers:{
"Content-Type": "application/json"
}
};
const body = JSON.stringify({email, password});
axios.post('/api/user/login/', body, config)
.then(res => {
console.log(res.data)
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
})
history.push('/dashboard')
})
.catch(err =>{
dispatch(returnErrors(err.response.data, err.response.status, 'LOGIN_FAIL'));
dispatch({
type: LOGIN_FAIL
});
});
};

You need to change the reducer return method in the LOGIN_SUCCESS case block to this one
return {
...state,
user:action.payload,
isAuthenticated: true,
isLoading: false
};

Related

How to use latest state from redux store to update component in react

I have an application in which I am dispatching some actions, and the result of my action (which will give me the latest state from the store) is used to display or render components. Basically, I have an add operation, in which I am both storing new values in the backend and also adding the new value to the DOM. So, after the addition, based on the value of a "status" field from the store, I want to know if the operation was successful at the backend, if it is, then the DOM can be manipulated.
My action and reducers are shown below.
ZoneAction
export const addZoneAction = (type, title, token /*coordinates*/) => {
return async (dispatch) => {
dispatch({ type: LOADING_ZONE });
dispatch({ type: RESTART_ALL_RESPONSE_ZONE });
const coordinates = {
long: 28.605459712385027,
lat: -83.69021484375,
rad: 1484.1304490741
}
try {
let response = fetch(
"https://url.com/zone-groups",
{
method: "POST",
body: JSON.stringify({
user_id: parseInt(token.maid, 10),
group_type: type,
zone_title: title,
coordinates: coordinates
}),
}
);
let resJson = await response;
const data = await resJson.json();
if (resJson.status == 200) {
dispatch({ type: ADD_ZONE_SUCCESS, payload: data });
const notificationPayload = {
message : `Zone Group added successfully`
}
dispatch({
type: NOTIFICATION_SUCCESS,
payload: notificationPayload
});
}else{
dispatch({ type: ADD_ZONE_FAIL });
const notificationPayload = {
message : data,
title: "Error!",
persist: true
}
dispatch({
type: NOTIFICATION_ERROR,
payload: notificationPayload,
});
}
} catch (err) {
dispatch({ type: ADD_ZONE_FAIL });
const notificationPayload = {
message : err.message,
title: "Error!"
}
dispatch({
type: NOTIFICATION_ERROR,
payload: notificationPayload,
});
}
}
}
My Reducer
const initialState = {
isLoading: true,
status: null,
allZones: [],
selectZones: [],
};
const zoneReducer = (state = initialState, action) => {
switch (action.type) {
case RESTART_ALL_RESPONSE_ZONE:
return {
...state,
isLoading: false,
status: null,
};
case LOADING_ZONE:
return {
...state,
isLoading: true,
};
case ADD_ZONE_SUCCESS:
return {
...state,
isLoading: false,
allZones: [action.payload, ...state.allZones],
};
case ADD_ZONE_FAIL:
return {
...state,
isLoading: false,
allZones: state.allZones,
status: "error", //This value is what I am using to track the success of the operation
};
default:
return state;
}
};
So, I dispatch the addZoneAction normally in my modal component. In my AllZones component, where I intend to use the updated state, I get the zone from the store and access the "status" property. I also listen for when the value of that "status" property changes in useEffect. Furthermore, I created a local state variable "actionStatus" to track the status property from inside useEffect. My component is shown below.
AllZones
const zoneObj = useSelector((state) => state?.zone);
const [actionStatus, setActionStatus] = useState("");
//UseEffect
useEffect(() => {
if (zoneObj.status === "error") {
setActionStatus("error");
} else {
setActionStatus("success");
}
}, [zoneObj.status, actionStatus]);
The Problem
The problem is that I am not getting an updated value from the store. If I look at the Redux Devtool, I can see the current value of the "status" there if the request fails for example.
For instance if a request fails and I run the code below outside the use Effect:
console.log(actionStatus)
For a brief moment, I will see "success" then before seeing "error". And in that space of time, the action I don't want its execution would have executed.
Suprisingly, I get a very updated value of "status" inside the useEffect, but using it outside is the big issue now.
Is there anything I am not doing right? maybe from my store or from my array of dependencies in useEffect?
Has anyone implemented something like this before?
I will be happy to learn a better way of doing this. Thanks

Passing loaded user ID into my action for update User Data (MERN ReactJS + Express)

I'm trying to update my user profile data from the client-side. The user has to upload a file and the component will catch the actual user-loaded ID, store it into a state, and then use this state to find the user in the database to update the value I need. But I can't figure out how to pass the state to filter the user; in the way you see below the It gives me a PUT: http://localhost:3000/api/users/upgrade/undefined 404 (Not Found). Someone could help me?
Here's my server router:
//SERVER ROUTER
router.put("/upgrade/:id", upgrade.single("userPlus_doc"), (req, res) => {
User.findById(req.params.id)
.then((user) => {
user.userPlus = true;
user.userPlus_doc = req.file.originalname;
user
.save()
.then(() => res.json("User Upgraded!"))
.catch((err) => res.status(404).json({ success: false }));
})
.catch((err) => res.status(404).json({ success: false }));
});
My action and reducer:
//ACTION
export const upgradeUser = (formData, id) => (dispatch, getState) => {
axios
.put(`/api/users/upgrade/${id}`, formData, tokenConfig(getState))
.then((res) =>
dispatch({
type: USER_UPGRADE,
payload: res.data,
})
)
.catch((err) =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
export const setUserUpgradeID = (user_id) => (dispatch) => {
dispatch({
type: SET_USER_UPGRADE_ID,
payload: user_id,
});
};
//REDUCER
const initialState = {
user_id: "",
};
export default function foo(state = initialState, action) {
switch (action.type) {
...
case SET_USER_UPGRADE_ID:
return {
...state,
user_id: action.payload,
};
case USER_UPGRADE:
return {
...state,
user: state.user.filter((user) => user._id !== action.payload),
};
default:
return state;
}
}
And how I pass the information client-side:
class ProfileUpgrade extends Component {
state = {
userPlus_doc: "",
user_id: "",
};
onFileChange = (e) => {
this.setState({
userPlus_doc: e.target.files[0],
});
this.props.setUserUpgradeID({
user_id: this.props.auth.user._id,
});
};
/* onChange = () => {
this.props.setUserUpgradeID({
user_id: this.props.auth.user._id,
});
console.log(this.props.setUserUpgradeID);
};
*/
onSubmit = (e, user_id) => {
e.preventDefault();
const formData = new FormData();
/* formData.append("userPlus", this.state.userPlus); */
formData.append("userPlus_doc", this.state.userPlus_doc);
this.props.upgradeUser(formData, this.props.user_id);
};
render() {
return ( ... )
}
}
ProfileUpgrade.propTypes = {
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
user: state.user,
auth: state.auth,
user_id: state.user_id,
});
export default connect(mapStateToProps, { upgradeUser, setUserUpgradeID })(
ProfileUpgrade
);
The only things I need from the client-side are the doc title and the user ID to update the correct user object. To update the boolean userPlus I set the backend to set the value to true and save it.
Am I missing something in my action to make the component pass filter the user ID?

Creating posts with react redux

I'm a junior dev and have just joined recently. I'm trying to create a blog-like website where users can save a post and update an already saved post. I'm currently confused as to how to assign the snippetId within the post.
So this website was already made in Angular and I've been asked to migrate it to React. I'm mostly confused about how to store the ID as it is received from the server in response.data for a new post, and also, how I would receive it in the action.js file from the Redux store if it already exists.
Please help me understand the snippetData['snippetId'] part from the Angular and if I should I even use snippetData in the initialState or just use snippetId, snippetDescription, snippetTitle directly in the `initialState.
My code for now looks something like this:
action.js
import { SAVE_POST } from './types';
export const savePost=({snippetId, snippetDescription, snippetTitle})=> async dispatch=>{
const config = {
headers: {
'Content-Type': 'application/json'
}
}
}
const body = JSON.stringify({snippetId, snippetDescription, snippetTitle});
try{
if(snippetId==null){
const res = await axios.post('/api/save', body, config);
dispatch({
type: SAVE_POST,
payload: res.data
});}
else{
snippetData['snippetId']=snippetId
const res = await axios.post('/api/update', body, config);
dispatchEvent({
type: UPDATE_POST,
payload: res.data
})
}
}catch(err){
console.log(err);
}
reducer/post.js
import { SAVE_POST} from '../actions/types';
const initialState={
snippetData: {
snippetId: null,
snippetTitle: null,
snippetDescription: null
}
};
export default function (state=initialState, action){
const {type, payload}=action;
switch(type){
case SAVE_POST:
return {...state,
snippetData: {
snippetId: payload,
snippetDescription: payload,
snippetTitle: payload}
case UPDATE_POST:
return {...state,
snippetId: payload,
snippetDescription: payload,
snippetTitle: payload
}
}
}
This is finally the Angular file from where I've been asked to translate to React:
$scope.savesnippet=function(){
$scope.snippetdata={}
$scope.snippetdata['snippetTitle']=$scope.snippetTitle
$scope.snippetdata['snippetDescription']=$scope.snippetDescription
console.log($scope.snippetId)
if($scope.snippetId==null){
return $http.post('/api/save',$scope.snippetdata).then(function(response){
if(response.status==200){
$scope.snippetId=response.data;
toaster.pop('success','Snippet saved successfully!')
}else{
toaster.pop('danger','An error has occured while saving the snippet. Please try again')
}
});
}else{
$scope.snippetdata['snippetId']=$scope.snippetId
return $http.post('/api/update',$scope.snippetdata).then(function(response,status){
if(response.status==200){
toaster.pop('success','Snippet saved successfully!')
}else{
toaster.pop('danger','An error has occured while updating the snippet. Please try again')
}
});
}
}
edit:
editor.js
performSave = (snippetData) => {
const {enteredText, title} = this.state;
let {snippetId, snippetDescription, snippetTitle} = snippetData;
snippetTitle=title;
snippetDescription=enteredText;
savePost(snippetId, snippetDescription, snippetTitle);
}
const mapStateToProps = state=>({
snippetData: state.snippetData
})
export default connect(mapStateToProps, {savePost})(Editor);
What i understand from you given angular code, on API save success, you dont get entire data. U only get id of the save data. So in payload you need to update snippetId.
In case of save success, you dont need any update. U can just use as payload.
import { SAVE_POST } from "./types";
export const savePost = ({
snippetId,
snippetDescription,
snippetTitle
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
let snippetData = { snippetId, snippetDescription, snippetTitle };
try {
if (snippetId == null) {
const res = await axios.post("/api/save", JSON.stringify(snippetData), config);
snippetData.snippetId = res.data
dispatch({
type: SAVE_POST,
payload: snippetData
});
} else {
const res = await axios.post("/api/update", JSON.stringify(snippetData), config);
dispatchEvent({
type: UPDATE_POST,
payload: snippetData
});
}
} catch (err) {
console.log(err);
}
};
// Reducer:
import { SAVE_POST } from "../actions/types";
const initialState = {
snippetData: {
snippetId: null,
snippetTitle: null,
snippetDescription: null
}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SAVE_POST:
return {
...state,
snippetData: payload
};
case UPDATE_POST:
return {
...state,
snippetData: payload
};
}
}

How to dispatch from actioncreator to reducer wihout error in one time?

I am dispatching an image to firebase. Everything is successful and I can upload the image to firebase storage. But after I got success, my codes was suddenly dispatched an error one more time.By the way I logged some numbers to see what's going on and I got "1,2,3,4 then success and then failed error" on my console. How can I achive to upload image without getting failed error? I could upload but I also get error now.. Thank you in advance..
The error message says: "Invalid attempt to spread non-iterable instance".
export const uploadPhoto = (uri, contentType = 'image/jpeg') => {
return async dispatch => {
try {
console.log('1');
const userId = firebase.auth().currentUser.uid;
const photoId = await uniqueIdGenerator();
dispatch({ type: UPLOAD_START });
console.log('2');
const snapshot = await firebase.storage().ref()
.child(`/photos/${userId}`)
.child(photoId)
.put(uri, { contentType });
console.log('3');
await firebase.firestore()
.collection('users').doc(userId)
.collection('photos').doc(photoId)
.set({ url: snapshot.downloadURL });
console.log(snapshot.downloadURL);
console.log('4');
await dispatch({ type: UPLOAD_SUCCESS, payload: snapshot.downloadURL });
console.log('5');
Actions.pop();
console.log('6');
}
catch (error) {
dispatch({ type: UPLOAD_FAILED });
console.log(error.message);
}
}
}
here is my reducer:
const INITIAL_STATE = {
data: [],
loading: false
}
export default (state = INITIAL_STATE, { type, payload }) => {
console.log('reducerState', state);
console.log('type,payload', type, payload);
switch (type) {
case UPLOAD_START:
return { ...state, data: payload, loading: true }
case UPLOAD_SUCCESS:
console.log('succesa girdi');
return { ...state, data: [...state.data, payload], loading: false }
case UPLOAD_FAILED:
return { ...state, data: payload, loading: false }
case GET_PHOTOS_START:
return { ...state, data: payload, loading: true }
case GET_PHOTOS_SUCCESS:
return { ...state, data: payload, loading: false }
case GET_PHOTOS_FAILED:
return { ...state, data: payload, loading: false }
default:
return state;
}
}
EDIT
I have figured out the problem by remove all the dumy datas from my firebase storage. I use an emulator to test my app and I used same photos again and again. Most probably same photos causes a problem because I have not changed anything else in my app but now is working well.
Try to change your reducer
case UPLOAD_SUCCESS:
console.log('succesa girdi');
return { ...state, data: [...state.data, payload], loading: false }
to
case UPLOAD_SUCCESS:
console.log('succesa girdi');
return { ...state, data: payload, loading: false }
I think there is what the error is trying to warn you about. I'm not sure why you are trying to spread the state twice
UPDATE
in that case try this
case UPLOAD_SUCCESS:
console.log('succesa girdi');
let urls= state.data
urls.push(payload)
return { ...state, data: urls, loading: false }

How to handle multiple api request,to show loading indicator from one variable in redux store

i wanted to show loader for each and every request individually depending on what request made,Suppose in dashboard i have muiltple widget and they all have different api call, i wanted to show different loader for each request made,
one way is to make adding isLoading flag for every request made,which i think is not the good solution as the application grows,and i am finding solution that can handle multiple request from one flag
so how should i do to make dynamic individual loader based on every request
below is my reducer and action
reducer
export const intialstate = {
isAuth: false,
isLoading: false,
btnDisable: false
};
export default function(state = intialstate, action) {
switch (action.type) {
case API_REQUEST:
return {
...state,
isLoading: true,
};
case API_SUCCESS:
return {
...state,
isLoading: false,
isError: null
};
case API_FAILURE:
return {
...state,
isError: action.payload,
isLoading: false,
};
// no default
}
return state;
}
action.js
export const AnyAPIRequest = () => {
return (dispatch) => {
dispatch({
type: API_REQUEST
});
API.anygetcall()
.then((res) => {
dispatch({
type: API_SUCCESS
});
dispatch({ type: GETLIST, payload: res });
})
.catch((err) => {
dispatch({
type: API_FAILURE,
payload: err
});
});
};
};
Please help,how to implement dynamic loader based on different request and let me know any thing to update in current workflow
Two ways:
Have an integer count of API calls loading. IsLoading: IsLoading + 1 and then show the loading indicator if IsLoading > 1
Name each of your IsLoading differently to show different loading indicators. For example if you had a call to get students and a call to get teachers, you would have IsLoadingStudents and IsLoadingTeachers and have separate loading indicators for each component in the app
If you don't want to add a new isLoadingXXX for each new API request, you can use a collection and give each API request a string ID. Something like the following:
Reducer:
export const intialstate = {
isAuth: false,
isLoadingRequestIds: [],
btnDisable: false
};
export default function(state = intialstate, action) {
switch (action.type) {
case API_REQUEST:
return {
...state,
isLoadingRequestIds: [...state.isLoadingRequestIds, action.requestId],
};
case API_SUCCESS:
return {
...state,
isLoadingRequestIds:
state.isLoadingIds.splice(state.isLoadingRequestIds.indexOf(action.requestId)).slice(),
isError: null
};
case API_FAILURE:
return {
...state,
isError: action.payload,
isLoadingRequestIds:
state.isLoadingIds.splice(state.isLoadingRequestIds.indexOf(action.requestId)).slice(),
};
// no default
}
return state;
}
Actions:
export const AnyAPIRequest = (requestId) => {
return (dispatch) => {
dispatch({
requestId,
type: API_REQUEST
});
API.anygetcall()
.then((res) => {
dispatch({
requestId,
type: API_SUCCESS
});
dispatch({ type: GETLIST, payload: res });
})
.catch((err) => {
dispatch({
requestId,
type: API_FAILURE,
payload: err
});
});
};
};
export const StudentAPIRequest = () => AnyAPIRequest('student');
export const TeacherAPIRequest = () => AnyAPIRequest('teacher');

Categories

Resources