How to call service class method from functional component in react - reactjs

I have an OIDC client service
export class AuthService {
public userManager: UserManager;
private user: any = null;
constructor() {
const settings = this.getClientSettings();
this.userManager = new UserManager(settings);
}
public isLoggedIn(): boolean {
return this.user != null && this.user.access_token && !this.user.expired;
}
loadUser() {
this.userManager.getUser().then((user) => this.user = user);
}
public getUser(): Promise<User | null> {
return this.userManager.getUser().then((user) => this.user = user);
}
public login(): Promise<void> {
return this.userManager.signinRedirect();
}
}
Functional component
export default function NavMenu() {
useSelector((state: ApplicationState) => state.oidcUser);
const dispatch = useDispatch();
const [state, setState] = useState({
menu: {
open: true,
coordinates: undefined
}
});
const onClose = () => {
setState({
...state, menu: {
open: false,
coordinates: undefined
}
})
}
authService: AuthService;
const login = () => {
authService.startAuthentication(window.location.pathname);
};
const menuOptions = [
'Save',
'Edit',
'Cut',
'Copy',
'Paste',
];
return (<div>
<TopAppBar>
<TopAppBarRow>
<TopAppBarSection align='start'>
<TopAppBarTitle>Falcon</TopAppBarTitle>
</TopAppBarSection>
<TopAppBarSection align='end' role='toolbar'>
<div>
{(() => {
if (true) {
return (
<Button raised type="button" onClick={() => { login }}>Portal</Button>
)
} else {
return (
<Menu
open={state.menu.open}
onClose={onClose}
coordinates={state.menu.coordinates}
onSelected={(index, item) => console.log(index, item)}>
<MenuList>
{menuOptions.map((option : any, index : any) => (
<MenuListItem key={index}>
<MenuListItemText primaryText={option} />
{/* You can also use other components from list, which are documented below */}
</MenuListItem>
))}
</MenuList>
</Menu>
)
}
})()}
</div>
</TopAppBarSection>
</TopAppBarRow>
</TopAppBar>
</div>);
}
Trying to call
authService: AuthService;
const login = () => {
authService.startAuthentication(window.location.pathname);
};
Getting an error Cann't find the name authService
How to call service class method from react functional component.

Here, you could do something like this in your functional component:
import * as React from "react";
import { AuthService } from "./AuthService";
export default function NavMenu() {
const authService = new AuthService();
const login = () => {
authService.startAuthentication(window.location.pathname);
}
return (
<div className="App">
<h1>Hello NavMenu</h1>
</div>
);
}
If you have any more questions, would be happy to help!

Related

