Connection useFormik and useState is not updating the input field value - react-hooks

Task: I'm trying to change the value of the input field using user-friendly button with. In other words to change the value of the field I don't use the input/select and other tags intended for this.
To be more precise, I'm making an online store with an administrative panel and creating the form to create a category(product categories, such as clothing, electronics and others). And each category has a ParentId(0 - if the first parent or some other value if the child category). I made the catalog button the same is in absolutely every online store and want to link this logic with the creation of a new category.
In the expanded categories menu, select a category, and the parent ID of this category will be shown in the text field. Like, I will select "Clothes and shoes" as the parent category, and a value will be placed in the text field, for example, 8 (the parent ID of the category "Clothes and Shoes")
My approach: I create one state field and change this one when I click on the one of the menu items. Menu items open when I click on the button.
import React, {FC, useState} from "react";
import {Box, Button, Stack, SxProps, TextField} from "#mui/material";
import {useFormik} from "formik";
// own modules
import Catalog from "../catalog/Catalog";
const CreateCategory: FC = (sx?: SxProps) => {
const [parentId, setParentId] = useState<number>(0);
const formik = useFormik({
initialValues: {
name: "",
label: "",
parentId: parentId
},
onSubmit: (values, {setSubmitting}) => {
console.log("values: ", values); // value of the parentId doesn't change on submit
// ...other code...
setSubmitting(false);
}
});
console.log("parentId: ", parentId); // but value of the parentId in the state change properly
return (
<Box
sx={{
display: "grid",
justifyContent: "flex-start",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
gap: "2rem",
"> *": {
gridColumn: "span 2"
},
...sx
}}
component="form"
onSubmit={formik.handleSubmit}
>
<TextField
error={!!(formik.errors.name && formik.touched.name)}
name="name"
onChange={formik.handleChange}
value={formik.values.name}
label="Name (technical - in Latin)"
variant="outlined"
helperText={formik.errors.name}
/>
<TextField
error={!!(formik.errors.label && formik.touched.label)}
name="label"
onChange={formik.handleChange}
value={formik.values.label}
label="label(for users)"
variant="outlined"
helperText={formik.errors.label}
/>
<Stack direction="row" spacing={2}>
<TextField
sx={{flexGrow: 1}}
error={!!(formik.errors.parentId && formik.touched.parentId)}
name="parentId"
value={parentId}
label="parentId"
variant="outlined"
helperText={formik.errors.parentId}
/>
<Catalog onClickOverload={setParentId} /> {/*custom catalog button*/}
<Button
sx={{height: "100%", width: "max-content"}}
onClick={() => setParentId(0)} // you can change this value(0 to 10, example) to check, that initial value(0) doesn't change in the formik values
>
Set parent (0)
</Button>
</Stack>
<Button
sx={{gridColumnStart: 2, gridColumnEnd: 4}}
type="submit"
disabled={formik.isSubmitting}
variant="outlined">
Create
</Button>
</Box>
)
}
export default CreateCategory;
I can't figure out what to try, because the value of the state change but not the parentId value of the formik. I'm at a dead end. Where is my mistake?

There was no need to use useState in this case. It turned out that it was possible to use the following construction:
formik.setFieldValue(nameField, value);
And when you need to change the value in the some custom way - you can use this one.

Related

Lazyload image in Vue/Nuxt gallery component

