To give a bit of context, I am conducting a research focused on digital marketing and user experience. To enable the research, it is essential that I am able to get event logs from every component in an UI so I am, then, able to create datasets of usability pattern.
To do so in a web interface, e.g. using JavaScript, that's very simple.
window.addEventListener("someEvent", (e) => {//do something with the data});
The e element gives me everything I need, and If I want to listen to all window events, I can run a for loop through the window object events and add an event listener to each. My issue is with mobile development. For cross application reasons, am I am using React Native to try to create the same effect as window.addEventListener to mobile apps.
This is my first time using React Native. After a bit of searching, I am now aware that React Native does not have a window object (at least not as we understand window in JavaScript) and that the interface is translated to the platform native components, so document.getElementBy... would't work either.
What I though of was refs. I would only need to add a reference to the top component App. So what I have working so far:
export default function App() {
const viewRef = useRef();
useEffect(() => {
//I can use ref here to iterate through all events of View and
//bind event listeners to it
}, [viewRef]);
return (
<View
ref={viewRef}
style={styles.container}
onTouchEnd={(e) => {
console.log(e.target);
}}
>
<DummyComponent />
</View>
);
}
onTouchEnd event is bind to the top-layer component, so I can get everything that is a child of it. In that useEffect, I can do the same thing I would with JavaScript's window.
So I guess this is one way to do it. However, in my research I would like to enable any React Native app to begin logging events seamlessly. The state of the art would be creating a dependency that would being logging everything simply by installing it. That said, how can I iterate a React Native application to find Views and bind their events, without need to add ANYTHING to the actual component?
In JavaScript it would be something like:
document.getElementsByTagName("View").map((view) => {//bind view events});
So I'm not sure if this can help, but you can change the defaultProps of a component on start of your application.
So using your code as example you could do something like this:
const listener = e => {
console.log(e);
}
View.defaultProps = {
...View.defaultProps, // maintains original default props
onTouchEnd: listener
};
Basically this way you can have a global listener for each View component
Related
I'm curious, what's the best way to forward or dispatch events across multiple levels in component tree in Svelte JS?
Say I have App.Svelte, some intermediate number levels, each containing a child component, and Modal.Svelte. If I want to forward or dispatch an event from Modal to App, what's the right way to do this?
As I understand it, event forwarding in Svelte will traverse up the component tree and forward the event to the first parent that references the forwarded event. (Is this the correct interpretation?)
And using event dispatch approach, each nested component would need to 1/ import createEventDispatcher, 2/ create a dispatcher variable, 3/ define a function, which dispatches the event. Then parent's would need to import the function and reference it inside a tag, such as <p>. (Is this correct?)
If I'm correct on both of the above, I'm wondering if there isn't a more streamlined approach, eg connecting the event to stores, which would effectively flatten the component tree such that any component could receive the forwarded event. Though I imagine that this could induce some hard to debug behavior if multiple components reference the same forwarded event.
To forward a event from a child component or DOM element to the parent, you just have to define an on:event without handler. E.g.
<button on:click >
<Component on:open >
No need to use createEventDispatcher just for forwarding.
If you want to share events more widely, you can create an EventTarget and send events through that. Since subscriptions would not happen in the template via on:event, you have to make sure to remove listeners again. You can also use a context to just expose the object to a sub-tree of the component hierarchy.
Alternatively, events can be dispatched directly to DOM elements, such events will bubble up the entire element hierarchy (if enabled in the event options).
someElement.dispatchEvent(
new CustomEvent('my-event', { bubbles: true })
);
If you dispatch events to the window, directly or via bubbling, you can subscribe to those more conveniently using svelte:window:
<svelte:window on:my-event={() => ...} />
(These handlers are removed automatically when the component is destroyed.)
You might consider instead using accessor functions. In App.svelte, define a function that manipulates your top-level variables. Then place that function in an accessor object and pass that down as a prop to all your components that may need it.
<script>
[App.svelte]
let myVar, myObj, myWhatever
function updateMyVal(newValue) {
myVar = newValue
}
function mergeMyObject(mergeObj) {
myObj = {...myObj, ...mergeObj}
}
let accessorObject = {
updateMyVal: updateMyVal,
mergeMyObject: mergeMyObject
}
</script>
<ChildComponent {accessorObject} {myVar} {myObj} />
. . .
[ChildComponent.svelte]
<script>
export let accessorObject, myVar, myObj
accessorObject.updateMyVal(1234)
accessorObject.mergeMyObject({newProp: newVal})
</script>
And so forth... this has the advantage of pushing changes to application-wide variables from the top down, which I've found to work better for complex SPAs than a web of events, two-way-bindings or stores, at least in my limited experience.
I have a very large and complex React application. It is designed to behave like a desktop application. The interface is a document style interface with tabs, each tab can be one of many different type of editor component (there are currently 14 different editor screens). It is possible to have a very large number of tabs open at once (20-30 tabs). The application was originally written all with React class components, but with newer components (and where significant refactors have been required) I've moved to functional components using hooks. I prefer the concise syntax of functions and that seems to be the recommended direction to take in general, but I've encountered a pattern from the classes that I don't know how to replicate with functions.
Basically, each screen (tab) on the app is an editor of some sort (think Microsoft office, but where you can have a spreadsheet, text document, vector image, Visio diagram, etc all in tabs within the same application... Because each screen is so distinct they manage their own internal state. I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex. Each screen needs to be able to save it's current working document to the database, and typically provides a save option. Following standard object oriented design the 'save' function is implemented as a method on the top level component for each editor. However I need to perform a 'save-all' function where I iterate through all of the open tabs and call the save method (using a reference) on each of the tabs. Something like:
openTabs.forEach((tabRef) => tabRef.current.save());
So, If I make this a functional component then I have my save method as a function assigned to a constant inside the function:
const save = () => {...}
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level. Aside from the fact that would make it very difficult to find and maintain, it also would break my modular loading which only loads the component when needed as the save would have to be at a level above the code-splitting.
The only solution to this problem that I can think of is to have a save prop on the component and a useEffect() to call the save when that save prop is changed - then I'd just need to write a dummy value of anything to that save prop to trigger a save... This seems like a very counter-intuitive and overly complex way to do it.... Or do I simply continue to stick with classes for these components?
Thankyou,
Troy
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level.
You should ask yourself if the component should be smart vs dumb (https://www.digitalocean.com/community/tutorials/react-smart-dumb-components).
Consider the following:
const Page1 = ({ onSave }) => (...);
const Page2 = ({ onSave }) => (...);
const App = () => {
const handleSavePage1 = (...) => { ... };
const handleSavePage2 = (...) => { ... };
const handleSaveAll = (...) => {
handleSavePage1();
handleSavePage2();
};
return (
<Page1 onSave={handleSavePage1} />
<Page2 onSave={handleSavePage2} />
<Button onClick={handleSaveAll}>Save all</button>
);
};
You've then separated the layout from the functionality, and can compose the application as needed.
I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex.
I don't know if for some reason Redux is totally out of the picture or not, but I think it's one of the best options in a project like this.
Where you have a separated reducer for each module, managing the module's state, also each reducer having a "saveTabX" action, all of them available to be dispatched in the Root component.
I'm presently learning React-Testing-Library.
I'd like to test mouse interaction with an element. Presently it's a bit unclear to me the difference between userEvent.click(element) and fireEvent.click(element). Are both recommended for use, and in the example below are they being correctly implemented?
const mockFunction = jest.fn(() => console.info('button clicked'));
const { getByTestId } = render(<MyAwesomeButton onClick={mockFunction} />);
const myAwesomeButton = getByTestId('my-awesome-button');
// Solution A
fireEvent(myAwesomeButton)
expect(mockFunction.toHaveBeenCalledTimes(1);
// Solution B
userEvent.click(myAwesomeButton);
expect(mockFunction).toHaveBeenCalledTimes(1);
Thanks in advance for any clarity.
Behind the scenes, userEvent uses the fireEvent. You can consider fireEvent being the low-level api, while userEvent sets a flow of actions.
Here is the code for userEvent.click
You can see that depending of which element you are trying to click, userEvent will do a set of different actions (e.g. if it's a label or a checkbox).
According to Docs, you should use user-event to test interaction with your components.
fireEvent dispatches exactly the events you tell it to and just those - even if those exact events never had been dispatched in a real interaction in a browser.
User-event on the other hand dispatches the events like they would happen if a user interacted with the document. That might lead to the same events you previously dispatched per fireEvent directly, but it also might catch bugs that make it impossible for a user to trigger said events.
another good to mention difference between fireEvent and userEvent is that
by default fireEvent is wrapped inside act function and this is useful when the user does some action and this action will cause component updates and re-render. on the controversy, if we used userEvent probably will notice "not wrapped in act(...)" warning error that appears in the console.
act(() => {
userEvent.type(input, 'inputValue')
})
and here we don't need the act function, cause it's already wrapped over fireEvent
fireEvent.change(input, {target: {value: 'inputValue'}})
and this great article demonstrates this concept
Common mistakes with React Testing Library
I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:
Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.
This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.
So I guess the react way is to somehow represent this event with the app state?
I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):
export interface MessageQueue{
messages: number[],
push:(num: number)=>void,
pop:()=>number
}
const defaultMessageQueue{
messages:[],
push: (num:number) => {throw new Error("don't use default");},
pop: () => {throw new Error("don't use default");}
}
export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);
In the component I'm providing this with:
const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
messages: messages,
push:(num:number)=>{
setmessages([...messages, num]);
},
pop:()=>{
if(messages.length==0)return;
const message = messages[-1];
setmessages([...messages.slice(0, -1)]);
return message;
}
}}>
Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.
Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?
Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:
Define the route to have an optional path param. Something like content-page/:section?
In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.
const { section } = useParams();
useEffect(() => {
// the code to jump to the section
}, [section]);
I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener.
Also if it's a matter of scrolling you can scrollIntoView using refs
I have researched synthetic events in React and I understand that React pools events to improve performance. I am also aware that events in React are not DOM events. I have read several posts and threads about this topic but I cannot find any mention of calling preventDefault after calling event.persist.
Many sites have mentioned that if we want to capture the value of event.target, for example, that one option is simply to cache it for later use but this does not apply to my use case.
I want to throttle an event handler that is listening for onDragOver events. In order to do this in React, I have to pass the event through 3 functions, calling event.persist on the first one so that the last one can see it.
However, event.preventDefault has no effect when I do call it. It's almost as if once we call event.persist, that's it and there's no turning back.
Below is some of the code but you may find it more helpful to experiment with it on StackBlitz.
import React, { Component } from 'react';
import { throttle } from 'throttle-debounce';
import DropItem from './DropItem';
class DropZone extends Component {
constructor(props) {
super(props);
this.onDragOverThrottled = throttle(500, this.onDragOver);
this.onDragStart = this.onDragStart.bind(this);
this.handleDragOver = this.handleDragOver.bind(this);
}
onDragStart(e, id) {
console.log('dragstart: ', id);
e.dataTransfer.setData("id", id);
}
onDragOver(e) {
e.preventDefault(); // this does nothing if event.persist fires before it
console.log('dragover...');
}
handleDragOver(e) {
e.persist();
this.onDragOverThrottled(e);
}
render() {
const items = this.props.items.map((item, index) => {
return <DropItem item={item} key={index} onDragStart={this.onDragStart} />;
});
return (
<div
className={this.props.class}
//onDragOver={this.handleDragOver} // See note 1 below
onDragOver={this.onDragOver} // See note 2 below
onDrop={(e) => {this.props.onDrop(e, this.props.location)}}>
<span className="task-header">{this.props.title}</span>
{items}
</div>
);
}
}
export default DropZone;
/*
NOTE 1
Commenting in this line shows that throttling works but preventDefault does not and we cannot drag and drop any box to another location.
NOTE 2
This skips throttling altogether but preventDefault does work which allows the box to be moved to a different area. Because throttling is disabled here, onDragOver fires a lot and, at times, keeps the user from moving boxes around quickly.
*/
All of the sources I have consulted have effectively implemented either a debounce or throttle to capture a value and then do something with that value but none of them have tried calling preventDefault after persist as I am attempting to do. Some of these sources are the following:
Blog post on throttling and debouncing
Example of throttling an input by peterbe
A fiddle that does exactly what I am aiming for but it is not written in React
After further research and experimentation, I found out how to resolve this.
TL;DR
My theory that event.persist() somehow prevented event.preventDefault() from working as expected was incorrect.
THE REAL PROBLEM
The reason that my drag-and-drop app did not work with throttling was because event.persist() does not forward the event to another handler but simply makes it available for other handlers to access. This means that event.preventDefault() must be called on each handler that uses the event. This seems very obvious now that I say it but because I had to send the event through multiple handlers to implement throttling, I mistakenly thought I was passing the event from one to the other.
DOCUMENTATION
What I state above is my observation and does not come from React's official documentation. But the React docs do say this:
If you want to access the event properties in an asynchronous way, you
should call event.persist() on the event, which will remove the
synthetic event from the pool and allow references to the event to be
retained by user code.
Although I had read this before, I skipped over it because I did not consider what I was doing to be asynchronous. But the answer is still here– it allows references to the event to be retained by user code.
LEARN MORE
For those who want to dig into this deeper, be sure to look at the README in my StackBlitz where I provide further details.