bindActionCreators not getting to dispatch on array - javascript

I am trying to call actions on an array ob objects.The action is called on the onSuccess method of a previous action. When calling the action on a single object I am able to execute everything properly but it never executes when I call them in a forEach or a for Loop.
this is my import
import {createClient} from "../../store/actions/clients/clients.actions";
import {createNewContact} from "../../store/actions/contact-list/contact-list.actions";
this is my connect decorator
#connect(
state => ({avatarURL: state.avatar.URL}),
dispatch => bindActionCreators({modalVisibility, updateAvatarURL, createClient, createNewContact}, dispatch)
)
this is my two actions getting called
this.props.createClient({
newClientData, onSuccess: (newClientData) => {
for (let contact of values.contacts) {
contact.clientId = newClientData.id;
this.props.createNewContact(contact);
}
this.props.modalVisibility(false);
}
});
this is my action
export const createNewContact = (params) => {
debugger
return dispatch => {
let user = {
email: params.email,
name: params.firstName,
surname: params.lastName,
password: params.password,
clientId: params.clientId
};
let onSuccess = () => {
dispatch({
type: actions.CREATE_CLIENT_SUCCESS,
payload: {...params.newClientData}
});
if (params.onSuccess) params.onSuccess({...params.newClientData});
};
let onFailure = () => {
dispatch({
type: actions.CREATE_CLIENT_FAILURE,
payload: error
});
if (params.onFailure) params.onFailure(error);
};
awsServices.createUserInTheGroup({...user, group: config.groups.clients}, onSuccess, onFailure);
};
};
thanks in advance!

Related

Action running twice in react-redux

I am building a todo-app using React. I am dispatching an action(COMPLETED_TASK) to reducer after request/response from firestore DB. Since I'm also using redux-logger I noticed that this action is running twice.
Here's the code for Action:
export const completedTask = (data, completedBool) => async (
dispatch,
getState
) => {
try {
const formObj = cloneDeep(data);
const onlyDate = formObj.taskTime;
const getId = formObj._id;
const getDate = cleanDate(onlyDate, 'DD');
const getMonth = cleanDate(onlyDate, 'MM');
const getYear = cleanDate(onlyDate, 'YYYY');
const { uid } = getState().firebase.auth;
const todoUpdateString = `todoListByDate.${getDate}.${getId}.completed`;
await db.doc(`todos-col/${uid}&${getMonth}${getYear}`).update({
[todoUpdateString]: completedBool,
});
formObj.completed = completedBool;
dispatch({
type: todoTypes.COMPLETED_TASK,
payload: {
todosByMonthData: {
[`${getMonth}-${getYear}`]: {
[getDate]: { [getId]: formObj },
},
},
selectedDate: {
selectedDay: getDate,
selectedMonth: getMonth,
selectedYear: getYear,
},
},
});
} catch (err) {
console.log('Error!!', err);
}
};
In the screenshot, at 1, the action is dispatched to the reducer(it logs payload data from reducer below), and then again the action with type: "COMPLETED_TASK" is dispatched but it is not received by the reducer.
Why is this happening? Can anybody help?

Can't use new redux state right after fetching a response from Socket.IO

