Changing class state from an imported function in ReactJS - javascript

I am trying to change the state of a class from an imported named component so I only need to write the code once. Is this even possible?
I have 3 files ( Login, ForgotPassword, Register ). On ALL of these files I am listening for an "onChange" event on the input fields that all do the same thing as show below:
onChange method:
onChange = (e) => {
this.setState(() => ({ errors: {} }));
let fields = this.state.fields;
fields[e.target.name] = e.target.value;
this.setState(() => {
return {
fields
}
});
};
I'd really like to import this as a named method along with some others that are working already:
import { onFocus, onBlur, onChange } from './Utils/Input';
The issue is (as seen above in the onChange code), that I need to update the class state from this method.
Is this at all possible? I am very new to React, so I might be going about this in the completely wrong way.
Thank you in advance!

When trying to update the state of a component you are always updating the state regarding to a particular this.
You are trying to write on* functions which are independent of the instances you are working with. So it makes no sense to use "this" inside those.
You could pass the instance of the Class to the on-function like this:
https://codesandbox.io/s/focused-cache-zzfvp

Use the method by binding the context to it from inside the class like this
onChange.bind(this)

Related

Vue3 web component set attribute

I created stand alone web component, with Vite and Vue3, to replace old jQuery libraries.
When rendered in html, it looks something like:
<custom-datepicker id="deliveryTime">
#shadow-root
<div>...actual component...</div>
</custom-datepicker>
When user select date it would be perfect if I can set attribute value to this element, like:
<custom-datepicker value="selected value HERE" ...
so the form can then use this value from the element by it's id.
Problem is that the only way I manage to achieve this is by emitting event (selected) from my web component and listening to that event, so the form can use value, like:
const datepicker = document.querySelector('#deliveryTime');
const dateSelected = (e) => {
datepicker.value = e.detail.val;
}
window.addEventListener('selected', dateSelected);
If I set value attribute within web component, rest of the app (Apache Velocity template) can't access it because it is in the shadow-root:
<custom-datepicker id="deliveryTime">
#shadow-root
<div value="selected value is sadly HERE">...actual component...</div>
</custom-datepicker>
Is it even possible to achieve this from inside the web component (so I can omit listener)?
I found solution, maybe not the cleanest one but it works :)
In my web-component, in mounted hook component is selected:
const dp = ref(null)
onMounted(() => {
dp.value = document.querySelector("#deliveryTime");
})
After date is selected, instead of emitting just set attribute:
dp.value.setAttribute('value', value)
What if you access the HTMLElement linked to your shadowhost and then use the "setAttribute" function on it ? Assuming you have access to the shadowRoot inside your component using 'this', I think something like that will do the trick (https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/host) :
const shadowHost = this.shadowRoot.host; // The HTMLElement linked to <custom-datepicker />
shadowHost.setAttribute("value", someValueOfYourChoice);

How to test the reaction to a component event in Svelte?

