How to listen for Modal submission on Slack Bolt? - slack

I've been looking through the docs for a day now, and still can't figure out this simple question: what is the #slack/bolt ^2.3.0 method that is used to listen for a Modal submission?
const slackbot = new App({ token, signingSecret });
slackbot.shortcut(
"open_modal",
async ({ ack, body, client, context, payload }) => {
await ack();
const result = await client.views.open({ ... });
}
);
slackbot.METHOD_HERE(ID_HERE,
async ({ ack }) => {
ack();
// handle modal submitted data here...
}
);
What values take the place of METHOD_HERE and ID_HERE in the code above?
I am able to open a modal without problem via the global shortcuts menu; but can't seem to figure out how to capture user's submission of the modal.
For example, this never captures any submissions:
slackbot.view("open_modal", async ({ ack }) => {
ack();
// do things here...
});

You need to use the callback_id used when you create the Modal view:
slackbot.shortcut(
"open_modal",
async ({ ack, body, client, context, payload }) => {
await ack();
const result = await client.views.open({
trigger_id: body.trigger_id,
view: {
type: "modal",
callback_id: "YOUR_CALLBACK_ID", // <= listen for this ID
title: {
type: "plain_text",
text: "Modal Title",
},
blocks: [ ... ],
},
});
}
);
Then, to listen to submissions on the above Modal, use this:
app.view('YOUR_CALLBACK_ID', optionalMiddleWareFunction, async ({ payload }) => {
const submittedValues = payload.view.state.values
// do stuff with submittedValues
});
callback_id is the ID that you defined when creating modal view. You can see an example here.
You can read the corresponding official documentation here.

Related

useEffect doesn't track changes when new item in my database is created

I'm fetching all the user's transactions within useEffect() but when I create a new transaction the useEffect doesn't reload then I need to refresh the page in order to see the changes. I've searched a lot and I've tried somethings such as useCallback() and useRef() with useEffect() but still doesn't work, probably because I'm not quite understand how to use them properly. When I pass the data that I want to be watched, in my case [transactions] I get an infinite loop because setState will mudate my state and then the component will reload hence useEffect will call my function and setState will be trigger and this will happen all over again.
const [transactions, setTransactions] = useState([]);
useEffect(() => {
getUserTransactions();
}, []);
const getUserTransactions = async () => {
if (currentUser) {
const token = await firebase.auth().currentUser.getIdToken();
axios
.get("http://localhost:8080/transactions", {
headers: {
"Content-Type": "application/json",
Authorization: token,
},
})
.then((res) => {
setTransactions(res.data);
})
.catch((err) => console.log(err));
}
};
I was wondering if the async operation can cause this issue because in another project, I haven't had any problem.
const createTransaction = async (e) => {
e.preventDefault();
if (currentUser) {
const token = await firebase.auth().currentUser.getIdToken();
const data = {
title: textRef.current.value,
price: priceRef.current.value,
category: categoryRef.current.value,
};
axios
.post("http://localhost:8080/transactions", data, {
headers: {
"Content-Type": "application/json",
Authorization: token,
},
})
.then((res) => {
setTransactions(prevTransactions => [...prevTransactions, res.data.rows])
})
.catch((err) => console.log(err));
setIsOpen(false);
}
};
That because the dependency array of your useEffect is empty, meaning it happens only once, on the initial render of the page.
The purpose of useEffect is to react to state changes, it's unaware of your backend. So when adding a new transaction, you should change the state accordingly. I'd suggest something along those lines:
When you add a transaction, the server's response will include all the details you need to display it on your frontend, so when you get the response object, just change the state by appending it. Something like:
const addTransaction = async () => {
const newTransactionData = (await axios.post('http://localhost:8000/transactions/add' /*or whatever your request looks like*/)).data
setTransactions(prevTransactions => [...prevTransactions, newTransactionData])
}

Why Redux need two times dispatch