I have a function "sendMessage" in React class:
class MessageForm extends React.Component {
...
sendMessage = async () => {
const { message } = this.state;
if (message) {
this.setState({ loading: true });
if (this.props.isPrivateChannel === false) {
socket.emit("createMessage", this.createMessage(), (response) => {
this.setState({ loading: false, message: "", errors: [] });
});
} else {
if (this.state.channel && this.state.channel._id === undefined) {
socket.emit("createChannelPM", this.state.channel, async (response) => {
const chInfo = { ...response, name: this.props.currentChannel.name };
console.log("chInfo : ", chInfo);
await this.props.setCurrentChannel(chInfo).then((data) => {
if (data) {
console.log("data : ", data);
console.log("this.props.currentChannel : ", this.props.currentChannel);
}
});
});
}
...
function mapStateToProps(state) {
return {
isPrivateChannel: state.channel.isPrivateChannel,
currentChannel: state.channel.currentChannel,
};
}
const mapDispatchToProps = (dispatch) => {
return {
setCurrentChannel: async (channel) => await dispatch(setCurrentChannel(channel)),
}
};
Here, in sendMessage function, I retrieve "response" from socket.io, then put this data into variable "chInfo" and assign this to Redux state, then print it right after assinging it.
And Redux Action function, "setCurrentChannel" looks like:
export const setCurrentChannel = channel => {
return {
type: SET_CURRENT_CHANNEL,
payload: {
currentChannel: channel
}
};
};
Reducer "SET_CURRENT_CHANNEL" looks like:
export default function (state = initialState, action) {
switch (action.type) {
case SET_CURRENT_CHANNEL:
return {
...state,
currentChannel: action.payload.currentChannel
};
...
The backend Socket.io part look like (I use MongoDB):
socket.on('createChannelPM', async (data, callback) => {
const channel = await PrivateChannel.create({
...data
});
callback(channel)
});
The console.log says:
Problem : The last output, "this.props.currentChannel" should be same as the first output "chInfo", but it is different and only print out previous value.
However, in Redux chrome extension, "this.props.currentChannel" is exactly same as "chInfo":
How can I get and use newly changed Redux states immediately after assinging it to Redux State?
You won't get the updated values immediately in this.props.currentChannel. After the redux store is updated mapStateToProps of MessageForm component is called again. Here the state state.channel.currentChannel will be mapped to currentChannel. In this component you get the updated props which will be accessed as this.props.currentChannel.
I believe you want to render UI with the latest data which you which you can do.

How to return a promise from redux thunk action and consume it in component

I am using React+Redux+Redux Thunk + Firebase authentication. Writing code in Typescript.
My action is:
//Type for redux-thunk. return type for rdux-thunk action creators
type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
IStoreState, //my store state
null,
Action<userActionTypes>
>
export const signInWithEmailAndPasword =(email:string, pasword:string): AppThunk=>{
return async (dispatch)=>{
auth.signInWithEmailAndPassword(email, pasword).then(response=>{
if(response.user){
const docRef = db.collection("users").doc(response.user.uid);
docRef.get().then(function(doc) {
if (doc.exists) {
const userData = doc.data(); //user data from firebase DB
//if user exists in DB, dispatch
dispatch({
type: userActionTypes.SIGN_IN_USER,
payload: userData
})
return userData;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
}
})
.catch(err=> dispatch(setUserError(err.message)))
}
}
My SignIn component, where i am dispatching this above action:
import React, { useState } from 'react'
//some other imports
//...
//
import { useDispatch, useSelector } from 'react-redux';
import { signInWithEmailAndPasword } from '../../redux/actions/userActions';
interface ISignInState {
email: string;
password: string;
}
const SignIn = (props:any) => {
const [values, setValues] = useState<ISignInState>({ email: '', password: '' })
const dispatch = useDispatch();
const handleInputChange = (e: React.FormEvent<HTMLInputElement>): void => {
const { name, value } = e.currentTarget;
setValues({ ...values, [name]: value })
}
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault()
const { email, password } = values;
dispatch(signInWithEmailAndPasword(email, password))
//// -> gives error: Property 'then' does not exist on
//// type 'ThunkAction<void, IStoreState, null, Action<userActionTypes>>'
.then(()=>{
props.history.push('/');
setValues({ email: '', password: '' })
})
}
return (<div>Sign in UI JSX stuff</div>)
So when i try to use .then() after dispatch(signInWithEmailAndPasword(email, password)) it gives an error Property 'then' does not exist on type 'ThunkAction<void, IStoreState, null, Action<userActionTypes>>'
So how can i return promise from redux action and chain a .then() on it? I always assumed that thunk actions return promises by default.
Thanks for your help
Edit:
Temporary soluton was to use any as return type of above action:
export const signInWithEmailAndPasword = (email:string, pasword:string):any =>{
return async (dispatch: any)=>{
try {
const response = await auth.signInWithEmailAndPassword(email, pasword)
if(response.user){
const userInDb = await getUserFromDB(response.user)
dispatch(userSignIn(userInDb))
return userInDb
}
} catch (error) {
dispatch(setUserError(error.message))
}
}
}
But I don't want to use any
Just add return before this line:
auth.signInWithEmailAndPassword(email, pasword).then(response=>{
So it would be:
export const signInWithEmailAndPasword =(email:string, pasword:string): AppThunk=>{
return async (dispatch)=>{
return auth.signInWithEmailAndPassword(email, pasword).then(response=>{
It should work.
AppThunk<Promise<void>>
You need to explicitly declare the AppThunks return type, which in this case should be a Promise containing nothing. You have already made it async so just make sure to enter the correct AppThunk return type
export const signInWithEmailAndPassword = (email: string, password: string): AppThunk<Promise<void>> => {
return async (dispatch) => {
// do stuff
}
}
Thunks return functions, not promises. For this you could look at redux-promise. But to be honest if your doing something this complex you would be much better off using redux-saga.
Another approach would be to use the concepts behind redux-api-middleware to create your own custom redux middleware. I have done this in the past to connect a message queue to redux.

Converting from class to functional component with async state setting

I have a simple class-based component that I'm trying to convert to a function-based component, but am running into all kinds of dead ends.
My component is a straightforward adaptation of the boilerplate gifted-chat package, and uses Watson Assistant as a backend to provide responses. There's nothing complex about the backend part, these are just thin wrappers on Watson Assistants's API:
getSessionID = async (): Promise<string>
gets a session ID for use in communicating with the backend, and
sendReply = async (reply: string, sessionID: string): Promise<string>
returns Assistant's response to the string provided as a reply. These are not the source of the trouble I'm having (the bodies of both could be replaced with return await "some string" and I'd have the same issues): the class-based version (below) works perfectly.
But I'm at a loss to figure out how to convert this to a functional form, in particular:
I'm struggling to find a suitable replacement for componentWillMount. Using useEffect with sessionID as state results in errors: getMessage gets called (even if I await) before the required sessionID is set.
I can avoid this by not making sessionID state (which it arguably shouldn't be) and just making it a global (as in the functional attempt below). But even if I do this:
After each user reply, and receipt of a response, the user reply is removed from the conversation, so that the entire conversation just consists of generated replies.
Both of these problems are, I think, linked to the lack of callbacks in the hook-based state setting idiom, but the issue could also lie elsewhere. In any case, I'm at a loss to know what to do.
Chatter.tsx (working class based version)
import React from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
class Chatter extends React.Component {
state = {
messages: [],
sessionID: null,
}
componentWillMount() {
WatsonAssistant.getSessionID()
.then((sID) => {
this.setState( {
sessionID: sID,
} )
} )
.then(() => this.getMessage(''))
.catch((error) => {
console.error(error)
} )
}
onSend = (message = []): void => {
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ), () => {
this.getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
} )
}
getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, this.state.sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ))
}
render() {
return (
<GiftedChat
messages={ this.state.messages }
onSend={ messages => this.onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
}
export default Chatter
Chatter.tsx (failed function based attempt)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage(''))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages)
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
}
const getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(messages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Chatter.tsx (working function based version)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage('', []))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages) // Apparently, no waiting goes on here
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '), newMessages)
}
const getMessage = async (text: string, currentMessages): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(currentMessages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Ok, since I don't have your full code I'm not sure this will just work as-is (in particular without the types from your dependencies I'm not sure if/how much the compiler will complain), but should give you something you can adapt easily enough.
const reducer = ({ messages }, action) => {
switch (action.type) {
case 'add message':
return {
messages: GiftedChat.append(messages, action.message),
};
case 'add sent message':
return {
// Not sure if .append is variadic, may need to adapt
messages: GiftedChat.append(messages, action.message, action.message[0].text.replace(/[\n\r]+/g, ' ')),
}
}
};
const Chatter = () => {
const [sessionID, setSessionID] = useState(null);
const [messages, dispatch] = useReducer(reducer, []);
const getMessage = async (text: string, sessionID: number, type: string = 'add message'): Promise<void> => {
const response = await WatsonAssistant.sendReply(text, sessionID);
const message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
};
dispatch({
type,
message,
});
};
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => (setSessionID(sID), sID))
.then(sID => getMessage('', sID))
.catch((error) => {
console.error(error)
});
}
fetchData();
}, []);
return (
<GiftedChat
messages={messages}
onSend={messages => getMessage(messages, sessionID, 'add sent message')}
user={{
_id: 1,
}}
/>
);
};
Main difference is useReducer. As far as I can tell in the original code you had two actions: append this message or append this message and then a copy of it with the text regex replaced. I've used different dispatches to the reducer to handle the cases rather than the callback to setState. I've modified your attempt at useEffect, here I'm (ab)using the comma operator to return the ID returned from the service so that it can be fed directly to getMessage as a parameter rather than relying on state that hasn't been updated yet.
I'm still kinda skeptical in general about the hooks API, but assuming this works I actually think it simplifies the code here.

How to test vuex plugins store.subscribe

I'm using jest to test a vue application, I have a doubt how can I test a plugin code. This is the code I'm trying to test:
export const persistPlugin = store => {
store.subscribe(async (mutation, state) => {
// filter all keys that start with `__`
const _state = omitPrivate(state);
const storedState = await storage.get('state');
if (isEqual(_state, storedState)) return;
storage.set(store, 'state', _state);
});
};
What I'm stuck at is the store.subscribe part. store is passes as argument of the plugin method, but I don't know how to call this method from the test is a wat that triggers the function block of the plugin.
You could use testPlugin helper for this. Here it is an example which you could adapt for the state verification.
I prefer to track mutations instead of direct state changes:
import { persistPlugin } from "#/store";
export const testPlugin = (plugin, state, expectedMutations, done) => {
let count = 1;
// mock commit
const commit = (type, payload) => {
const mutation = expectedMutations[count];
try {
expect(type).toEqual(mutation.type);
if (payload) {
expect(payload).toEqual(mutation.payload);
}
} catch (error) {
done(error);
}
count++;
if (count >= expectedMutations.length) {
done();
}
};
// call the action with mocked store and arguments
plugin({
commit,
state,
subscribe: cb =>
cb(expectedMutations[count - 1], expectedMutations[count - 1].payload)
});
// check if no mutations should have been dispatched
if (expectedMutations.length === 1) {
expect(count).toEqual(1);
done();
}
};
describe("plugins", () => {
it("commits mutations for some cases", done => {
testPlugin(
persistPlugin,
{ resume: { firstName: "Old Name" } },
[{ type: "updateResume", payload: { firstName: "New Name" } }], // This is mutation which we pass to plugin, this is payload for plugin handler
[{ type: "updateResume", payload: { firstName: "New Name" } }], // This is mutation we expects plugin will commit
done
);
});
});

Categories