How to track app custom events in entire react app - reactjs

I want to add functionality that will collect custom events in redux in entire react app.
What I want to achieve is to place all event functions it in one place and only use this functions in components in my app when I want to trigger some event.
interface IEventsLoggerContext {
[key: string]: (val?: any) => void
}
export const EventsLoggerContext = createContext<IEventsLoggerContext>({})
class EventsLogger extends Component<{}, any> {
constructor (props: Readonly<{}>) {
super(props)
}
// event methods like below
pageLoaded = () => {
// here will be saving the event to redux store
console.log('page loaded')
}
targetClick = (e: SyntheticEvent) => {
// here will be saving the event to redux store
console.log('target click', e.target)
}
// ...
render () {
return (
<EventsLoggerContext.Provider
value={{
pageLoaded: this.pageLoaded,
targetClick: this.targetClick
}}
>
{this.props.children}
</EventsLoggerContext.Provider>
)
}
}
export default EventsLogger
I want to make all event log actions available in app so I wrapped all into my event provider:
<EventsLogger>
...
</EventsLogger>
And using in component like this:
const MainApp: React.FC = () => {
const { pageLoaded } = useContext(EventsLoggerContext)
useEffect(() => {
pageLoaded()
}, [pageLoaded])
return (
<div>Main App page</div>
)
}
Is this correct way to do this or is there maybe better approach to get functionality like this?

Using React Context is a clever way to solve this although it will require more code when adding more events compared to simply using the browser native window.dispatchEvent() function.
// SomeComponent.tsx
export const SomeComponent : FC = props => {
useEffect(() => {
const pageLoadEvent = new CustomEvent(
"pageLoaded",
{
detail : {
at: Date.now()
}
}
);
window.dispatchEvent(pageLoadEvent);
}, []):
// ...
}
// SomeOtherComponent.tsx
export const SomeOtherComponent : FC = props => {
useEffect(() => {
window.addEventListener("pageLoaded", onPageLoaded);
return () => window.removeEventListener("pageLoaded", onPageLoaded);
}, []);
function onPageLoaded(e: CustomEvent) {
// Do whatever you want here :)
}
// ...
}

Related

How to call a component class function in my App.tsx?