In Svelte, I have a parent component which listens to a component event dispatched by a child component.
I know how to use component.$on to check that the dispatched event does the right thing within the component which is dispatching, like so.
But I can't figure out how to check that the component which receives the dispatch does the right thing in response.
Here's a basic example:
Child.svelte
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
function handleSubmit(event) {
dispatch('results', 'some results')
}
</script>
<form on:submit|preventDefault={ handleSubmit }>
<button type='submit'>Submit</button>
</form>
Parent.svelte
<script>
import Child from './Child.svelte'
let showResults = false
function handleResults(event) {
showResults = true
}
</script>
<Child on:results={ handleResults } />
{ #if showResults }
<p id='results'>Some results.</p>
{ /if }
The idea is to eventually write a test using #testing-library/svelte like:
import { render } from '#testing-library/svelte'
import Parent from './Parent.svelte'
test('shows results when it receives them', () => {
const rendered = render(Parent)
// ***
// Simulate the `results` event from the child component?
// ***
// Check that the results appear.
})
If the parent were reacting to a DOM event, I would use fireEvent.
But I don't know how I would get a hold of the <Child> component in this case, and even if I could I'm guessing that Svelte is using a different mechanism for component events.
(Just to test it out, I used createEvent to fire a custom results event on one of the DOM elements rendered by <Child> but it didn't seem to do anything.)
Anyone have any ideas? Thanks!
If you're already planning on using #testing-library/svelte, I think the easiest way is not to try to manually trigger the Child component's results event, but to use Testing Library to grab the form/submit elements and trigger the submit event (using fireEvent a SubmitEvent on the <form> or their #testing-library/user-event library, or even a vanilla dispatchEvent). Svelte would then dispatch the custom results event that Parent is listening on.
Something like:
test('shows results when it receives them', async () => {
// Arrange
const rendered = render(Parent)
const submitButton = rendered.getByRole('button', {
name: /submit/i
});
const user = userEvent.setup();
// Act
await user.click(submitButton);
// Assert
const results = rendered.queryByText(/some results\./i);
expect(results).not.toBe(null);
});
Hope this is what you had in mind.
Edit:
For mocking Child.svelte, something like this in a __mocks__/Child.svelte should work:
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function handleSubmit(event) {
dispatch("results", "some results");
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<button type="submit">Test</button>
</form>
Which is the exact same implementation as the actual module (I gave the button a different label just to make it clear it's the mocked version when querying it), but the idea is that this would never need to change and is only used to dispatch a results event. Then you'd just need to tell Jest or whatever you're using that you're mocking it (jest.mock("./Child.svelte");), change the getByRole query to match the new name (or just leave the mock with the original name), then it should just work.
Whether you think that's worth it or not is up to you. I've generally had success testing the UI as a whole rather than mocking sub-components, but I guess it comes down to preference. Yes, you might have to change the test if the Child component changes, but only if you change the label of the button or change the user interaction mechanism.
You don't need to know about the details of the components, you don't even need to know that it's split into a separate Child component, all the test would care about is a general idea of the structure of the UIā€”that there's a button called "Submit" and that clicking on it should show an additional <p> tag.

React hooks onclick event with multiple params without unnecessary rerender?

I am using react hooks and functional components and was wondering how I can add multiple params to an react onClick event.
I know there are different options to achieve this. In the past I used this style below (from https://reactjs.org/docs/handling-events.html):
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
But now I am facing this exact described problem from the official react docs. I am getting to many rerenders because of these arrow functions in the onClick attribute:
The problem with this syntax is that a different callback is created
each time the LoggingButton renders. In most cases, this is fine.
However, if this callback is passed as a prop to lower components,
those components might do an extra re-rendering. We generally
recommend binding in the constructor or using the class fields syntax,
to avoid this sort of performance problem.
I have put my function already in a useCallback hook. But if I use this function in a onClick event with an arrow function it will trigger rerenders again.
If I change it to the function reference only it is not triggering rerenders.
So far this is fine.
But: How do I add multiple parameters to this functionreference when using react hooks and functional components.
Will I get by default always the e (event parameter?) as first parameter?
Can somebody explain to me when and how I am getting the react event parameter and when I will not receive this event?
How can I add multiple params beside the event parameter in my onClick attribute?
For example:
What if I have this function and want to use it in the react onClick attribute, prevent unnecessary rerender and add multiple different parameter in the function call
const myClickFunction = (e, value1, value2, value3) => {
// ...
}
// this would trigger rerenders because of the arrow function how do I prevent this?
<button onClick={(e) => myClickFunction(e, "input1", "input2", "input3")}>
Click me
</button>
One trick I like to use in this case is to "bind" the parameters to rendered element using data attributes
const myClickFunction = (e) => {
const value1 = e.currentTarget.getAttribute('data-value1')
const value2 = e.currentTarget.getAttribute('data-value2')
const value2 = e.currentTarget.getAttribute('data-value2')
}
// this would trigger rerenders because of the arrow function how do I prevent this?
<button onClick={myClickFunction} data-value1="a" data-value2="b" data-value3="c">
Click me
</button>
This way you can memoise your function using useCallback safely and you can reuse the same function if you want to pass it to array of children for example. This is not ideal, you couple parents and children and you can only use data which is serializeable to string (basically only primitives).
Better solution would be to store your values somewhere out of component tree so you can access them without closures (for example in redux-thunk you don't need to pass a lot of stuff around, you can just get data you need from store directly by calling getState)

Need some suggestions for a dynamic form using React

I'm building an enterprise-level application and I need some tips and suggestions for handling dynamic form.
The fields of the form are totally dynamic and they come differently for each user.
I loop through each field(fields come from an API call) on a file called renderUiType.js and based on a property of the field called uitype, we render different Inputs.
For example if uitype===1{render TextField}, if uitype===2{ render Checkbox } and so on...
So far the displaying part is correct but now I want to save the values of each field rendered and have them all in an object so I can do a POST API Call
So my question is, how can I do that? Should I create an onChange handler function for each form-element at the main file renderUiType.js and then pass it with props to the form-elements components or should I use Redux?
Any suggestion/article or anything is welcomed. Thank you
The folder structure looks like the image below(just in case it helps to understand what I ask)
..
You can use one callback function and use it in each onChange component specific handlers. You could have everything in state of the Form if you would like hidden under the unique keys/id, so you don't need to have Redux. f.e.
if (uitype===1)
{render <TextField value={this.state[fieldId]} onChange={this.onChange}/>}
if (uitype===2)
{ render <Checkbox value={this.state[fieldId]} onChange={this.onChange}/>}
or to simplify:
const getComponentByUIType = (uiType) => {
switch(uiType) {
case 1: return TextField
case 2: return Checkbox
}
}
// ...
onChange = fieldId => value => this.setState(state => ({fieldId: value}))
//...
render() {
getComponentByUIType(uiType).map(Component => <Component value={this.state[fieldId]} onChange = {this.onChange(fieldId)} />
}
Using Redux for this shouldn't be necessary unless you need to access this form's state somewhere outside the form. If you only need the form info to do a POST, I would keep all the data inside one component's state.
Just use the unique ID provided by the IP (the one you were gonna use for the POST) to build that state object. Every field will have an onChange that updates the main form component's state, and then that same value from the state is passed in to each field as a prop.

Trigger onChange when setting State in componentDidMount?

I need to add some query paramaters to my url as a person checks off checkboxes.
I am using react router so I do something like this in my checkboxes on change event.
const stringified = queryString.stringify(parsed);
const path = `${this.props.location.pathname}?${stringified}`;
this.props.history.replace(path)
This does however seem to cause a re-render of the page(not sure if this should be happening, would prefer it not to do that so maybe I got to use something other than replace?).
I wanted to check on componentDidMount the url to see if the value is there. If it is there then I wanted to update the state of the checkbox.
#observable
isChecked = false;
#action
componentDidMount() {
const parsed = queryString.parse(this.props.location.search);
this.isChecked = parsed && parsed["param"] === this.props.option;
}
However I don't see the onChange being trigger.
Right now I have on change a function that takes the value and uses it to filter, so I need the function to run.
I could put that function in the componentDidMount but I wanted to make sure before I do that, there is nothing I am missing on why the change event is not be fired.
Try setting the state in the constructor() and incomponentDidUpdate().
When a URL parameter is added to the same route, the existing component is utilized (i.e. an update event) vs. a new one being created. As a result, you won't see a componentDidMount() event.
Another option/solution is to update the state of isChecked directly and push the history/url change.
If what you are trying to prevent is the page refresh use this built in function in your onSubmit event(if I understand your question correctly.)
event.preventDefault();
It stops the browser from auto-refreshing! Make sure to call event in your function though.
ie
onSubmit=(event)=>{
event.preventDefault();
//rest of code
}
If you are trying to filter, the es6 .filter method is useful for checkboxes. I personally used a select dropdown menu to filter the options and selectively show the ticked items in a ToDo List: "Done" "Active" "Completed" and used those states in my filter method.

Categories

Resources