I have action that always returns Promise.reject:
module.exports = { create: createActionAsync('CREATE_USER', () => {
return Promise.reject({
response: {
type: 'error',
message: 'It will be implemented soon',
},
});
})}
But in component catch block doesn't work:
onAddUser(data) {
const { userActions: { create } = {} } = this.props;
create(data)
.then(() => {})
.catch(err => console.error(err)) // not working
Related
I have a code that is not working. The goal is to fetch data from API on init and then only from on click or keypress event. I don't understand how to combine result from data$, which should come on init, and then only pick result from events.
of(res) || merge(event1, event2) <- not okay
const event1 = fromEvent(document, 'click');
const event2 = fromEvent(document, 'keypress');
const data$ = fromFetch('https://api.artic.edu/api/v1/artworks/129884')
.pipe(
switchMap((response) => {
if (response.ok) {
return response.json();
} else {
return of({ error: true, message: `Error ${response.status}` });
}
}),
catchError((err) => {
console.error(err);
return of({ error: true, message: err.message });
}),
)
.pipe();
data$
.pipe(
switchMap((res) => {
return (
of(res) ||
merge(event1, event2).pipe(
switchMap((res) => {
console.log(res);
return data$;
}),
)
);
}),
)
.subscribe((art) => {
console.log('Fetched ' + art.data.id);
});
You are close, but not using the merge operator correctly as far as I can tell.
I would do mine something like this:
const event1 = fromEvent(document, 'click');
const event2 = fromEvent(document, 'keypress');
const event3 = of(true); // just to trigger on page load
const data$ = fromFetch('https://api.artic.edu/api/v1/artworks/129884')
.pipe(
map((response) => {
if (response.ok) {
return response.json();
} else {
return { error: true, message: `Error ${response.status}` };
}
}),
catchError((err) => {
console.error(err);
return of({ error: true, message: err.message });
}),
);
const merged = merge(event1, event2, event3);
merged.pipe(
switchMap(event => $data)
).subscribe();
event3.pipe(take(1)).subscribe(); // triggers the fetch on load.
EDIT: Merge documentation for reference
I am working on a websocket project in react. But when I send a message, the websocket does reload to display new messages. I have to reload my page manually to show the changes.
Here's my use Effect Hook
useEffect(() => {
if (socket.current) {
socket.current.on('msgSent', ({ newMsg }) => {
console.log('MESSAGE SENT', newMsg)
if (newMsg.receiver === openChatId.current) {
setMessages((prev) => [...prev, newMsg])
setChats((prev) => {
const previousChat = prev.find(
(chat) => chat.messagesWith === newMsg.receiver
)
previousChat.lastMessage = newMsg.msg
previousChat.date = newMsg.date
return [...prev]
})
}
})
}
}, [])
When I remove the useEffect dependency (i.e []), It works but it renders the message multiple times on the screen.
Here's the rest of my frontend client code
const openChatId = useRef('')
const auth = useContext(AuthContext)
const queryMessage = new URLSearchParams(search).get('message')
useEffect(() => {
if (!socket.current) {
socket.current = io(process.env.REACT_APP_API)
}
if (socket.current) {
socket.current.emit('join', { userId: auth.user._id })
socket.current.on('connectedUsers', ({ users }) => {
users.length > 0 && setConnectedUsers(users)
})
}
if (chats.length > 0 && !queryMessage) {
history.push(`/messages?message=${chats[0].messagesWith}`, undefined, {
shallow: true,
})
}
return () => {
if (socket.current) {
socket.current.emit('logout')
socket.current.off()
}
}
}, [chats])
useEffect(() => {
const getAllChats = async (token) => {
try {
setLoading(true)
const res = await getChats(token)
if (res) {
setChats(res)
setLoading(false)
}
} catch (err) {
console.log(err)
setLoading(false)
}
}
getAllChats(auth.token)
}, [])
useEffect(() => {
const loadMessages = () => {
socket.current.emit('loadMessages', {
userId: auth.user._id,
messagesWith: queryMessage,
})
socket.current.on('messagesLoaded', async ({ chat }) => {
setMessages(chat.messages)
setBannerData({
firstName: chat.messagesWith.firstName,
lastName: chat.messagesWith.lastName,
profilePicUrl: chat.messagesWith.profilePicUrl,
})
openChatId.current = chat.messagesWith._id
})
socket.current.on('noChatFound', async () => {
const { firstName, lastName, profilePicUrl } = await ChatGetUserInfo(
queryMessage,
auth.token
)
setBannerData({ firstName, lastName, profilePicUrl })
setMessages([])
openChatId.current = queryMessage
})
}
if (socket.current) {
loadMessages()
}
}, [queryMessage])
const sendMsg = (msg) => {
if (socket.current) {
socket.current.emit('sendNewMsg', {
userId: auth.user._id,
msgSendToUserId: openChatId.current,
msg,
})
}
}
The backend works very well. U think my problem is with the useEffect
I fixed it. I was missing the [chats] dependency
Just to make it clear router uses the code below and my messages.js are inside api folder....
router.use("/messages", require("./messages"));
so my api call is correct.
Backend for posting the message.... I know conversationId will be null if no conversation exists but... I am trying to send message where conversation exists already and still I am getting cannot read the conversationId of undefined....
// expects {recipientId, text, conversationId } in body
// (conversationId will be null if no conversation exists yet)
router.post("/", async (req, res, next) => {
try {
if (!req.user) {
return res.sendStatus(401);
}
const senderId = req.user.id;
const { recipientId, text, conversationId, sender } = req.body;
// if we already know conversation id, we can save time and just add it to message and return
if (conversationId) {
const message = await Message.create({ senderId, text, conversationId });
return res.json({ message, sender });
}
// if we don't have conversation id, find a conversation to make sure it doesn't already exist
let conversation = await Conversation.findConversation(
senderId,
recipientId
);
if (!conversation) {
// create conversation
conversation = await Conversation.create({
user1Id: senderId,
user2Id: recipientId,
});
if (onlineUsers.includes(sender.id)) {
sender.online = true;
}
}
const message = await Message.create({
senderId,
text,
conversationId: conversation.id,
});
res.json({ message, sender });
} catch (error) {
next(error);
}
});
module.exports = router;
This is the frontend that posts the data to the backend....
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
Okay so here is detail information on how I am dispatching it.
class Input extends Component {
constructor(props) {
super(props);
this.state = {
text: "",
};
}
handleChange = (event) => {
this.setState({
text: event.target.value,
});
};
handleSubmit = async (event) => {
event.preventDefault();
// add sender user info if posting to a brand new convo,
// so that the other user will have access to username, profile pic, etc.
const reqBody = {
text: event.target.text.value,
recipientId: this.props.otherUser.id,
conversationId: this.props.conversationId,
sender: this.props.conversationId ? null : this.props.user,
};
await this.props.postMessage(reqBody);
this.setState({
text: "",
});
};
render() {
const { classes } = this.props;
return (
<form className={classes.root} onSubmit={this.handleSubmit}>
<FormControl fullWidth hiddenLabel>
<FilledInput
classes={{ root: classes.input }}
disableUnderline
placeholder="Type something..."
value={this.state.text}
name="text"
onChange={this.handleChange}
/>
</FormControl>
</form>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(Input));
const mapDispatchToProps = (dispatch) => {
return {
postMessage: (message) => {
dispatch(postMessage(message));
},
};
};
// message format to send: {recipientId, text, conversationId}
// conversationId will be set to null if its a brand new conversation
export const postMessage = (body) => (dispatch) => {
try {
const data = saveMessage(body);
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
So I have attached what I want to do here now....
But I am still getting the problem....
// CONVERSATIONS THUNK CREATORS, this is how I am getting data from the backend
export const fetchConversations = () => async (dispatch) => {
try {
const { data } = await axios.get("/api/conversations");
dispatch(gotConversations(data));
} catch (error) {
console.error(error);
}
};
export const setNewMessage = (message, sender) => {
return {
type: SET_MESSAGE,
payload: { message, sender: sender || null },
};
};
// REDUCER
const reducer = (state = [], action) => {
switch (action.type) {
case GET_CONVERSATIONS:
return action.conversations;
case SET_MESSAGE:
return addMessageToStore(state, action.payload);
case ADD_CONVERSATION:
return addNewConvoToStore(
state,
action.payload.recipientId,
action.payload.newMessage
);
default:
return state;
}
};
I am getting an error saying Cannot read property 'conversationId' of undefined while using a reducer function... Should I give the setintial value of the message to empty?
export const addMessageToStore = (state, payload) => {
const { message, sender } = payload;
// if sender isn't null, that means the message needs to be put in a brand new convo
if (sender !== null) {
const newConvo = {
id: message.conversationId,
otherUser: sender,
messages: [message],
};
newConvo.latestMessageText = message.text;
return [newConvo, ...state];
}
return state.map((convo) => {
if (convo.id === message.conversationId) {
const convoCopy = { ...convo };
convoCopy.messages.push(message);
convoCopy.latestMessageText = message.text;
return convoCopy;
} else {
return convo;
}
});
};
Issue
The saveMessage function is declared async
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
but the postMessage action creator isn't async so it doesn't wait for the implicitly returned Promise to resolve before continuing on and dispatching to the store. This means that data.message is undefined since a Promise object doesn't have this as a property.
export const postMessage = (body) => (dispatch) => {
try {
const data = saveMessage(body); // <-- no waiting
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
Solution
Declare postMessage async as well and await the data response value.
export const postMessage = (body) => async (dispatch) => {
try {
const data = await saveMessage(body); // <-- await response
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
I would like to refactor the fetchRelationships function to use async await. I am not sure what the best way is to do it as this code contains nested .then at response.json().then((json) =>....
Could sby pls post the refactored version?
export const fetchRelationshipsError = (error) => {
return {
type: FETCH_RELATIONSHIPS_FAILURE,
payload: { error }
};
};
export const fetchRelationshipsRequest = () => {
return { type: FETCH_RELATIONSHIPS_REQUEST };
};
export const fetchRelationshipsSuccess = (relationships) => {
return {
type: FETCH_RELATIONSHIPS_SUCCESS,
payload: relationships
};
};
export const fetchRelationships = (url) => {
return (dispatch) => {
dispatch(fetchRelationshipsRequest());
fetch(url, config)
.then((response) => {
const responseObj = {
response: response,
payload: response.json().then((json) => {
return { body: json }
})
};
if (!response.ok) {
const error = new Error(response.statusText);
responseObj.payload.then((response) => {
show_error_alert(response.body);
dispatch(fetchRelationshipsError(response.body));
});
throw error;
}
return responseObj.payload;
})
.then((response) => {
dispatch(fetchRelationshipsSuccess(response.body))
})
.catch((error) => { console.log('Request failed', error); });
};
};
Solution:
export const fetchRelationships = (url) => {
return async (dispatch) => {
dispatch(fetchRelationshipsRequest());
try {
const response = await fetch(url, config);
const jsonResponse = await response.json();
if (!response.ok) {
show_error_alert(jsonResponse);
dispatch(fetchRelationshipsError(jsonResponse));
const error = new Error(response.statusText);
throw error;
}
dispatch(fetchRelationshipsSuccess(jsonResponse));
} catch(error) {
console.log('Request failed', error);
}
};
};
Ill take a stab at this:
export const fetchRelationshipsError = (error) => {
return {
type: FETCH_RELATIONSHIPS_FAILURE,
payload: { error }
};
};
export const fetchRelationshipsRequest = () => {
return { type: FETCH_RELATIONSHIPS_REQUEST };
};
export const fetchRelationshipsSuccess = (relationships) => {
return {
type: FETCH_RELATIONSHIPS_SUCCESS,
payload: relationships
};
};
export const fetchRelationships = (url) => {
return async (dispatch) => {
dispatch(fetchRelationshipsRequest());
try{
const response = await fetch(url, config)
const jsonResponse = await response.json()
if(!response.ok){
show_error_alert(jsonResponse);
dispatch(fetchRelationshipsError(jsonResponse));
const error = new Error(response.statusText);
throw error;
}
dispatch(fetchRelationshipsSuccess(jsonResponse));
}catch(error){
console.log('Request failed', error);
}
};
I try to implement a facebook login using react-native and redux but I'm face to a problem :
In my console, I have all information of the User but in the object for redux the authToken is undefined and I don't understand why ..
Here is my code
app/src/facebook.js
import {
LoginManager,
AccessToken,
GraphRequest,
GraphRequestManager,
} from 'react-native-fbsdk';
const facebookParams = 'id,name,email,picture.width(100).height(100)';
export function facebookLoginAPI() {
return new Promise((resolve, reject) => {
LoginManager.logInWithReadPermissions(['public_profile', 'user_friends', 'email'])
.then((FBloginResult) => {
if (FBloginResult.isCancelled) {
throw new Error('Login cancelled');
}
if (FBloginResult.deniedPermissions) {
throw new Error('We need the requested permissions');
}
return AccessToken.getCurrentAccessToken();
console.log(FBloginResult);
})
.then((result) => {
resolve(result);
console.log(result);
})
.catch((error) => {
reject(error);
console.log(error);
});
});
}
export function getFacebookInfoAPI() {
return new Promise((resolve, reject) => {
const profileInfoCallback = (error, profileInfo) => {
if (error) reject(error);
resolve(profileInfo);
};
const profileInfoRequest =
new GraphRequest(
'/me',
{
parameters: {
fields: {
string: facebookParams,
},
},
},
profileInfoCallback
);
new GraphRequestManager().addRequest(profileInfoRequest).start();
});
}
export function getFacebookFriends() {
return new Promise((resolve, reject) => {
const profileInfoCallback = (error, profileInfo) => {
if (error) reject(error);
console.log(profileInfo);
resolve(profileInfo);
};
const profileFriendsRequest =
new GraphRequest(
'/me/friends',
{
parameters: {
fields: {
string: facebookParams,
},
},
},
profileInfoCallback
);
new GraphRequestManager().addRequest(profileFriendsRequest).start();
});
}
the action (with all action types in another file)
import { facebookLoginAPI, getFacebookInfoAPI } from '../src/facebook';
import { getServerAuthToken } from '../src/auth';
import {
AUTH_STARTED,
AUTH_SUCCESS,
AUTH_FAILURE,
AUTH_ERROR,
AUTH_FAILURE_REMOVE,
LOGOUT
} from './types';
export function authStarted() {
return {
type: AUTH_STARTED,
};
}
export function authSuccess(facebookToken, facebookProfile, serverAuthToken){
return {
type: AUTH_SUCCESS,
facebookToken,
facebookProfile,
authToken: serverAuthToken,
};
}
export function authFailure(authError){
return {
type: AUTH_FAILURE,
authError,
};
}
export function authFailureRemove() {
return {
type: AUTH_FAILURE_REMOVE,
};
}
export function logout() {
return {
type: LOGOUT,
};
}
export function facebookLogin() {
return (dispatch) => {
dispatch(authStarted());
const successValues = [];
facebookLoginAPI()
.then((facebookAuthResult) => {
[...successValues, ...facebookAuthResult.accessToken];
return getFacebookInfoAPI(facebookAuthResult.accessToken);
}).then((facebookProfile) => {
[...successValues, ...facebookProfile];
return getServerAuthToken();
}).then((serverAuthToken) => {
[...successValues, ...serverAuthToken];
dispatch(authSuccess(...successValues));
}).catch((error) => {
dispatch(authFailure(error));
setTimeout(() => {
dispatch(authFailureRemove());
}, 4000);
});
};
}
And the reducer :
import {
AUTH_SUCCESS,
AUTH_FAILURE,
AUTH_STARTED,
AUTH_ERROR,
AUTH_FAILURE_REMOVE,
LOGOUT
} from '../actions/types';
const initialState = {
authenticating: false,
authToken: null,
authError: null,
facebookToken: null,
facebookProfile: null,
}
function authReducer(state = initialState, action) {
switch(action.type) {
case AUTH_STARTED:
return Object.assign({}, state, {
authenticating: true,
loginText: 'Connexion..'
});
case AUTH_SUCCESS:
return Object.assign({}, state, {
authenticating: false,
authToken: action.authToken,
facebookToken: action.facebookToken,
facebookProfile: action.facebookProfile,
});
case AUTH_FAILURE:
return Object.assign({}, state, {
authenticating: false,
authError: action.authError.message,
});
case AUTH_FAILURE_REMOVE:
return Object.assign({}, state, {
authError: null,
});
case LOGOUT:
return Object.assign({}, state, {
authenticating: false,
authToken: null,
facebookToken: null,
facebookProfile: null,
loginText: null,
});
default:
return state;
}
}
export default authReducer;
I need to understand what is the authToken, and why is he undefined in my case ? does the auth is success .. I don't know !
Thank you !
following code look little fishy to me
export function facebookLogin() {
return (dispatch) => {
dispatch(authStarted());
const successValues = [];
facebookLoginAPI()
.then((facebookAuthResult) => {
[...successValues, ...facebookAuthResult.accessToken]; //remove this line
return getFacebookInfoAPI(facebookAuthResult.accessToken);
}).then((facebookProfile) => {
[...successValues, ...facebookProfile]; //remove this seems of no use
return getServerAuthToken(); //I think you may need to pass something here
}).then((serverAuthToken) => {
[...successValues, ...serverAuthToken]; //pass this value in authSuccess below instead of ...successValues (it may still be [])
dispatch(authSuccess(...successValues));
}).catch((error) => {
dispatch(authFailure(error));
setTimeout(() => {
dispatch(authFailureRemove());
}, 4000);
});
};
}