Mobx observer won`t update component

So I have TodoList component which is updated just fine when I add new Item. Inside I have TodoItem width delete and togglecomplete functions and they do invoke methodsof my todoList object, I see in console that object is changing, yet List component won't get rerendered. Any idia what can be done.
package json: "mobx": "^6.7.0", "mobx-react-lite": "^3.4.0",
import { ITodo } from '../type/ITodo';
import TodoItem from '../TodoItem/TodoItem.component'
import './TodoList.styles.css';
import { observer } from 'mobx-react-lite';
type Props = {
todoList: ITodo[];
}
const TodoList: React.FC<Props> = ({ todoList }) => {
return (
<div className="TodoList">
{
todoList && todoList.length > 0 && (
todoList.map(el => (
<TodoItem content={el} key={el.id} />
))
)
}
</div>
)
}
export default observer(TodoList);
here are delete and toggle complete functions
import { useStores } from '../Store/StoreContext';
import { observer } from 'mobx-react-lite';
type Props = {
content: ITodo;
}
const TodoItem: React.FC<Props> = ({ content }) => {
const { todoList } = useStores();
const { deadline, title , description, completed, id } = content
const handleChange = () => {
todoList.toggleComplete(id)
}
const deleteHandler = () => {
todoList.deleteTodo(id);
}
return (
<div className="TodoItem">
<div className="actions">
<FormControlLabel
onChange={handleChange}
/>
<Button
variant="outlined"
onClick={deleteHandler}
>
Delete
</Button>
</div>
</div>
)
}
export default observer(TodoItem);
and here is my store just in case, I keep my store in Context.
import { action, makeObservable, observable } from "mobx";
import { ITodo } from "../type/ITodo";
export class Todo {
todos: ITodo[] = [];
constructor() {
makeObservable(this, {
todos: observable,
getTodos: action,
addTodo: action,
deleteTodo: action,
toggleComplete: action,
});
}
getTodos() {
return this.todos;
}
addTodo(todo: ITodo) {
this.todos.push(todo);
}
deleteTodo(id: string) {
console.log(id);
this.todos = this.todos.filter((el) => el.id !== id);
}
toggleComplete(id: string) {
this.todos = this.todos.map((el) => {
if (el.id !== id) {
return el;
}
return { ...el, completed: !el.completed };
});
}
}
Here is repository on github: https://github.com/pavel-gutsal/mobx-todo-list
node version - 16.xx

StencilJS emit event to React

I have a nested set of StencilJS components. I would like to attach a function to my nested component so that my React app, which hosts the parent component, can read.
Example
<pw-actionbar
actions={getActions}
/>
In this actionbar component, I have another nested button component. It looks like this
return (
<Host>
<div class="container">
{
// iterate through array
this.actions.map((action) => {
// take object.icon and make an icon
const XmlIcon = `${action.icon}`;
==> I WANT A FUNCTION ON PW-BUTTON THAT PASSES 'action' which my react app reads
return <pw-button-side-menu
// shade the selected pages button
isselected={action.onpage ? 'selected' : 'notselected'}
class="displace"
>
<span slot="label">{action.name}</span>
<i slot="icon">
<XmlIcon
class="icon-position"
fillcolor={this.iconfillcolor}
strokecolor={this.iconstrokecolor}/>
</i>
</pw-button-side-menu>
})
}
</div>
</Host>
);
}
My react app has some component
functionEmittedFromPwButton(action) {
console.log(action) <=== I WANT THIS TO WORK IN MY REACT APP WHICH IS EMITTED FROM THE PW-BUTTON COMPONENT NESTED IN THE PW-ACTIONBAR COMPONENT
}
return (
<MyComponent>
<pw-actionbar actions={getActions}/> <=== that takes an array of objects. I want to capture the 'action' object emitted by the pw-button nested in this component in my react app
</MyComponent>
)
I have tried all sorts of different methods like this one to try to emit the object from stencil to react
On the stenciljs side
import { Component, h, Host, Prop, Event, EventEmitter } from "#stencil/core";
#Component({
tag: "pw-actionbar",
styleUrl: "pw-actionbar.scss",
shadow: true,
})
export class PwActionbar {
#Prop() actions: any = [];
#Prop() iconfillcolor: "white" | "black" = "white";
#Prop() iconstrokecolor: "white" | "black" = "white";
#Event() emitAction: EventEmitter;
render() {
const handleClick = (action) => {
this.emitAction.emit(action);
};
return (
<Host>
<div class="container">
{
// iterate through array
this.actions.map((action) => {
// take object.icon and make an icon
const XmlIcon = `${action.icon}`;
// cast the button
return (
<pw-button-side-menu
// shade the selected pages button
isselected={action.onpage ? "selected" : "notselected"}
class="displace button-lines"
onClick={() => handleClick(action)}
>
<span slot="label">{action.name}</span>
<i slot="icon">
<XmlIcon
class="icon-position"
fillcolor={this.iconfillcolor}
strokecolor={this.iconstrokecolor}
/>
</i>
</pw-button-side-menu>
);
})
}
</div>
</Host>
);
}
}
On the react side
const handleAction = async (action, history, i18n) => {
Metrics.track("Changed Page", { action });
if ("sign-out" === action) {
await authActions.logout();
history.push(`/${i18n.locale}`);
} else if ("help-desk" === action) {
history.push(`/${i18n.locale}/zendesk`);
} else if ("advisors" === action) {
pageActionsObjAdmin[0].onpage = true;
history.push(`/${i18n.locale}/admin/advisors`);
} else if ("users" === action) {
pageActionsObjAdmin[1].onpage = true;
history.push(`/${i18n.locale}/admin/users`);
} else if ("forecast" === action) {
pageActionsObjAdmin[3].onpage = true;
history.push(`/${i18n.locale}/admin/forecast`);
} else if ("stats" === action) {
pageActionsObjAdmin[4].onpage = true;
history.push(`/${i18n.locale}/admin/stats`);
}
};
const Layout = ({ children }) => {
const { i18n } = useLingui();
const [, setContext] = useContext(StripeErrorContext);
const history = useHistory();
useEffect(() => {
const listener = (e) => {
// set page button to be "Active"
pageActionsObjAdmin.forEach((element) => {
element.onpage = false;
});
handleAction(e.detail.page, history, i18n, setContext);
};
// listen for events emitted form the action bar
document.body.addEventListener("emitAction", listener);
return () => {
document.body.removeEventListener("emitAction", listener);
};
}, []); // eslint-disable-line
// refs for the actionbar
const elementRef = useRef(null);
useEffect(() => {
if (elementRef.current !== null) {
elementRef.current.actions = pageActionsObjAdmin;
}
}, [elementRef]);
return (
<Wrapper>
<Header />
<BodyLayout>
<pw-actionbar
ref={(el) => (elementRef.current = el)}
style={{ paddingTop: "56px", zIndex: "99" }}
class="action-bar"
/>
<div className="main-layout" style={{ width: "100%" }}>
{children}
</div>
</BodyLayout>
</Wrapper>
);
};
export default Layout;

React - Render Key Press Event

I cannot seem to find a fitting example anywhere online. I have little experience with javaScript and React, and my issue might be trivial. The keypress event function works fine if run it by itself. However, if I try to implement it into the class app, and call the function from the render section I get this error: Error message. Any ideas? Thanks in advance. I have added the code.
import React, { Component, useEffect, useState } from 'react';
import './App.css';
import Spotify from 'spotify-web-api-js';
const spotifyWebApi = new Spotify();
class App extends Component {
constructor(){
super();
const params = this.getHashParams();
this.state = {
loggedIn: params.access_token ? true : false,
nowPlaying: {
name: 'Not Checked',
image: '',
device: '',
user_id: '',
playlists: []
}
}
if (params.access_token){
spotifyWebApi.setAccessToken(params.access_token)
}
};
useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState();
// Når du trykker på knappen - sætter vi keyPressed til true for at vise resultatet.
function downHandler({ key }) {
if (key === targetKey) {
this.setKeyPressed(true);
}
}
// Når du releaser knappen - sætter vi keyPressed til false for at fjerne resultatet igen.
const upHandler = ({ key }) => {
if (key === targetKey) {
this.setKeyPressed(false);
}
};
useEffect(() => {
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
// Det er altid "pænt" at ryde op efter sig selv, så vi fjerner eventListeners i return metoden
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
}, []);
return this.keyPressed;
}
Fapp() {
const aPressed = this.useKeyPress('a');
const sPressed = this.useKeyPress('s');
const dPressed = this.useKeyPress('d');
const fPressed = this.useKeyPress('f');
return (
<div>
{ aPressed ? 'a' : 'not a'}
</div>
);
}
getHashParams() {
var hashParams = {};
var e, r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ( e = r.exec(q)) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
getNowPlaying(){
spotifyWebApi.getMyCurrentPlaybackState()
.then((response) => {
this.setState({
nowPlaying: {
name: response.item.name,
image: response.item.album.images[0].url
}
}
)
})
}
handleKeyDown(event) {
if(event.keyCode === 13) {
console.log('Enter key pressed')
}
}
render() {
return (
<div className="App">
<a href='http://localhost:8888'>
<button>Login with Spotify</button>
</a>
<div> Now Playing: { this.state.nowPlaying.name } </div>
<div> user: { this.state.nowPlaying.user_id } </div>
<div>
<img src={ this.state.nowPlaying.image } style={{width: 100}}/>
</div>
<button onClick={() => this.getNowPlaying()}>
Check Now Playing
</button>
</div>
);
};
}
export default App;

value must be a mock or spy function when using jest.fn

Getting this error
Matcher error: received value must be a mock or spy function
Received has type: object
Received has value: {}
However, i think i shouldn't be getting this error because im using jest.fn. So im mocking the function.
describe('Should simulate button click', ()=> {
it('should simulate button click', () => {
// add the name of the prop, which in this case ites called onItemAdded prop,
// then use jest.fn()
const wrapper = shallow(<TodoAddItem onItemAdded={() => jest.fn()}/>)
// console.log('props',wrapper.find('button').props());
wrapper.find('button').simulate('click');
expect(wrapper).toHaveBeenCalled(); // error happens when this executes
})
})
todo-add-item.js
import React, { Component } from 'react';
import './todo-add-item.css';
export default class TodoAddItem extends Component {
render() {
return (
<div className="todo-add-item">
<button
className="test-button btn btn-outline-secondary float-left"
onClick={() => this.props.onItemAdded('Hello world')}>
Add Item
</button>
</div>
);
}
}
app.js (using the component in this file)
import React, { Component } from 'react';
import AppHeader from '../app-header';
import SearchPanel from '../search-panel';
import TodoList from '../todo-list';
import ItemStatusFilter from '../item-status-filter';
import TodoAddItem from '../todo-add-item';
import './app.css';
export default class App extends Component {
constructor() {
super();
this.createTodoItem = (label) => {
return {
label,
important: false,
done: false,
id: this.maxId++
}
};
this.maxId = 100;
this.state = {
todoData: [
this.createTodoItem('Drink Coffee'),
this.createTodoItem('Make Awesome App'),
this.createTodoItem('Have a lunch')
]
};
this.deleteItem = (id) => {
this.setState(({ todoData }) => {
const idx = todoData.findIndex((el) => el.id === id);
const newArray = [
...todoData.slice(0, idx),
...todoData.slice(idx + 1)
];
return {
todoData: newArray
};
});
};
this.addItem = (text) => {
const newItem = this.createTodoItem(text);
this.setState(({ todoData }) => {
const newArray = [
...todoData,
newItem
];
return {
todoData: newArray
};
});
};
this.onToggleImportant = (id) => {
console.log('toggle important', id);
};
this.onToggleDone = (id) => {
console.log('toggle done', id);
};
};
render() {
return (
<div className="todo-app">
<AppHeader toDo={ 1 } done={ 3 } />
<div className="top-panel d-flex">
<SearchPanel />
<ItemStatusFilter />
</div>
<TodoList
todos={ this.state.todoData }
onDeleted={ this.deleteItem }
onToggleImportant={ this.onToggleImportant }
onToggleDone={ this.onToggleDone } />
<TodoAddItem onItemAdded={ this.addItem } />
</div>
);
};
};
I'm not 100% sure, but I believe you should do something like this:
describe('should simulate button click', () => {
it('should simulate button click', () => {
const mockedFunction = jest.fn();
const wrapper = shallow(<TodoAddItem onItemAdded={ mockedFunction } />);
wrapper.find('button').simulate('click');
expect(mockedFunction).toHaveBeenCalled();
});
});
You are testing if the onItemAdded function gets called when you click the <TodoAddItem /> component. So you have to mock it first using jest.fn and then check if the mocked function got called after you simulated the click.
For me works replacing the next one:
const setCategories = () => jest.fn();
With this one:
const setCategories = jest.fn();
I suppose that you should to set just jest.fn or jest.fn() in your code.

Firebase/React/Redux Component has weird updating behavior, state should be ok

I am having a chat web app which is connected to firebase.
When I refresh the page the lastMessage is loaded (as the gif shows), however, for some reason, if the component is otherwise mounted the lastMessage sometimes flickers and disappears afterwards like it is overridden. When I hover over it, and hence update the component, the lastMessage is there.
This is a weird behavior and I spent now days trying different things.
I would be very grateful if someone could take a look as I am really stuck here.
The db setup is that on firestore the chat collection has a sub-collection messages.
App.js
// render property doesn't re-mount the MainContainer on navigation
const MainRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<MainContainer>
<Component {...props} />
</MainContainer>
)}
/>
);
render() {
return (
...
<MainRoute
path="/chats/one_to_one"
exact
component={OneToOneChatContainer}
/>
// on refresh the firebase user info is retrieved again
class MainContainer extends Component {
componentDidMount() {
const { user, getUserInfo, firebaseAuthRefresh } = this.props;
const { isAuthenticated } = user;
if (isAuthenticated) {
getUserInfo(user.id);
firebaseAuthRefresh();
} else {
history.push("/sign_in");
}
}
render() {
return (
<div>
<Navigation {...this.props} />
<Main {...this.props} />
</div>
);
}
}
Action
// if I set a timeout around fetchResidentsForChat this delay will make the lastMessage appear...so I must have screwed up the state / updating somewhere.
const firebaseAuthRefresh = () => dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user) {
localStorage.setItem("firebaseUid", user.uid);
dispatch(setFirebaseAuthUser({uid: user.uid, email: user.email}))
dispatch(fetchAllFirebaseData(user.projectId));
}
});
};
export const fetchAllFirebaseData = projectId => dispatch => {
const userId = localStorage.getItem("firebaseId");
if (userId) {
dispatch(fetchOneToOneChat(userId));
}
if (projectId) {
// setTimeout(() => {
dispatch(fetchResidentsForChat(projectId));
// }, 100);
...
export const fetchOneToOneChat = userId => dispatch => {
dispatch(requestOneToOneChat());
database
.collection("chat")
.where("userId", "==", userId)
.orderBy("updated_at", "desc")
.onSnapshot(querySnapshot => {
let oneToOne = [];
querySnapshot.forEach(doc => {
let messages = [];
doc.ref
.collection("messages")
.orderBy("created_at")
.onSnapshot(snapshot => {
snapshot.forEach(message => {
messages.push({ id: message.id, ...message.data() });
});
});
oneToOne.push(Object.assign({}, doc.data(), { messages: messages }));
});
dispatch(fetchOneToOneSuccess(oneToOne));
});
};
Reducer
const initialState = {
residents: [],
oneToOne: []
};
function firebaseChat(state = initialState, action) {
switch (action.type) {
case FETCH_RESIDENT_SUCCESS:
return {
...state,
residents: action.payload,
isLoading: false
};
case FETCH_ONE_TO_ONE_CHAT_SUCCESS:
return {
...state,
oneToOne: action.payload,
isLoading: false
};
...
Main.js
// ...
render() {
return (...
<div>{React.cloneElement(children, this.props)}</div>
)
}
OneToOne Chat Container
// without firebaseAuthRefresh I don't get any chat displayed. Actually I thought having it inside MainContainer would be sufficient and subscribe here only to the chat data with fetchOneToOneChat.
// Maybe someone has a better idea or point me in another direction.
class OneToOneChatContainer extends Component {
componentDidMount() {
const { firebaseAuthRefresh, firebaseData, fetchOneToOneChat } = this.props;
const { user } = firebaseData;
firebaseAuthRefresh();
fetchOneToOneChat(user.id || localStorage.getItem("firebaseId"));
}
render() {
return (
<OneToOneChat {...this.props} />
);
}
}
export default class OneToOneChat extends Component {
render() {
<MessageNavigation
firebaseChat={firebaseChat}
firebaseData={firebaseData}
residents={firebaseChat.residents}
onClick={this.selectUser}
selectedUserId={selectedUser && selectedUser.residentId}
/>
}
}
export default class MessageNavigation extends Component {
render() {
const {
onClick,
selectedUserId,
firebaseChat,
firebaseData
} = this.props;
<RenderResidentsChatNavigation
searchChat={this.searchChat}
residents={residents}
onClick={onClick}
firebaseData={firebaseData}
firebaseChat={firebaseChat}
selectedUserId={selectedUserId}
/>
}
}
const RenderResidentsChatNavigation = ({
residents,
searchChat,
selectedUserId,
onClick,
firebaseData,
firebaseChat
}) => (
<div>
{firebaseChat.oneToOne.map(chat => {
const user = residents.find(
resident => chat.residentId === resident.residentId
);
const selected = selectedUserId == chat.residentId;
if (!!user) {
return (
<MessageNavigationItem
id={chat.residentId}
key={chat.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
{residents.map(user => {
const selected = selectedUserId == user.residentId;
const chat = firebaseChat.oneToOne.find(
chat => chat.residentId === user.residentId
);
if (_isEmpty(chat)) {
return (
<MessageNavigationItem
id={user.residentId}
key={user.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
</div>
}
}
And lastly the item where the lastMessage is actually displayed
export default class MessageNavigationItem extends Component {
render() {
const { hovered } = this.state;
const { user, selected, chat, isGroupChat, group, id } = this.props;
const { messages } = chat;
const item = isGroupChat ? group : user;
const lastMessage = _last(messages);
return (
<div>
{`${user.firstName} (${user.unit})`}
{lastMessage && lastMessage.content}
</div>
)
}
In the end it was an async setup issue.
In the action 'messages' are a sub-collection of the collection 'chats'.
To retrieve them it is an async operation.
When I returned a Promise for the messages of each chat and awaited for it before I run the success dispatch function, the messages are shown as expected.

Resources