I'm trying to create a simple gallery component where if you click on some image a Light-Box will appear where you can see full size photo and have options like next and previous photo or close the Light-Box.
Currently When I need to change the image to next or previous I change the src of the img-tag and it works.
Here comes my problem. I want to lazy load my images. I use lazysizes in my project.
So the simple implementation to have an image to load is to add the class "lazyload" and to pass the property data-src instead of src.
However if I change to data-src my methods for next and previous image are not working.
< script >
export default {
props: {
data: {
type: Array,
required: true,
},
},
data: () => ({
visible: false,
currentImage: 0,
}),
methods: {
Toggle(index) {
this.currentImage = index
this.visible = !this.visible
},
Next() {
if (this.currentImage !== this.data.length - 1) this.currentImage++
},
Prev() {
if (this.currentImage !== 0) this.currentImage--
},
},
} <
/script>
<template>
<div id="gallery" class="gallery">
<!-- images grid -->
<div v-for="(item, i) in data" :key="'gallery-image' + i" class="image">
<img :src="item.image.thumbnail.url" #click.native="Toggle(i)" class="lazyload"/>
</div>
<!-- image lighbox on click -->
<div v-if="visible" class="lightbox">
<Icon class="cancel" #click="Toggle()"/>
<Icon name="left" :class="{ disable: currentImage == 0 }" #click="Prev()"/>
<img :src="data[currentImage].image.url" class="lazyload"/>
<Icon name="right" :class="{ disable: currentImage == data.length - 1 }" #click="Next()"/>
</div>
</div>
</template>
UPDATE
I forgot to add crucial code. To implement lazysizes in a Nuxt project we need to add in nuxt.config.js the fallowing code. You can read more here.
build: {
extend(config, { isClient, loaders: { vue } }) {
vue.transformAssetUrls.img = ['data-src', 'src']
},
},
As I investigate in the developer tools I found that when triggering click for method like Next image, the src of the image does not change, only the data-src. I'm guessing I need a way to trigger this transform so that everything can work as expected.
Also, on top of my comment, I do recommend looking into the official nuxt image module which do have native lazy loading out of the box: https://image.nuxtjs.org/components/nuxt-img
You could maybe combo this with some simple lightbox that does the trick for you. I've used vue-silentbox before, it is working pretty well.
You can have that kind of code there
<silent-box :gallery="photosToDisplay">
<template #silentbox-item="{ silentboxItem }">
<img :src="silentboxItem.src" :key="silentboxItem.id" />
</template>
</silent-box>
So, I guess that you could totally swap img by a nuxt-img there, and have it lazy-loaded.
The images are not lazy-loaded in the project, but here is a small project that I did to try out the lightbox if you want to quickly look how it renders (URL in the top right corner).
Probably this is not the most elegant way to do it . I force re-render to my image component. You need to assign a key value to component and whenever the value changes a new instance creates of the component.

Can we maintain object for text inputs in redux form

I am thinking to use redux Form for my application.
can we maintain an object for text input types.
ex: I have one field Title so in redux store i want to main it as:
Title: {
en_US: "This is in English",
en_GB: "This is UK English",
fr_FR: "This is in french"
}
So I have flags for selecting languages whenever i change the language my form should be rendered with the locale selected.
Thanks
Yes, it's possible, but it has nothing to do with redux-form. You could just render the label for an input independently:
<label>{this.props.myTextLabel}</label>
<Field id="myText" /> // Field comes from redux-form
Another option is to use a custom component and pass the title to it:
<Field id="myText" component={MyInput} label={this.props.myTextLabel} />
const MyInput = ({ ..., label }) => {
return (
<div>
<label>{label}</label>
<input type="text" ... />
</div>
)
}

Set input[text] value according to radio button