I am new in React Native and i'm trying to develop a mobile app with Expo.
I am trying to call a function of a component class in my App.tsx. I don't want that function is static because i need to access to my variable of my state which is in my constructor of my class.
App.tsx
const App = () => {
const [variable, setVariable] = useState('');
useEffect(() => {
//doing some stuff
}, [])
Class1.method(variable);
[...]
}
Class1.tsx
class Class1 extends Component<any, any> {
constructor(props: any){
super(props);
this.state = {
company_name: [],
}
}
method(param: any) {
Object.values(param).map(function(d: any, idx){
this.state.company_name = [...this.state.company_name, d];
});
}
[...]
So the thing is that i am having an array in my App.tsx and i want to pass it to my Class1.
Is that possible to do in that way or am i missing something ?
Thanks in advance
Put your array in props
const App = () => {
const [names, setNames] = useState([]);
const addCompanyName = (name) => {
setNames([...names, name]);
}
const addRandomCompany = () => {
addCompanyName(Math.random().toString());
}
return <>
<Button title='random name' onPress={addRandomCompany}/>
<Child companyNames={names}/>
</>
}
const Child = ({ companyNames }) => {
return <>
{companyNames.map((name) => <Text>{name}</Text>)}
</>
}
You should export your Class1 component by adding export default Class1; at the bottom of your Class1.tsx, after class declaration.
Then you will be able to import the component and use it in the App.tsx file.
Read this React doc on Code splitting to learn more.

Restart react timer from parent component

I have a small issue here:
I have two components, a parent, and a timer, which is one of the children of parent
the parent passes down to the child a delay and a callback. The timer will execute the callback every delay milliseconds.
this is the code for the timer:
interface TimerProps {
delayInMilliseconds: number;
callback: Function;
}
const Timer = (props: TimerProps) => {
const { delayInMilliseconds, callback } = props;
const [ timerId, setTimerId ] = React.useState(0);
React.useEffect(() => {
createTimer();
}, []);
const createTimer = () => {
setTimerId(setInterval(callback, delayInMilliseconds))
};
const stopTimer = () => {
clearInterval(timerId);
};
const restartTimer = () => {
stopTimer();
createTimer();
};
return <button onClick={restartTimer}>stop timer</button>;
};
So far, so good. The timer does what id needs to do, and the restartTimer function works.
What I'm trying do do now is to tie the restartTimer function to a button that is present on the parent component.
I've tried to use React.forwardRef with React.useImperativeHandle, but it's not entirely clear to me the mechanism behind it, and so far I haven't had any luck
Could anyone help me understand how to "expose" the child's restartTimer function to the parent element?
I ended up just making the functional component a class component, and using useRef to access it from the parent element
child:
interface TimerProps {
delayInMilliseconds: number;
callback: Function;
}
interface TimerState {
timerId: number;
}
class Timer extends React.Component<TimerProps, TimerState> {
constructor(props: TimerProps) {
super(props);
this.state = {
timerId: 0,
};
}
componentDidMount() {
this.createTimer();
}
createTimer = () => {
this.setState({
...this.state,
timerId: setInterval(this.props.callback, this.props.delayInMilliseconds),
});
};
stopTimer = () => {
clearInterval(this.state.timerId);
};
restartTimer = () => {
this.stopTimer();
this.createTimer();
};
render() {
return null;
}
}
export default Timer;
parent:
const handleRefreshButtonClick = () => {
if (timerRef) {
timerRef?.current?.restartTimer();
}
}
...
const timerRef = React.useRef();
return(
<Timer
ref={timerRef}
delayInMilliseconds={refreshDelay //5000}
callback={doOtherStuff}/>
)
...
<Button onClick={handleRefreshButtonClick} />
create the state and function in your parent component and pass that to your child component i.e timer component and handle it from your parent component.

How can I convert a class with a constructor to a functional component using React Hooks?

How can I convert the below class with a constructor to a functional component using React Hooks?
class App extends React.Component {
constructor(props) {
super(props);
this.toggleVisibility = this.toggleVisibility.bind(this);
this.handleOnBlur = this.handleOnBlur.bind(this);
}
I saw somewhere online that you can do something like this for the constructor:
const useConstructor(callBack = () => {}) {
const [hasBeenCalled, setHasBeenCalled] = useState(false);
if (hasBeenCalled) return;
callBack();
setHasBeenCalled(true);
}
And then change the class to a function and use it like this:
const App = () => {
useConstructor(() => {});
But I'm not sure what to do with toggleVisibility and handleOnBlur
You no need to use a constructor inside the functional components ( unless some difficult specific issue ). You can do it like simple arrow functions inside functional component like that:
const App = props => {
const toggleVisibility = toggleProps => {
console.log('toggleProps should be true', toggleProps);
};
const handleOnBlur = () => {};
return (
<>
<button onClick={handleOnBlur} />
<button
onClick={() => {
toggleVisibility(true);
}}
/>
</>
);
};
No need for binding and useConstructor, the following should be equivalent:
const App = (props) => {
let toggleVisibility;
let handleOnBlur;
// ... rest of your component logic
}

component not re rendering when call action in mobx

I'm using mobx v6.
HomePage calls roomStore.fetchRooms when scrolls down to bottom, yes I use IntersectionObserver and lodash/throttle function for implement infinite scroll.
I checked roomStore.fetchRooms been called when loadMore function called, and roomStore.homeRoomList been updated.
All functions change states in Mobx stores are decorated with #action.
I wonder why my HomePage component is not re-rendered.
//RoomStore
export default class RoomStore extends BasicStore {
#observable homeRoomList: GetRoomsPayload["rooms"] | null;
constructor({root, state}: { root: RootStore, state: RoomStore}){
super({root, state});
makeObservable(this);
this.homeRoomList = state?.homeRoomList ?? null;
}
async fetchRooms(category?: string, page:number = 0){
const [error,response] = await this.api.GET<GetRoomsPayload>(`/room/${category}?page=${page}`);
if(error){
throw Error(error.error)
}
if(response && response.success){
const { data } = response
this.feedFetchHomeRooms(data.rooms);
return response.data;
}
return Promise.resolve();
}
#action.bound
feedFetchHomeRooms(rooms: GetRoomsPayload["rooms"]){
if(rooms){
if( this.homeRoomList) {
this.homeRoomList = [...this.homeRoomList, ...rooms];
}
else {
this.homeRoomList = rooms;
}
}
}
}
// HomePage Component
const HomePage: FC & HomePageInitStoreOnServer = ({}) => {
const { pathname } = useLocation();
const homeRef = useRef<HTMLUListElement>(null);
const infiniteScrollTargetRef = useRef<HTMLDivElement>(null);
const { roomStore } = useMobxStores();
const handleLoadMore = () => {
throttleFetch();
}
const throttleFetch = useCallback(throttle(() => {
roomStore.fetchRooms()
},500),[]);
useInfiniteScroll({
target: infiniteScrollTargetRef,
cb: handleLoadMore,
});
useEffect(() => {
if(!roomStore.homeRoomList){
roomStore.fetchRooms()
}
},[]);
return (
<section >
<RoomContainer ref={homeRef}>
{roomStore.homeRoomList?.map((room: any) => {
return (
<Card
room={room}
key={room.id}
/>
);
})}
</RoomContainer>
<InfiniteScroll targetRef={infiniteScrollTargetRef}/>
</section>
);
};
export default observer(HomePage);
The component (HomePage) that renders observable data needs to be wrapped into the observer.
import { observer } from 'mobx-react-lite'
const HomePage = observer(() => {
// your code of component
})
you can find more details in official docs here

React testing component simulate click does not recognise function

I have this connected component that i am trying to test, i import two actions that dispatch a call to the store. The actual button click i am trying to test it should toggle between css classes.
When i simulate the click in my test it i get a error that one of my actions triggered by the click event is not a function.
TypeError: setLikedProducts is not a function
13 |
14 | const handleLike = () => {
> 15 | return like ? (setLike(false), removeLikedProduct(product)) : (setLike(true), setLikedProducts(product));
| ^
16 | }
17 |
18 | return (
My Component:
export function LikeProduct (props) {
const [like, setLike] = useState(false);
const { product, setLikedProducts, removeLikedProduct } = props;
const handleLike = () => {
return like ? (setLike(false), removeLikedProduct(product)) : (setLike(true), setLikedProducts(product));
}
return (
<div className="LikeProduct">
<Button
className={like ? "LikeProduct__like" : "LikeProduct__button"}
variant="link"
onClick={handleLike}>
<FaRegThumbsUp />
</Button>
</div>
);
}
const mapDispatchToProps = () => {
return {
setLikedProducts,
removeLikedProduct
}
}
export default connect(null, mapDispatchToProps())(LikeProduct);
my Test:
const props = {
info: {
product: "",
setLikedProducts: jest.fn(),
removeLikedProduct: jest.fn()
}
}
describe('Does LikeProduct Component Render', () => {
let wrapper = shallow(<LikeProduct {...props}/>);
it('LikeProduct render its css class', () => {
expect(wrapper.find('.LikeProduct').length).toBe(1);
});
it('Trigger the button on LikeProduct', () => {
console.log(wrapper.debug())
wrapper.find('Button').simulate('click');
});
Not sure why i am getting this error
your props are incorrectly defined, given your props contract
it should be
const props = {
product: "",
setLikedProducts: jest.fn(),
removeLikedProduct: jest.fn()
}
By the way, just in case you don't know, you can use useDispatch hook from react-redux in order to access dispatch function, instead of using connect

Resources