How do I pass browser properties to react app talking to MSBOT? - reactjs

We have hosted a bot on ServiceNow and would now like to pass attributes from the browser to the BOT. How can I make this happen?
This question is actually part 2 of a question I had posted & which I have already found a solution for.
Since the BOT is already logged into ServiceNow. I want to extract some elements from the background/servicenow page source and pass it to the react app as shown below. The BOT authenticates the user by email so it would act like a SSO because he is already connected to ServiceNow with the same email id. We therefore want to simply pass that value.
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
setTimeout(() => {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language,
userid: "a.b#c.d",
username: "a.b#c.d"
}
}
});
}, 1000);
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
if (action.payload.activity.from.role === 'bot') {
this.setState(() => ({ newMessage: true }));
}
}
return next(action);
});

You can pass the data from your page to your bot by dispatching an event listener on the page and catching the event in the web chat implementation.
In this example, I am simulating a user having logged in with a button click. The button click creates the event. When the event is registered, it is picked up by web chat which then takes the values stored in window.NOW.user and forwards that data to the bot. To help drive the point home, I am sending a message greeting the user by name while also sending the data (name and email) behind the scenes.
Hope of help!
app.js: Imports the view for display.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import WebChatView from './webChatView';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Route path="/" exact component={WebChatView} />
</div>
</Router>
);
}
}
export default App;
webChatView.js: I import the webchat component into the view and create a function that, on click (again, just to simulate someone having logged in), creates and dispatches an event.
import React, { Component } from 'react';
import WebChat from './webchat';
class WebChatView extends Component {
constructor(props) {
super(props);
this.sendToBot = this.sendToBot.bind(this);
}
render() {
return (
<div>
<div>
<WebChat id="webchat" role="main" />
</div>
<div>
<button id="loginBtn" onClick={this.sendToBot}>Login</button>
</div>
</div>
)
}
sendToBot = () => {
let sendToBotEvent = new Event('sendToBot')
window.dispatchEvent(sendToBotEvent);
window['NOW'] = {
'user': {
'name': 'John Doe',
'email': 'john.doe#somedomain.com'
}
}
}
}
export default WebChatView;
webchat.js: Lastly, is web chat, itself. I create a store and an event listener. When the event is dispatched in the window, the message/data is also dispatched to the bot. Because I'm simulating logging in as one step, I have included a setTimeout so the window.NOW.user data has a chance to save. The store is passed to <ReactWebChat> which, subsequently, sends the associated data to the bot for processing.
import React from 'react';
import ReactWebChat, { createDirectLine, createStore, } from 'botframework-webchat';
export default class WebChat extends React.Component {
constructor(props) {
super(props);
const store = window.WebChat.createStore();
window.addEventListener('sendToBot', () => {
setTimeout(() => {
store.dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'Service Now user name',
value: window.NOW.user
}
})
store.dispatch({
type: 'WEB_CHAT/SEND_MESSAGE',
payload: {
text: `Hi ${window.NOW.user.name}!`
}
})
}, 300)
})
}
this.state = {
store: store
};
componentWilUnmount() {
window.removeEventListener('sendToBot', null)
}
render() {
return (
this.state.directLine ?
<ReactWebChat
directLine={this.state.directLine}
store={this.state.store}
/>
:
<div>Connecting to bot…</div>
)
}
}

Related

How to access naviagtion options from imported file in react-native

I'm passing data through different pages down to the last page in my app, its been working fine.
But the issue is the last page has 2 components so the typical </ChatActivity navigation="{this.props.navigation}" />, here's what I mean:
I have an App.js
content of App.js
import ChatScreen from './chat'
class ChatActivity extends Component {
static navigationOptions = {
...
}
render() {
return(
<ChatScreen navigation={this.props.navigation} />
)
}
}
I also have chat.js that contains the chat component. Chat.js itself, needs to import Fire from './fire.js'
so now, this.props.navigation was only passed to Chat.js...but I need to access it from fire.js as well.
I've read about import {useNavigation}, but from what i have tried it didn't work cause my fire.js doesn't even look like the example in the docs
this is my fire.js
class Fire extends React.Component{
constructor (props) {
super(props)
this.init()
this.checkAuth()
}
init = () => {
firebase.initializeApp({
})
};
checkAuth = () => {
firebase.auth().onAuthStateChanged(user => {
if (!user) {
firebase.auth().signInAnonymously();
}
})
}
send = messages => {
messages.forEach(item => {
const message = {
text: item.text,
timestamp: firebase.database.ServerValue.TIMESTAMP,
// image: item.image,
//video: item.video,
user: item.user
}
this.db.child(`NEED NAVIGATION PARAMS HERE`).push(message)
})
}
parse = message => {
const {user, text, timestamp} = message.val();
const {key, _id} = message
const createdAt = new Date(timestamp)
return {
_id,
createdAt,
text,
user
}
}
get = callback => {
this.db.child(`NEED NAVIGATION PARAMS HERE`).on('child_added', snapshot => callback(this.parse(snapshot)))
}
off() {
this.db.off()
}
get db() {
return firebase.database().ref(`NEED NAVIGATION PARAMS HERE`);
}
get uid(){
return(firebase.auth().currentUser || {}).uid
}
}
export default new Fire();
Since i couldn't access navigation params, I tried AsyncStorage, but thats probably not the best practice and it isn't working too well. Not sure if its the AsyncStorage or react-native-gifted-chat but when I load the chat page once, it shows the same messages for other chats till I restart the app which shouldn't be cause i'm fetching the data based on unique parameters.
You have just missed one step here...
Since you have passed the navigation as props by using the following approach:
<ChatScreen navigation={this.props.navigation} />
the chat screen gets to use navigation properties of ChatActivity.
For Fire.js to be able to use the navigation as well, that was provided to Chat.js by ChatActivity you will need to pass the navigation props received by Chat.js to Fire.js in the same way.
This is how your Chat.js should look like:
import Fire from './Fire'
class Chat extends Component {
static navigationOptions = {
...
}
render() {
return(
<Fire navigation={this.props.navigation} />
)
}
}
That should solve the issue. Cheers!

