Socket.io - Terminal returning data as "null" - reactjs

I'm building a forum app using reactjs, nodejs, express and mongoDB, where users can create account, login and then post a post text. I want this post to be displayed instantly To all others users, for that i am using socket.io server.
I would appreciate if you check my whole project here in github: (https://github.com/darkpsinight/forum)
However, whenever a user login or connect to the app, i get this null in socket terminal:
Connected user.✅
null
I tried to investigate this null message, and i discovered that data is returning null.
How to make data returns data of createdPostsocket instead of null ?
Edit:
createdPostsocket is initialized as null, and my catch statement returns null, so either i am not assigning a value to createdPostsocket or i am throwing an error in my promise.
Meanwhile, i already assigned a value in Slice at fulfilled case :
[createPost.fulfilled]: (state, action) => {
console.log(action.payload);
window.location.reload(false);
state.createdPostsocket= action.payload.data.data
}
Which means createdPostsocket assigned the value of payload.data.data, right?
My codes:
- Back:
app.js (socket server)
const io = require("socket.io")(4000, {
cors: {
origin: "http://localhost:3000",
},
});
io.on('connection', (socket) => {
console.log('Connected user.✅');
socket.on('addPost', data => {
console.log(data); //returning null in terminal
io.emit('newPost', data)
})
socket.on('disconnect', function (socket) {
console.log('Disconnected user.⛔️');
});
});
- Front:
postsSlice.js:
const initialState = {
posts: [],
createdPostsocket: null
};
const postsSlice = createSlice({
name: "posts",
initialState,
reducers: {},
extraReducers: {
//Create http request 3 cases
[createPost.pending]: (state, action) => {
},
[createPost.fulfilled]: (state, action) => {
console.log(action.payload);
window.location.reload(false);
state.createdPostsocket= action.payload.data.data
},
[createPost.rejected]: (state, action) => {
},
},
});
export const { } = postsSlice.actions;
export const selectPosts = (state) => state.posts
export default postsSlice.reducer;
index.jsx:
const posts = useSelector(selectPosts)
const socket = useRef()
useEffect(() => {
socket.current = io("ws://localhost:4000")
}, [])
useEffect(() => {
socket.current.emit("addPost", posts.createdPostsocket);
console.log (posts.createdPostsocket) //returning null in console developper
}, [posts.createdPostsocket])
postsAPI.js
import { axiosInstance } from "../../config/axios";
import { requests } from "../../config/requests";
//post service
export const PostsService = {
// Create post request
create: (data) => {
console.log(data);
return axiosInstance
.post(requests.postapi.create, data, { credentials: "include" })
.then((res) => {
return res;
})
.catch((err) => {
return err;
});
},
};
- back:
controller.js:
module.exports = {
create: (req, res) => {
const data = {
user: req.user.sub,
text: req.body.text,
/* image: req.file && req.file.filename */
}
console.log(data) //returning user and text
const t = new post(data)
t.save().then(t => t
.populate('user')
.then(post => {
res.status(200).json({
message: 'post created',
data: post
})
})
.catch(err => {
res.status(500).json({
message: 'post not created',
data: null
})
})
)
},
}

Remove window.location.reload(false); so you wont lose your data. window.location.reload(false); will reload your page and delete all the data. However it is not recommanded to use vanilla JavaScript to call the reload method to tell the browser to reload the current page and you will loose any data.

Related

Redux toolkit action is stuck in pending state even after api response is recieved

I am using redux toolkit and all of the other actions for slice are working as expected but only editCountry action has problem. My editCountry action is stuck in country/editCountry/pending. I have no idea what is wrong with code.
This is how redux slice code is defined.
slice.js
const initialState = {
countries: {},
isLoading: false,
isError: false,
isSuccess: false,
message: ''
}
export const editCountry = createAsyncThunk(
'country/editCountry',
async (payload, thunkAPI) => {
try {
const response = await apiProvider.editCountry(payload);
return response;
}
catch (e) {
return thunkAPI.rejectWithValue(e)
}
})
const countriesSlice = createSlice({
name: 'country',
initialState,
reducers: { },
extraReducers: (builder) => {
builder
.addCase(editCountry.pending, (state, action) => {
state.isLoading = true
})
.addCase(editCountry.fulfilled, (state, action) => {
console.log('fulfilled before: ', state); // shown on console
console.log('fulfilled action: ', action.payload.data); // undefined
const unUpdatedCountries = state.countries.data.filter(c => c._id !==
action.payload.data._id)
const updatedCountries = [action.payload.data, ...unUpdatedCountries];
state.isLoading = false;
state.isSuccess = true;
state.countries = {...state.countries, data: updatedCountries };
console.log('fulfilled after: ', state); // do not shown on console
})
.addCase(editCountry.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
}
Here is the Api helper method which is invoked inside editCountry action.
ApiProvider.js
export const editCountry = async (payload) => {
let token = getToken();
const config = {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
};
try{
const {data} = await axios.put(`${baseUrl}/edit-country`, payload, config);
if(data.status){
return Promise.resolve(data);
}
return Promise.reject(data.message);
}
catch (e) {
return Promise.reject(e.message);
}
}
And here is my Submit method from where action is dispatched.
Country.js
const handleSubmit = async (e, newFormData) => {
e.preventDefault()
const apiPayload = {
Country_ID: newFormData.id,
Name: newFormData.name,
DialCode: newFormData.dialCode.includes("+") ? newFormData.dialCode : "+"+newFormData.dialCode,
ISO: newFormData.iso,
CallRate: newFormData.minutePrice,
Blocked: newFormData.isBlocked.value,
}
try {
await dispatch(editCountry(apiPayload)).unwrap()
toggle() // hide form modal
toast.success('Country updated successfully');
}
catch (e) {
setFormData({
...formData,
errorMsg: e.message,
});
toggle();
toast.error(e);
}
}
On action dispatch redux is stuck on pending but in network tab api call is completed.
[enter image description here][1]
Network tab image:
[1]: [https://i.stack.imgur.com/X3rbA.png][1]
Redux dev tools image:
[1]: https://i.stack.imgur.com/sQUdJ.png
Just return the data, don't return wrap it in Promise.resolve.
if(data.status){
return Promise.resolve(data);
}
return Promise.reject(data.message);
}
In editCountry.fulfilled, it should be action.payload not action.payload.data.
In editCountry, you send data and in editCountry async thunk, you send the complete response which is basically the data. In editCountry.fulfilled, you have complete data in payload but you are trying to access the data from payload.
Hope this solve your issue

redux-toolkit delete all items when i try to delete one

i'm migrating slowly to redux-toolkit
when i try to delete a single item from my store, the action works well because i send the right ID from the component, but i think that the way i send this id to the slice is incorrect so i'm not able to delete the right item from my store(it delete all the items)
component dispatch code:
const handleDelete = async (e) => {
e.preventDefault()
const del_id = e.currentTarget.getAttribute("data-id")
dispatch(deleteComment(del_id))
}
action & api call:
export const deleteComment = (id) => async (dispatch, getState) => {
try {
dispatch(deleteCommentsLoading())
const {
userLogin: { userInfo },
} = getState()
const config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${userInfo.token}`,
},
}
const { data } = await axios.delete(
`${process.env.REACT_APP_API_KEY}/publication/comment/delete/${id}`,
config
)
dispatch(deleteCommentsSuccess({ commentId: id }))
// i tried deleteCommentsSuccess(data); (id)... nothing work to send this id to the slice
} catch (error) {
dispatch(
deleteCommentsFail(
error.response && error.response.data.message
? error.response.data.message
: error.message
)
)
}
}
my slice:
const commentSlice = createSlice({
name: "comments",
initialState: {
comment: {},
loading: false,
error: false,
},
... other reducers
// DELETE COMMENT
deleteCommentsLoading: (state) => {
state.loading = true
},
deleteCommentsFail: (state, action) => {
state.error = action.payload
state.loading = false
},
deleteCommentsSuccess: (state, action) => {
const { commentId } = action.payload
state.comment.comments.filter((item) => item._id !== commentId)
// i tried first when i send data or id to put action.payload.id nothing work
state.loading = false
state.error = false
},
and this's my comments store slice, every item have his own "_id"
How is your backend. (comment)
Something like this? :
const receita = await Recipe.findById(req.params.id)
if (receita) {
await receita.remove()
res.json({ message: 'Receita removed' })
} else {
res.status(404)
throw new Error('Receita not found')
}
})
state.comment.comments.filter((item) => item._id !== commentId) will just return a filtered copy of the array,but not actually modify anything.
You have to do
state.comment.comments = state.comment.comments.filter((item) => item._id !== commentId)

Error: Invariant failed: A state mutation was detected between dispatches

Reducer :
function listPeopleByName (state = {
getPeopleName:{}
}, action){
switch(action.type){
case C.LIST_PEOPLE_NAME:{
return {
...state
,getPeopleName :action.payload
}
}
default : {}
}
return state
}
Action:
function listPeopleByName(config) {
return function (dispatch) {
ApiService(config)
.then((resp) => {
dispatch({
type: C.LIST_PEOPLE_NAME,
payload: resp.data,
});
})
.catch((error) => {
dispatch({
type: C.LIST_PEOPLE_NAME,
payload: error,
});
});
};
}
ApiService is a function that make an axios request and returns a respones
Dispatching code :
listPeopleByNameFunction = () => {
const listPeopleByNameParam = {
id: someone,
},
let data = {
PeopleId: "snjenfner",
};
let listPeopleByNameCategory = getApiConfig(
"POST",
listPeopleByNameParam,
data
);
this.props.listPeopleByName(listPeopleByNameCategory);
};
const mapDispatchToProps = (dispatch) => ({
listPeopleByName: (config) => dispatch(listPeopleByName(config)),
});
Although I take the previous state (...state) and change the state with the payload i'm getting, it still shows the state is mutated. I would have used reduxtoolkit but this is a way old project that doesn't need to be migrated to reduxtoolkit.

React Redux - How to make a double dispatch

I'm fetch some data from my API and it correctly works. But when a double dispatch on the same page the API doesn't work anymore. It's better code to explain it:
Server:
router.get("/", (req, res) => {
let sql = "SELECT * FROM design_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
router.get("/", (req, res) => {
let sql = "SELECT * FROM food_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
They work.
action.js
export const fetchDesignCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/designcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_DESIGN_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
export const fetchFoodCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/foodcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_FOOD_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
Both of them work perfectly.
reducer.js
const initalState = {
db: [],
loading: true,
designcat: [],
foodcat: [],
}
export default (state = initalState, action) => {
switch (action.type) {
// different cases
case FETCH_DESIGN_CAT:
return {
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
food: action.payload,
loading: false,
}
}
The reducer updates the states perfectly.
Page settings.js
const Settings = ({ designcat, foodcat, loading }) => {
const dispatch = useDispatch()
// ... code
useEffect(() => {
dispatch(fetchDesignCat()) // imported action
dispatch(fetchFoodCat()) // imported action
// eslint-disable-next-line
}, [])
// ... code that renders
const mapStateToProps = state => ({
designcat: state.appDb.designcat,
foodcat: state.appDb.foodcat,
loading: state.appDb.loading,
})
export default connect(mapStateToProps, { fetchDesignCat, fetchFoodCat })(
Settings
)
Now there's the problem. If I use just one dispatch it's fine I get one or the other. But if I use the both of them look like the if the second overrides the first. This sounds strange to me.
From my ReduxDevTools
For sure I'm mistaking somewhere. Any idea?
Thanks!
Your reducer does not merge the existing state with the new state, which is why each of the actions just replace the previous state. You'll want to copy over the other properties of the state and only replace the ones your specific action should replace. Here I'm using object spread to do a shallow copy of the previous state:
export default (state = initalState, action) => {
switch (action.type) {
case FETCH_DESIGN_CAT:
return {
...state, // <----
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
...state, // <----
food: action.payload,
loading: false,
}
}
}
Since the code is abbreviated, I'm assuming you're handling the default case correctly.
As an additional note, since you're using connect with the Settings component, you don't need to useDispatch and can just use the already connected action creators provided via props by connect:
const Settings = ({
designcat,
foodcat,
loading,
fetchDesignCat,
fetchFoodCat,
}) => {
// ... code
useEffect(() => {
fetchDesignCat();
fetchFoodCat();
}, [fetchDesignCat, fetchFoodCat]);
// ... code that renders
};
There's also a race condition in the code which may or may not be a problem to you. Since you start both FETCH_DESIGN_CAT and FETCH_FOOD_CAT at the same time and both of them set loading: false after finishing, when the first of them finishes, loading will be false but the other action will still be loading its data. If this case is known and handled in code (i.e., you don't trust that both items will be present in the state if loading is false) that's fine as well.
The solution to that would be either to combine the fetching of both of these categories into one thunk, or create separate sub-reducers for them with their own loading state properties. Or of course, you could manually set and unset loading.

How to Create Middleware for refresh token in Reactjs with axios and redux

i am working with reactjs on front end the issue is after certain time period the accessToken is expired and server send status of 401(unauthorized) then i need to send refresh token back to server it works fine until i manually send the refresh token i set the setInterval function but thats not a good approach how to automatically send it when token is expired.
i also google it but everyone is talking about creating middleware anyone please give me the hint how to create that middleware or any other solution or link any article related to it . i created this but this didnt works for me however when server send status of 401 then middleware ran but it dosent dispatch my refreshToken() function
const customMiddleWare = store => next => action => {
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
if(error.status === 401) {
// do something when unauthorized
store.dispatch(refreshToken());
}
return Promise.reject(error);
});
console.log("Middleware triggered:", action);
next(action);
}
By the way i am using redux, redux-thunk and axios. thanks,
some time ago i used to use the next way:
First of all i created some api folder, where each function returns data for axios requests
// /api.js
export function signIn (data) {
return {
method: 'post',
api: '/sign-in'
data: data
}
}
export function signUp (data) {
return {
method: 'post',
api: '/registration'
data: data
}
}
then i generated action type by specific rule, like: SIN_IN_REQUEST, where: SIGN_IN means signIn function in /api.js; REQUEST means that you need to do api request. As result my middleware looked like the next:
// request middleware
const instance = axios.create({
baseURL: '/api'
});
function camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(word, index) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '');
}
const customMiddleWare = store => next => action => {
if (!action.type.endsWith('_REQUEST')) {
next();
return;
}
const methodName = action.type.replace('_REQUEST', ''); // removed _REQUEST from action type
const camelCaseMethodName = camelize(methodName); // the result is "signIn"
const method = api[camelCaseMethodName];
if (!method) {
next();
return;
}
const dataForRequest = method(action.payload);
try {
const response = await instance(dataForRequest);
const newActionType = action.type.replace('_REQUEST', '_SUCCESS');
dispatch({
type: newActionType,
payload: {
requestPayload: action.payload,
response: response,
}
})
} catch(error) {
if (error.status === '401') {
dispatch(refreshToken());
next();
return;
}
const newActionType = action.type.replace('_REQUEST', '_FAILURE');
dispatch({
type: newActionType,
payload: {
requestPayload: action.payload,
error: error,
}
})
}
next();
}
After that you can easily manage any api request in your application like that:
function someTHunkMethod(username, password) {
return (dispatch, getState) => {
dispatch({
type: 'SIGN_IN_REQUEST',
payload: {
username,
password
}
})
}
}
function oneMoreThunk(data) {
return (dispatch, getState) => {
dispatch({
type: 'GET_USERS_REQUEST',
payload: data
})
}
}
And in reducer do something like that
...
switch (action.type) {
case 'SIGN_REQUEST':
return {
isLoading: true,
user: null
}
case 'SIGN_SUCCESS':
return {
isLoading: false,
user: action.payload.response.data
}
case 'SIGN_FAILURE':
return {
isLoading: false,
user: null
}
default:
return state
}

Resources