I'm trying to set the value of an input corresponding to the selected radio button, but I just can't figure out how.
<input type="text" class="input" name="provider" v-model="selected_provider">
<b-radio-group v-model="provider_options">
<div class="field">
<template v-for="provider in providers">
<b-radio
name="select_provider[]" id="provider_#{{ $index }}"
model="selected_provider"
value="provider"
type="radio">
#{{ provider.provider_name }}
</b-radio>
</template>
</div>
</b-radio-group>
I've managed to get an array to display and that the input shows whatever is written in selected_provider, but I just don't know how to link those together.
var app = new Vue({
el: '#app',
data: {
selected_provider: '',
providers: [
{
provider_name: 'Twitch',
provider_url: 'https://www.twitch.tv/'
},
{
provider_name: 'Smashcast',
provider_url: 'https://www.smashcast.tv/'
},
{
provider_name: 'AzubuFrost',
provider_url: 'https://www.smashcast.tv/'
}
]
},
});
furthermore I'd like to attach the value of
<input type="text" class="input" name="name" id="name">
to the end of provider_url in selected_provider
Is someone so kind to help me?
If you set your for-loop to v-for="(provider, index) in providers"You can put a v-on:click="setSelectedProvider(index)" inside <b-radio></b-radio> and can create a method that looks like this:
setSelectedProvider: function(index){
this.$set(this, 'selected_provider', index);
}
Whenever a radio button is clicked this will set selected_provider to the index of the clicked radio button.
As for attaching the value of your other input to the end of provider_url, you can create a method that does this:
updateProviderURL: function(event){
this.$set(this.providers[this.selected_provider], 'provider_url',
'https://www.twitch.tv/' + event.currentTarget.value);
}
You can then add a listener to your input that looks like this:
v-on:input="updateProviderURL($event)"
It's good to set selected_provider by default to 0 inside the data part of your component so that the updateProviderURL function never tries to set a part of the array that doesn't exist ;)

Bold specific word within Redux Form's Form Field input

I have a FormField from ReduxForm that I want to make bold for its label. But I don't want to bold the entire label. Just a specific word within it. I know you can do this with a normal label for by placing a span with a class in vanilla html, but how do I do this with Redux Form?
Just specify a label prop which accepts a string or a node:
export const renderField = ({ label, input, meta, ...props }) => (
<div>
<label>{ label }</label>
<input { ...input } />
</div>
);
// In your form
<Field component={renderField} label="Foo bar baz" />
<Field component={renderField} label={ <span>Foo <strong>bar</strong> baz</span> } />

How to prepopulate tokenput with values found in database

I am using the tokeninput control found here at http://loopj.com/jquery-tokeninput/ - its quite popular I believe.
I have the following code that fills the input box nicely with author names - but I want to prepopulate the control with values found in the database when the user is in an EDIT session i.e. to find authors that have been found for that record already (looks something like this):
Here's the code:
$("#authorlist").tokenInput('/author/getauthors/', {
hintText: "Enter surname",
searchingText: "Searching...",
preventDuplicates: true,
allowCustomEntry: true,
highlightDuplicates: false,
tokenDelimiter: "*",
theme: "facebook"
// prePopulate: [{"id": 5016, "name": "Test 1" }]
});
Obviously this already gets a full list of authors (/author/getauthors/) - but it needs to prepopulate from that list too, with authors already found that record - and thats the bit I can't seem to figure out.
I can see that that you can use prePopulate in the javascript (I've commented it out) and I have the found author values in my Edit.cshtml i.e.
#foreach(var item in Model.AUTHORs.Select(model => new { model} ))
{
<div type="hidden" id="authorHidden" > #item.model.FULL_NAME</div>
}
So it's just a case of putting those values in some kind of json format and getting the tokeninput control to populate them ready for when the form is loaded and shown to the user.
Other code for displaying the tokeninput control in Edit.cshtml is:
<div class="editor-label">Authors</div>
<div class="authors">
<div class="editor-field">
<input type="text" id="authorlist" name="tiAuthors" />
</div>
</div>
Any help or pointers are much appreciated.
You could use an HTML5 data-* attribute on your input inside the view to put the list of authors that you want to be prepopulated:
<input type="text" id="authorlist" name="tiAuthors" data-authors="#Json.Encode(Model.AUTHORs.Select(a => new { id = a.AuthorId, name = a.AuthorName })))" />
and then:
$('#authorlist').tokenInput('/author/getauthors/', {
hintText: 'Enter surname',
searchingText: 'Searching...',
preventDuplicates: true,
allowCustomEntry: true,
highlightDuplicates: false,
tokenDelimiter: '*',
theme: 'facebook',
prePopulate: $('#authorlist').data('authors')
});

Resources