The following is my App.js
class Root extends Component {
constructor(props) {
super(props);
this.state = {
data:''
}
}
componentDidMount() {
const { dispatch } = this.props;
dispatch(actions.getTable((data) => {
console.log(data)
}));
}
}
Then the following is my action.js:
export function getTable(callback) {
return (dispatch) => {
axios.get({
api: 'localhost/test',
})
.then((list) => {
callback(list.data)
dispatch({
type: RECEIVE_TABLE_DATA,
data: list.data,
});
})
};
}
As my understanding the data had already been dispatched to Reducer in action.js, such as:
dispatch({
type: RECEIVE_TABLE_DATA,
data: list.data,
});
then, why the data was dispatched again in App.js, such as:
dispatch(actions.getTable((data) => {
console.log(data)
}));
From looking at your code, I'm assuming you are using redux-thunk. With redux-thunk, you can use a thunk, which is a function that can dispatch other actions, which is useful when dealing with async activity. A thunk can also be dispatched as a regular action.
For example, in your code, getTable is an action creator which returns a thunk. That's why it can dispatch the RECEIVE_TABLE_DATA action. Notice it could dispatch another action, such as FETCH_TABLE_DATA before the axios call for example.
So this code
dispatch(actions.getTable((data) => {
console.log(data)
}));
dispatch a thunk. It does not dispatch the data again.
If you remove
dispatch({
type: RECEIVE_TABLE_DATA,
data: list.data,
});
from the getTable function, the data won't be dispatched at all.
I recommend you read the documentation from https://github.com/reduxjs/redux-thunk or the async example from the redux doc. In this file for example https://github.com/reduxjs/redux/blob/master/examples/async/src/actions/index.js you can see the difference between a regular action creator such as requestPosts or receivePosts, and a thunk action creator such as fetchPosts. I hope it helps :)

Can the completion of one async call be sequenced before the start of another using useEffect?

I'm trying to use useEffect in my React app but also refactor things more modularly. Shown below is the heart of actual working code. It resides in a Context Provider file and does the following:
1. Calls AWS Amplify to get the latest Auth Access Token.
2. Uses this token, in the form of an Authorization header, when an Axios GET call is made to an API Endpoint.
This works fine but I thought it would make more sense to move Step #1 into its own useEffect construct above. Furthermore, in doing so, I could then also store the header object as its own Context property, which the GET call could then reference.
Unfortunately, I can now see from console log statements that when the GET call starts, the Auth Access Token has not yet been retrieved. So the refactoring attempt fails.
useEffect(() => {
const fetchData = async () => {
const config = {
headers: { "Authorization":
await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
alert('Error getting authorization token: '.concat(error))
})
}};
await axios.get('http://127.0.0.1:5000/some_path', config)
.then(response => {
// Process the retrieved data and populate in a Context property
})
.catch(error => {
alert('Error getting data from endpoint: '.concat(error));
});
};
fetchData();
}, [myContextObject.some_data]);
Is there a way of refactoring my code into two useEffect instances such that the first one will complete before the second one starts?
You could hold the config object in a state. This way you can separate both fetch calls and trigger the second one once the first one finished:
const MyComponent = props => {
const myContextObject = useContext(myContext);
const [config, setConfig] = useState(null);
useEffect(() => {
const fetchData = async () => {
const config = {
headers: {
Authorization: await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
alert("Error getting authorization token: ".concat(error));
})
}
};
setConfig(config);
};
fetchData();
}, [myContextObject.some_data]);
useEffect(() => {
if (!config) {
return;
}
const fetchData = async () => {
await axios
.get("http://127.0.0.1:5000/some_path", config)
.then(response => {
// Process the retrieved data and populate in a Context property
})
.catch(error => {
alert("Error getting data from endpoint: ".concat(error));
});
};
fetchData();
// This should work for the first call (not tested) as it goes from null to object.
// If you need subsequent changes then youll have to track some property
// of the object or similar
}, [config]);
return null;
};

Loader icon in Bot Framework Webchat

