ReactJS events - this.props.onSubmit(this.state....) - reactjs

The following code snippet is from a simple Todo list which stores information on a local EOS blockchain and has a frontend web interface built on ReactJS. The question is related to ReactJS, specifically the event handler code snippet
saveTodo(e) {
e.preventDefault();
this.props.onSubmit(this.state.description)
this.setState({ description: "" })
}
The full program can be found here...https://github.com/eosasia/eos-todo/blob/master/frontend/src/index.jsx
In the body of the event handler saveTodo(e), there is a line this.props.onSubmit(this.state.description). I would like to know what exactly is going on here?
I am new to ReactJS, and it looks to that the above line of code is somehow setting a property (props) by calling a built-in function onSubmit() with an argument retrieved from the state object. Is this correct? I don’t see how onSubmit() was assigned to props anywhere in this code, but somehow we are able to use it like so: this.props.onSubmit(this.state.description) …. What’s going on here?
Thank you kindly.
P.S. pardon the terminology. I'm not sure if "event handler" or "event listener" is the right word.

The TodoForm component takes a property "onSubmit".
That line is simply calling this property (that was passed to it by its parent) and passing in the description ( taken from the state of TodoForm ).
For example:
<TodoForm onSubmit={(description) => alert(description)} />
Read more about props in react here.

Related

How to test react components props (expect component to be called with)