ReactJS infinite loop after react-select dropdown selection

I just started teaching myself ReactJS a few weeks ago and I'm stuck trying to figure out why my API gets consistently hit in an infinite loop after selecting a value from a dropdown. I have a search component called StateSearch.js that is being rendered in the StatePolicyPage.js component.
In StatePolicyPage.js I call <StateSearch parentCallback={this.callbackFunction} /> so that I can get the value the user picked from the dropdown and set the state. In StateSearch.js I'm passing the selected value using props.parentCallback(response.data)
The problem is that an infinite loop occurs for some reason and my Rails API keeps getting called over and over instead of just returning the data one time.
(StateSearch.js) search component
import React, { useState } from 'react'
import Select from 'react-select'
import makeAnimated from 'react-select/animated';
import axios from 'axios';
import statesJSON from '../../helpers/states';
// uses 'react-select'
export default function StateSearch(props) {
const [americanState, setAmericanState] = useState();
// if a state was selected from the dropdown
if (americanState) {
axios.get("http://localhost:3001/get_stuff", {
params: {
state: americanState.value
}
}).then(response => {
// the response back from the Rails server
if (response.status === 200) {
props.parentCallback(response.data); // send data back up to parent
}
}).catch(error => {
console.log("Error fetching the state ", americanState.value, error);
})
event.preventDefault();
}
// the dropdown select box for states.
return (
<div>
<Select
options={statesJSON}
placeholder="Select a State"
onChange={setAmericanState}
noOptionsMessage={() => 'Uh-oh nothing matches your search'}
className=""
components={makeAnimated()}
isSearchable
isClearable={true}
/>
</div>
)
}
(StatePolicyPage.js) the component that the search results should be passed to
import React, { Component } from 'react'
import Navigation from './Navigation';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import StateSearch from './search/StateSearch';
export default class StatePolicyPage extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
stateName: '',
updatedAt: '',
createdAt: ''
}
}
callbackFunction = (childData) => {
console.log(childData);
this.setState({
id: childData.id,
stateName: childData.state_name,
updatedAt: childData.updated_at,
createdAt: childData.created_at
})
}
render() {
return (
<div>
<Navigation
isLoggedIn={this.props.loggedInStatus}
user={this.props.user}
handleLogoutClick={this.props.handleLogoutClick}
handleLogout={this.props.handleLogout}
/>
<Container>
{/* get the dropdown value from the StateSearch back */}
<StateSearch parentCallback={this.callbackFunction} />
<div>
<Row>
{ this.state.id }
</Row>
</div>
</Container>
</div>
)
}
}
Always use useEffect() hook for asynchronous tasks.
useEffect(() => {
// if a state was selected from the dropdown
if (americanState) {
axios.get("http://localhost:3001/get_stuff", {
params: {
state: americanState.value
}
}).then(response => {
// the response back from the Rails server
if (response.status === 200) {
props.parentCallback(response.data); // send data back up to parent
}
}).catch(error => {
console.log("Error fetching the state ", americanState.value, error);
})
}
}, [americanState]);
This line looks to me like it could cause an infinite loop:
components={makeAnimated()}
I'm not entirely sure what this function is doing, but when passing functions to another component you can't directly invoke them.
Try replacing the above line with this:
components={makeAnimated}
or with this:
components={() => makeAnimated()}

server side rendering react Issue rendering a subroute