I am using Bot Framework Webchat. There are few user related data which I am posting using back channel post activity through the store option to greet the user.
<ReactWebChat
activityMiddleware={ activityMiddleware }
directLine={ window.WebChat.createDirectLine( this.state.token ) }
store = {this.handleGetStore()}
styleOptions={styleOptions}
/>
handleGetStore returns the store data:
handleGetStore(){
const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'userDetail',
value: this.state.userDetail
}
});
}
return next(action);
});
return store;
}
When the connection initiates the loader appears.
After that there is delay of about 3-5 seconds before the welcome message appears and in the meantime the Webchat seems ready for the user.
A slight delay of 3 seconds is acceptable but quite often the delay is upto 10 seconds or more. I understand that this can be slightly improved by using the Always On feature of the App Service and scaling up the plan. Is there a way I can wait for the back channel welcome message to appear and show the loader until then?
Reference: https://github.com/microsoft/BotFramework-WebChat/pull/1866
Unfortunately, the connection status display relies on events received from DirectLineJs and Web Chat does not support customizing its behavior at the moment. That being said, there is a hacky way to accomplish what you're trying to do by dispatching pseudo DirectLine events.
Here are the steps below:
Create a flag that will indicate whether or not the bot has sent a welcome message - received_welcome_message.
When Web Chat dispatches a connection fulfilled event, check the flag
to ensure a welcome message has been received. If the bot has not
sent a welcome message, dispatch the welcome event to the bot and reset the
connection status to fulfilling.
When Web Chat receives an activity
from the bot, check if it is a welcome message. I would recommend
adding a name attribute to message on the bot side to check - await
context.sendActivity({ text: 'Welcome', name: 'welcome'}). If the
activity is a welcome message, dispatch a connection fulfilled event and set the flag to true.
For more details take a look at the code snippets below.
let received_welcome_message = false;
const store = createStore(
{},
({ dispatch}) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
if (!received_welcome_message) {
dispatch({
type: 'DIRECT_LINE/CONNECT_FULFILLING'
});
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: { name: 'webchat/join' }
});
return
}
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY' && action.payload.activity.name === 'welcome') {
received_welcome_message = true;
dispatch({
type: 'DIRECT_LINE/CONNECT_FULFILLED',
});
}
return next(action);
}
);
Edit
A less hacky approach is to dispatch a post activity pending event when the connection to the bot is fulfilled to mimic the bot sending a welcome message. Note, that the bot is unaware of the mimicked activity. See the code snippet below.
const store = createStore(
{},
({ dispatch}) => next => action => {
console.log(action)
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'DIRECT_LINE/POST_ACTIVITY_PENDING',
meta: { method: 'keyboard' },
payload: {
activity: {
from: { role: "bot" },
text: "Welcome Message",
textFormat: "plain",
timestamp: new Date().toString(),
type: "message"
}
}
})
}
return next(action);
}
Hope this helps!

redux epic fires only once

I saw a related post about this issue, but my set up is a little different, so I don't have a solution yet.
I have this action creator and epic:
export const postToSendEmail = (emailBody: any) => ({ type: EMAIL.POST_EMAIL, payload: emailBody });
export const sendEmailEpic = (action$: any) => action$.pipe(
ofType(EMAIL.POST_EMAIL),
mergeMap((action: any) =>
return Observable.ajax({
url: "http://...",
body: action.payload,
...
}).flatMap(() => of(sendEmailDone()))
),
catchError((error: any, source: any) => {
// this doesn't work
of(handleError(error));
// this restarts the epic
return source;
})
);
In a react component, I have a button that just calls the actionCreator:
<button onClick={this.sendEmail}>send email</button>
sendEmail() {
this.props.dispatch(postToSendEmail({ test: "test" }));
}
Clicking the button the first time works fine, and I see the api call in the Network, but clicking it a second time never fires. Why is that?
EDIT:
I updated the catch, but now, how do I dispatch the action I need? Using of(handleError) doesn't seem to actually call it.

Resources