I need to test that a react component is called with opened={true} prop after an button click is fired. I am using testing-library ( testing-library/react + testing-library/jest-dom).
I mocked the Component using something like
import Component from "./path-to-file/component-name"
...
jest.mock("./path-to-file/component-name", () => {
return jest.fn().mockImplementation(() => {
return null
})
})
I first tried with:
expect(Component).toBeCalledWith(expect.objectContaining({"opened": true}))
expect(Component).toHaveBeenCalledWith(expect.objectContaining({"opened": true}))
expect(Component).toHaveBeenLastCalledWith(expect.objectContaining({"opened": true}))
but I got Error: expect(jest.fn()).toBeCalledWith(...expected).
Same went for expect.objectContaining({"opened": expect.anything()})
And even for expect(Component).toBeCalledWith(expect.anything())
And the difference is empty array:
I also tried with expect(ChartMenu.mock).toBeCalledWith(expect.anything()). I got a different error but still not working (this time the error was Error: expect(received).toBeCalledWith(...expected) + Matcher error: received value must be a mock or spy function)
Thank you in advice!
EDIT: here is a simplified version of the component I want to test:
const Component = () => {
const [chartMenuOpened, setChartMenuOpened] = useState(false)
return (
<Flex>
<EllipseIcon onClick={() => setChartMenuOpened(true)}>
+
</EllipseIcon>
<ChartMenu
opened={chartMenuOpened}
close={() => setChartMenuOpened(false)}
/>
</Flex>
)
}
Basically I want to make sure that when the + icon is clicked the menu will be opened (or called with open value). The issue is that I cannot render ChartMenu because it needs multiple props and redux state.
I was able in the end to mock useState in order to check that the setState was properly called from the icon component (in order to make sure there won't be future changes on the component that will break this using this post).
But I would still really appreciate an answer to the question: if there is any way to create a spy or something similar on a react component and check the props it was called with? Mostly because this was a rather simple example and I only have one state. But this might not always be the case. Or any good idea on how to properly test this kind if interaction would be really appeciated.
I think you are on the right track to test if the component has been called with that prop, it's probably the syntax issue in your code
I learn this trick from colleague and you can try to see if this helps fix your issue.
expect(Component).toHaveBeenCalledWith(
expect.objectContaining({
opened: true,
}),
expect.anything()
);
While the question on how to is answered, I did some extra research on this and it seems like in React components there is a second parameter refOrContext (so basically most of the time it's an empty object, but it can also be a ref or a context)
Despite pointing out the reason for the behavior, I also wanted to highlight that it is safer to use expect.anything() as the second argument (rather than just {} which would work only in most of the cases ):
More information about React second argument here

RxJS and repeated events

I am new to RxJs in general but am investigating a bug in some React code in which, upon an unrelated action, an old event seems to be emitted and rendered to a display error. Think if you had two buttons that generated two messages somewhere on screen, and clicking one button was showing the message for the other button.
Being new to RxJs I'm not positive where the problem lays. I don't see a single ReplaySubject in the code, only Obserables, Subjects, and BehaviourSubjects. So this is either misuse of an RxJs feature or just some bad logic somewhere.
Anyway I found the code with the related Observable and I'm not quite sure what this person was trying to accomplish here. I have read up on combineLatest, map, and pipe, but this looks like pointless code to me. Could it also be somehow re-emitting old events? I don't see dynamic subscriptions anywhere, especially in this case.
Tldr I don't understand the intent of this code.
export interface IFeedback {
id: number
text: string
}
export interface IFeedbackMessages {
message: IFeedback | undefined
}
feedback$ = new BehaviorSubject<IFeedback | undefined>(undefined)
feedbackNotifs$: Observable<IFeedbackMessages> = combineLatest([
feedback$
]).pipe(
map(([feedback]) => ({
feedback
})
))
I also found this which maybe be an issue. In the React component that displays this message, am I wrong but does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
const FeedbackDisplay: React.FC () => {
const [feedbackNotifications, setFeedbackNotifications] = React.useState<IFeedbackMessages>()
React.useEffect(() =>
{
const sub = notification$.subscribe(setFeedbackNotifications)
return () => sub?.unsubscribe()
}, [notifications$])
}
Could it also be somehow re-emitting old events?
Yes, it probably is. BehaviorSubject has the unique property of immediately emitting the last value pushed to it as soon as you subscribe to it.
It's great when you want to model some persistent state value, and it's not good for events whose actual moment of occurrence is key. It sounds like the feedback messages you're working with fall into the second category, in which case Subject is probably a better choice.
does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
Not exactly. useEffect accepts a callback, and within that callback you can optionally return a "cleanup" function. React will hang onto that function until the effect is triggered again, then it calls it to clean things up (which in this case consists of closing out the subscription) to make room for the next effect.
So in this case, the unsubscribe will only happen when the component is rendered with a new value for notifications$. Also worth pointing out that notifications$ will only change if it's either passed as a prop or created within the component function. If it's defined outside the function (imported from another file for example), you don't need to (and in fact should not) put it into useEffect's dependency array.

React Testing Library TypeError: Failed to execute 'removeEventListener' on 'EventTarget': 2 arguments required, but only 1 present

i have react class component that have event
constructor(props) {
super(props);
this.monthRef = React.createRef();
this.yearRef = React.createRef();
this.state = {
moIndexActive: props.initialMonth - 1,
yeIndexActive: years.findIndex(y => y === `${props.initialYear}`),
};
}
//some code blablabla
// this error occur in this line bellow
componentWillUnmount() {
this.monthRef.current.removeEventListener('scroll');
this.yearRef.current.removeEventListener('scroll');
}
this component used in functional component,
when i test the functional component there is error message
i am using React Testing Library for test this, i already searched on google but not yet found solution for this, please help me.
if there is any way to mock the removeEventListener on react testing library or Jest.
thanks in advance.
The removeEventListener needs two arguments, type and listener.
type specifies the type of event for which to remove an event listener and
listener is the EventListener function of the event handler to remove from the event target.
So try to put a null in the place of listener like this
this.monthRef.current.removeEventListener('scroll',null)
Hope it works.
alfan-juniyanto,
The error message tells you exactly what's wrong -- your call to the method removeEventListener('scroll'), in your componentWillUnmount() function, is expecting two parameters:
The event to stop listening to,
The current event handler that you had previously registered when you called addEventListener (which doesn't appear to be in the code snippet you supplied).
For more information, you can read here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
However, to specifically answer your question, you should pass a pointer to the event handler that you used in the addEventListener cycle.
If you just want to make this error message stop for the purposes of your test, you could do something like this:
componentWillUnmount() {
const _fnEmptyFunctionPointer = ()=>{};
this.monthRef.current.removeEventListener('scroll', _fnEmptyFunctionPointer);
this.yearRef.current.removeEventListener('scroll', _fnEmptyFunctionPointer);
}
The code snippet I provided above simply just passes an empty function as the second (required) parameter for the removeEventListener method (really however, like I said previously, you should be passing the pointer to the original function you registered for the listener).
Hope this helps at least explain whats going on with that error you recieved.
Good luck!

access to the endpoints wp api rest title post rendered inreact app

I cracking my head against the wall, I'm developing a React App with wordpress as an API (wp rest API). everything is working well, I'm fetching and rendering all the post but when I go to the single post I can't fetch and render the title and content because it's said {rendered: "dwdwdf"} and I do the {post.title.rendered} as I do in the postlist component to get the title and works but in the single post doesn't.
To make it more clear here is the code:
const id = this.props.match.params.id;
let postUrl = `http://thesite.com/wp-json/wp/v2/posts/${id}`;
fetch(postUrl)
.then(data => data.json())
.then(data => {
this.setState({
item: data
})
})
when I console the this.state.item.title it shows me this:
{rendered: "dwdwdf"}
rendered: "dwdwdf"
__proto__: Object
it should be render as I do in my postlist component, like this {this.state.item.title.rendered} and done ! but not, it gave me this error:
TypeError: Cannot read property 'rendered' of undefined
I checked the api rest wp documentation and a lot of blogs and stackoverflow but I can't find anything that helps me.
if someone can guide me, thanks.
In principle, what you have shown should work. The response of that WP API endpoint does include:
"title": {
"rendered": "Your post's title"
},
This is backed up by your console.log output.
You haven't shown your default state, where your console.log output is written, or where you are trying to access the full path (i.e. {this.state.item.title.rendered}), but it sounds like you are doing at least the full path part in the render function.
Assuming so, I believe what you have is a timing issue. The render function may run as many times as it needs to (i.e. when the component updates). The first time it runs, your state does not have the title property yet, as your HTTP request is not yet complete. It's undefined, and when you try to access a child property of undefined, you get that error.
The difference with your earlier console statement is you aren't trying to access a property of undefined. You are just outputting the state's value itself (i.e., undefined). And one very tricky thing about the console is that it's not a historical record. A value that a console.log shows can change...say from 'undefined' to the value that gets set there later, title property and all. It all happens so fast that you don't see this.
It's best to keep in mind that the render() function may run over and over again, and your JSX needs to be written in such a way that it accounts for the possible states you expect. Here, you can expect that initially your state for "item" does not have all the properties that it will have later.
You could instead write something like,
{this.state.item.title ? this.state.item.title.rendered : 'Loading...'}
Or whatever else you'd like to write there (or leave it blank, etc.). So you are first checking to see if title has a truthy value (not undefined), and if so, accessing the child's property. A common pattern is to use this form:
{this.state.item.title && (<h1>{this.state.item.title.rendered}</h1>)}
Here the difference is we are relying on JavaScript's return value for &&, which will be the second item's value. If the first item is falsy, it doesn't even look at the second part, so it doesn't complain.
Or, you may try to take a step back and track a separate variable in state for your loading process. Something like dataReady, which you would set to true once you receive the HTTP request back. Then, your render code looks a bit cleaner:
{this.state.dataReady && (
<h1>{this.state.item.title.rendered}</h1>
<h2>(some other stuff</h2>
)}
For more information, I recommend you read about conditional rendering.
I'm just guessin but this may be due to your state not having the needed initial conditions to render correctly, since you are loading the info asynchronously.
For example, if your intial state is like this:
this.state = {
item: {},
...anyOtherAttributes
}
and you try to render this.state.item.title.rendered, it won't find title so it'll be undefined and then React won't be able to get the rendered property correctly.
To fix it you should initialize your posts like:
this.state = {
item: {
title: {rendered: ''}
},
...anyOtherAttributes
}
so it has kind of a placeholder to render while waiting for the aynchronous call to end and update the state.
Hope it helps.

Using this.props.history.push("/path") is re-rendering and then returning

Edited the question after further debugging
I am having a strange issue, tried for a while to figure it out but I can't.
I have a React Component called NewGoal.jsx, after a user submits their new goal I attempt to reroute them to my "goals" page.
The problem: After they submit the browser loads in my goal page, but only for one second. It then continues and goes BACK to the NewGoal page!!
I am trying to understand why this is happening, I am beginning to feel that this might be an async issue.
Here is my code, currently it is using async-await, I also tried the same idea using a .then() but it also didn't work:
async handleSubmit(event)
{
const response = await axios.post("http://localhost:8080/addGoal",
{
goalID: null,
duration: this.state.days,
accomplishedDays: 0,
isPublic: this.state.isPublic,
description: this.state.name,
dateCreated: new Date().toISOString().substring(0,10),
}) */
// push to route
this.props.history.push("/goals");
}
While debugging, I tried taking out the functionality where I post the new message, and just did a history.push, code is below - and this completely worked.
// THIS WORKS
async handleSubmit(event)
{
// push to route
this.props.history.push("/goals");
}
But as soon as I add anything else to the function, whether before the history.push or after, it stops working.
Any advice would be very very appreciated!
Thank you
In the React Router doc's the developers talk about how the history object is mutable. Their recommendation is not to alter it directly.
https://reacttraining.com/react-router/web/api/history#history-history-is-mutable
Fortunately there are few ways to programmatically change the User's location while still working within the lifecycle events of React.
The easiest I've found is also the newest. React Router uses the React Context API to make the history object used by the router available to it's descendents. This will save you passing the history object down your component tree through props.
The only thing you need to do is make sure your AddNewGoalPage uses the history object from context instead of props.
handleSubmit(event)
...
//successful, redirect to all goals
if(res.data)
{
this.context.history.push("/goals")
}
...
})
}
I don't know if you're using a class component or a functional component for the AddNewGoalPage - but your handleSubmit method hints that it's a member of a Class, so the router's history object will be automatically available to you within your class through this.context.history.
If you are using a functional component, you'll need to make sure that the handleSubmit method is properly bound to the functional component otherwise the context the functional component parameter is given by React won't not be available to it.
Feel free to reply to me if this is the case.

Resources