I have a route: /events with a subroute /events/:id
I created a reducer and an action for fetching all the events and display them in the eventsPage.
This part works fine.
Now from the events page, when I click on an event I'm trying to navigate to /events/:id
In the events/:id page I created an action which is called in the componendDidMount callback then display the event from there.
It works fine when I click on a link to an event's details page from the list. Now I'm looking for the way to perform the request on the server side so when I do a full reload of the page, the selected event is still displayed.
Here is my events/:id page component
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {fetchEvent} from '../actions/eventsActions'
class EventDetail extends Component {
constructor(props){
super(props);
this.state = {
event: null
}
}
componentDidMount(){
this.props.fetchEvent(this.props.match.params.slug).then(response => {
this.setState({
event: response.event
})
}).catch(error => {
console.log('error', error);
});
}
render() {
return (
<div>
{JSON.stringify(this.state)}
</div>
);
}
}
function loadData(store){
}
export default {
component: connect(null, {fetchEvent})(EventDetail),
loadData
};
my fetch_event action
import {FETCH_EVENT} from './types';
export const fetchEvent = (event_slug) => async(dispatch, getState, api) => {
const res = await api.get(`/api/events/${event_slug}`);
dispatch({
type: FETCH_EVENT,
payload: res.data
})
return res.data;
}
How can I fix this?

How to refresh a List View in admin on rest

I am trying to get a list to refresh after a custom action was successfully executed.
i used the saga from the admin on rest tutorial
function * actionApproveSuccess () {
yield put(showNotification('Executed'))
yield put(push('/comments'))
// does not refresh, because the route does not change
// react-redux-router also has no refresh() method, like react-router has...
}
the other idea i had was to somehow trigger the refresh action of the list component, but i have no idea how to access that or how to hook that up to the ACTION_SUCCESS event.
There is no way to refresh a route via react router, and that's a known problem. Admin-on-rest's List component has its own refresh mechanism, but offers no API for it.
My advice would be to use a custom <List> component based on admin-on-rest's one. And if you find a way to expose the refresh action, feel free to open a PR on the aor repository!
#Danila Smirnov's answer above shows this message when I use it now:
Deprecation warning: The preferred way to refresh the List view is to connect your custom button with redux and dispatch the refreshView action.
Clicking the refresh button itself wasn't working either nowadays.
Here's the tweaked version that I got working in mine.
Edit: Modified it a bit more to make it reusable.
RefreshListActions.js
import React, { Component } from 'react'
import FlatButton from 'material-ui/FlatButton'
import { CardActions } from 'material-ui/Card'
import NavigationRefresh from 'material-ui/svg-icons/navigation/refresh'
import { connect } from 'react-redux'
import { REFRESH_VIEW } from 'admin-on-rest/src/actions/uiActions'
import { refreshView as refreshViewAction } from 'admin-on-rest/src/actions/uiActions'
class MyRefresh extends Component {
componentDidMount() {
const { refreshInterval, refreshView } = this.props
if (refreshInterval) {
this.interval = setInterval(() => {
refreshView()
}, refreshInterval)
}
}
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
const { label, refreshView, icon } = this.props;
return (
<FlatButton
primary
label={label}
onClick={refreshView}
icon={icon}
/>
);
}
}
const RefreshButton = connect(null, { refreshView: refreshViewAction })(MyRefresh)
const RefreshListActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refreshInterval }) => (
<CardActions>
{filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) }
<RefreshButton primary label="Refresh" refreshInterval={refreshInterval} icon={<NavigationRefresh />} />
</CardActions>
);
export default RefreshListActions
In my list that I want to refresh so often:
import RefreshListActions from './RefreshListActions'
export default (props) => (
<List {...props}
actions={<RefreshListActions refreshInterval="10000" />}
>
<Datagrid>
...
</Datagrid>
</List>
)
Definitely hacky, but a work-around could be:
push('/comments/1') //any path to change the current route
push('/comments') //the path to refresh, which is now a new route
using refreshView action via redux works well.
see example....
import { refreshView as refreshViewAction } from 'admin-on-rest';
import { connect } from 'react-redux';
class MyReactComponent extends Component {
//... etc etc standard react stuff...
doSomething() {
// etc etc do smt then trigger refreshView like below
this.props.refreshView();
}
render() {
return <div>etc etc your stuff</div>
}
}
export default connect(undefined, { refreshView: refreshViewAction })(
MyReactComponent
);
I've solve this task with small hack via Actions panel. I'm sure it is not correct solution, but in some situations it can help:
class RefreshButton extends FlatButton {
componentDidMount() {
if (this.props.refreshInterval) {
this.interval = setInterval(() => {
this.props.refresh(new Event('refresh'))
}, this.props.refreshInterval)
}
}
componentWillUnmount() {
clearInterval(this.interval)
}
}
const StreamActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refresh }) => (
<CardActions>
{filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) }
<RefreshButton primary label="Refresh streams" onClick={refresh} refreshInterval={15000} refresh={refresh} icon={<NavigationRefresh />} />
</CardActions>
);
export default class StreamsListPage extends Component {
render() {
return (
<List
{...this.props}
perPage={20}
actions={<StreamActions />}
filter={{ active: true }}
title='Active Streams'>
<StreamsList />
</List>
)
}
}
The push is just a redirect for AOR which did not seem to work for me either. What guleryuz posted was on the right track for me.. Here's what I did building on his example:
// Import Statement
import { refreshView as refreshViewAction } from 'admin-on-rest';
class RemoveButton extends Component {
handleClick = () => {
const { refreshView, record, showNotification } = this.props;
fetch(`http://localhost:33333/api/v1/batch/stage/${record.id}`, { method: 'DELETE' })
.then(() => {
showNotification('Removed domain from current stage');
refreshView();
})
.catch((e) => {
console.error(e);
showNotification('Error: could not find domain');
});
}
render() {
return <FlatButton secondary label="Delete" icon={<DeleteIcon />}onClick={this.handleClick} />;
}
}
These bits are important as well:
RemoveButton.propTypes = {
record: PropTypes.object,
showNotification: PropTypes.func,
refreshView: PropTypes.func,
};
export default connect(null, {
showNotification: showNotificationAction,
refreshView: refreshViewAction,
})(RemoveButton);
So the way this works is it uses AOR's refreshViewAction as a prop function. This uses the underlying call to populate the data grid for me which is GET_LIST. This may not apply to your specific use case. Let me know if you have any questions.
Pim Schaaf's solution worked like a charm for me, Mine looks a bit different
yield put(push('/comments/-1')); // This refreshes the data
yield put(showNotification('')); // Hide error

