Making v-model:value lazy - javascript

I have a simple Vue component, which comes from a 3rd party package.
The component is a text editor, and I have to provide it with a value for it to render correctly.
<SomeComponent v-model:value="text" />
<script setup>
const props = {
records: {
type: Object,
},
}
const text = computed(() => props.records.first())
</script>
Now, I want to update my database everytime the text property is changed.
watch(text, () => {
//Post to database...
})
I don't, however, want to update the database on every keystroke - hence, I want to make the v-model lazy. This I am trying to do like this:
<SomeComponent v-model:value.lazy="text" />
However, this doesn't work. The code inside my watch method is being fired on every keystroke.

As shown in the documentation, v-model is just some sugar syntax.
So that those are the same
<input v-model="text">
<input :value="text" #input="event => text = event.target.value">
If you want to have your component updated on the change event, you could then use something like this
<SomeComponent :value="text" #change="event => text = event.target.value" />
Or this one rather
<SomeComponent v-model.lazy="text" /> <!-- text is the getter and the setter here -->
As shown in the documentation: https://vuejs.org/guide/essentials/forms.html#lazy
If you want something even more advanced, you could look into debouncing your inputs (more work but it's kinda more clever into handling when to update the state).

You do not need to use v-model:value="text", just v-model="text" is enough.
If you wanted the input to be lazy, without any extra functionality (like posting to DB on every change) then v-model.lazy="text" would be enough.
When using watch, it doesn't matter whether your input is updated lazily or instantly. The watcher watches for each and every change, i.e: every keystroke in the case of an input.
So if you wish to make it lazy and make a call to DB on every change, then you need to this:
<SomeComponent :value="text" #change="onChange">\
<script setup>
const onChange = (event) => {
text.value = event.target.value;
// post to DB
}
</script>
If this doesn't work, then the suspect is the 3rd party package. In which case I suggest you look at the package's documentation and see if they provide a prop for the text editor to be lazy.

The 3rd party plugin did not allow me to omit the v-model:value property, so I ended up using lodash' debounce method instead:
<SomeComponent v-model:value.lazy="text" />
watch(text, _.debounce(function () {
//Post to database...
}, 500));

Related

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.

ReactJS autocomplete from React Bootstrap not working

I'm trying to build an autocomplete search field, using this form component from React Bootstrap, which is rendered as a input by the browser.
Here's what I did in my React component:
<FormControl
id="frenchToEnglishInput"
placeholder="type a word..."
aria-label="Recipient's username"
autocomplete="on"
data={frenchToEnglishWords}
onChange={this.fetchFrenchToEnglish}
renderItem = {item => {
return (
<div>
{item}
</div>
);
}}
/>
the frenchToEnglishWords array is declared outside the component as a var, as I intend to update it as I type some value into the input field.
Now here is the function that triggers the onChange event :
fetchFrenchToEnglish = async () => {
if(document.getElementById("frenchToEnglishInput").value!==''){
axios.get(dictionaryURIs.english.French_English+""+document.getElementById("frenchToEnglishInput").value)
.then(response => {
frenchToEnglishWords = response.data
})
}
}
The request is made from MongoDb, after setting up an autocomplete index on a collection, but this part works fine.
My question is, why is the only "autocomplete" I'm getting is the one made of the previous words I've typed ?
Or maybe the array I'm using as input data must be a const (as I've seen in many examples) and not a var ?
When I do type in some word, I do not get any autosuggestion from the frenchToEnglishWords, which is being updated from the DB.
You need to use State!
This is not working because the data field is not a State, you need to bind the fetchFrenchToEnglish function to the data state.
But first of all, there's no reason to use var, because the most differences between them is just scope and immutability, here is more about it.
Also, you can use hooks like useState and useRef to no use getElementById.

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.

SweetAlert2: How to get back values from react component?

With sweetalert2 now there is sweetalert2-react-content, that lets you use React components to show inside Swal.
It's good, but I can't seem to find how to get back values from that component.
For example, I have a component, that has two inputs.
MySwal.fire({
html: <MyComponent />
}).then(value => {
// how to get here my values from MyComponent??
});
I want get whether checkboxes are checked or not from that component.
I tried to have the state for those inputs and onChangeHandler in the component where I call MySwal. But that didn't seem to work at all, the checkboxes would not change.
In the previous version of this library there was swal.setActionValue, that seemed be what I need, but it doesn't work with the current sweetlalert2 version.
To sum up, when I press OK on the prompt, I want to get the value in the promise, that would be set by the MyComponent.
DEMO
You can try using preConfirm() as per the documentation. From perConfirm we can pass custom values.
Please find this example : https://codesandbox.io/s/kk2nv9nmy7
The following trick should do the job:
let returnedValue;
MySwal.fire({
html: <Options onChange={value => (returnedValue = value)} />
}).then(value => {
console.log(returnedValue);
});
Long story short you could pass onChange callback to the component and use it for updating a variable which stores a value. It's not very pretty but it does the job.
You could find the full example here https://codesandbox.io/s/o1005xv1q

How to update a field value after action dispatched with redux

I would like to populate a form after an ajax call with redux.
My case is pretty simple:
I have a simple user form (with only one text field for now), it's a react view bound to the state with connect().
I call a rest API to fetch the user.
When the api call is done, an action is dispatched with the user.
A reducer update the store state with the user.
I would like to populate/update the form with the retrieved values.
Solution 1:
If I set the value from the props like that:
const Field = ({ field, onFieldChange, value }) => (
<input
value={value}
onChange={(event) => { onFieldChange(field, event.target.value) }}
type="text"
/>
)
It works but I get this warning:
Field is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa).
I understand why I get this error as I should not use a component to display something and also be able to update it.
I also tried to use the defaultValue props but this is only used at the component creation (and we don't have the user yet). After the ajax call return, defaultValue cannot be called.
Solution 2:
Use redux-form with a custom plugin to update the form model each time the state get updated. I don't find this solution really clean but maybe I'm wrong.
I really thin that I'm going in the wrong direction and that it should exist a better way.
Does somebody already faced this kind of issue?
I encountered the same problem when I was trying to pass undefined as the input value.
To fix this, ensure that you are passing at least empty string to the input, not undefined
const Field = ({ field, onFieldChange, value }) => (
<input
value={value || ''} // <- add fallback value here
onChange={(event) => { onFieldChange(field, event.target.value) }}
type="text"
/>
)
Actually you might try to make your component statefull - store and manage value of input inside of it (they say it's ok).
Or if you really need this value in the store use redux-form, I have realy good experience of using it (you'll have to write less boilerplate code).
By the way, you will not have to use any custom plugin, you can use initialValues, see more here
The solution above will work for sure, but it doesn't seem to be nice.

Categories

Resources