How to dispatch actions from Child components three level down?

I am currently facing this issue designing a React application and I don't seem to be able to find an answer to it.
So my application has following heirarchy of Components in React Router
App
-> DynamicContainer
-> -> LoginComponent
Now, LoginComponents has form elements to take username and password.
I have userActionCreators where the login is handled, and it dispatches login successful when finished, but I don't seem to be able find the right way to connect my LoginComponent to dispatch actions or call actionCreators.
How do I do it? Any suggestion would be appreciated.
One option is to bind your single-purpose forms to their actions with connect. Since <LoginComponent /> is typically always doing the exact same thing, you can use it like this:
import React from 'react';
import * as userActionCreators from '../actions/users';
import { connect } from 'react-redux';
export class LoginComponent extends React.Component {
static propTypes = {
login: React.PropTypes.func.isRequired
}
render() {
const { login } = this.props;
const { username, password } = this.state;
return (
<form onSubmit={ () => login(username, password) }>
...
</form>
);
}
}
export default connect(null, userActionCreators)(LoginComponent);
connect automatically binds the action creator and separately provides dispatch to props, so if you want to be more explicit, the above example is the same as
import React from 'react';
import { login } from '../actions/users';
import { connect } from 'react-redux';
export class LoginComponent extends React.Component {
static propTypes = {
dispatch: React.PropTypes.func.isRequired
}
render() {
const { login, dispatch } = this.props;
const { username, password } = this.state;
return (
<form onSubmit={ () => dispatch(login(username, password)) }>
...
</form>
);
}
}
export default connect()(LoginComponent);
And for reference, userActionCreators:
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAILED = 'LOGIN_FAILED';
export function login(username, password) {
if (username === 'realUser' && password === 'secretPassword') {
return { type: LOGIN_SUCCESS, payload: { name: 'Joe', username: 'realUser' } };
} else {
return { type: LOGIN_FAILED, error: 'Invalid username or password };
}
}
if I understood you correctly, if you read Example: Todo List | Redux you'll find the example that you might be looking for.
There's the App component, connect()ed to Redux, and then there're the other components: AddTodo, TodoList, Todo and Footer.
App calls TodoList that calls Todo where user can click something. This click will surf back callback after callback, from Todo to TodoList to App as detailed below:
App calls TodoList with
<TodoList todos={visibleTodos} onTodoClick={ index => dispatch(completeTodo(index)) } />
TodoList calls Todo with
<Todo {...todo} key={index} onClick={ () => this.props.onTodoClick(index) } />
Todo component has a <li> with onClick={this.props.onClick} property.
So, backwards, when someones clicks inside the Todo compoment, that will call this.props.onClick which will call this.props.onTodoClick(index) from TodoList (notice the optional added parameter index), then, at last, this will invoke the function dispatch(completeTodo(index)) from App.
Two options:
Pass a bound actionCreator from your Container (which is connected to Redux) down to a child component (which is not connected to Redux) via the props object.
Consider adopting React Component Context in your project.

Resources