React Select Always re-render, even when change another state - reactjs

React Select Always re-render, even when change another state.
...
const [expense_currency, setExpenseCurrency] = React.useState(expense_claim.expense_currency);
const [remarks, setRemarks] = React.useState(expense_claim.remarks);
...
...
return (
...
<div className="form-group row p-0 col-sm-12 col-md-6">
<label className="text-nowrap col-form-label col-sm-4">Currency</label>
<div className="col-sm-8 p-0" title={expense_currency}>
<Select
className="select2"
placeholder="Select Currency"
isDisabled={false}
isClearable={true}
value={expense_currency}
options={select_currency}
onChange={selected => { setExpenseCurrency(selected.value) }}
/>
</div>
</div>
<div className="form-group row p-0 col-sm-12 col-md-6">
<label className="text-nowrap col-form-label col-sm-4">
Remarks
</label>
<div className="col-sm-8 p-0">
<textarea
className="form-control"
required={true}
disabled={false}
name="remarks"
placeholder=""
defaultValue={remarks}
onChange={e => setRemarks(e.target.value)}
></textarea>
</div>
</div>
)
SS from my chrome react profiler
i have try react-select v2.0.0 to v.2.4.4 all have same result.
can i know how to make it not rerender when i'm update my remarks fields ?

This depends on whether Select is using PureComponent or memo() or something similar. But if it is the props you pass in will break the check every time since you are creating a callback for onChange every render.
To have a consistent callback reference use useCallback:
const onSelectChange = useCallback(selected => setExpenseCurrency(selected.value), [setExpenseCurrency]);
And pass it in:
onChange={onSelectChange}
If select_currency is a non-primitive e.g. array or object you will need to ensure that is a consistent reference too. You didn't post that so can't advise.

Related

How to bind an object attribute in TS React

To introduce myself to React I am developing a small application - it is a 'media bookmark'. For example you really like a chapter from a certain book, so you give the application the name of the book, chapter, that it is a book, a description and a link if applicable.
The error I keep getting is:
Argument of type 'string' is not assignable to parameter of type 'SetStateAction<MediaBookmarkDTO>'
This is my code:
const [newBookmark, setNewBookmark] = useState<MediaBookmarkDTO>({ bookmarkName: '', bookmarkDescription: '', bookmarkType: '', bookmarkChapOrEp: '', bookmarkLink: '' });
And where I try to bind:
<div className="form-group col-md-4">
<label htmlFor="BookmarkName">Name:* </label>
<input type="text" className="form-control" id="BookmarkName" placeholder="Name"
value={newBookmark.bookmarkName} onChange={(e) => setNewBookmark(e.target.value)} />
</div>
Currently you are trying to update newBookmark with a string. Since a string isn't a MediaBookmarkDTO, you get an error. You probably meant to update the name only, which you can do inline like this:
<div className="form-group col-md-4">
<label htmlFor="BookmarkName">Name:* </label>
<input
type="text"
className="form-control"
id="BookmarkName"
placeholder="Name"
value={newBookmark.bookmarkName} onChange={(e) => setNewBookmark({
...newBookmark,
bookmarkName: e.target.value,
})}
/>
</div>
First of all welcome to stack overflow family.
newBookmark variable is MediaBookmarkDTO which is a object, when you try to update directly using setNewBookmark(e.target.value), it is trying to provide string value to newBookmark variable, which is where typescript is complaining.
When you are working with forms, in starting I would recommend to have separate state for each field it will help you understand more, when you got the base, then you can use single variable to store all form state. Below is an example to manage the form using a separate state.
import React, { useState } from 'react'
function BookmarkComponent() {
const [bookmarkName, setNewBookmarkName] = useState<string>('');
const [bookmarkDescription, setBookmarkDescription] = useState<string>('');
const [bookmarkType, setBookmarkType] = useState<string>('');
const [bookmarkChapOrEp, setBookmarkChapOrEp] = useState<string>('');
const [bookmarkLink, setBookmarkLink] = useState<string>('');
return (
<form>
<div className="form-group col-md-4">
<label htmlFor="BookmarkName">Name:* </label>
<input type="text" className="form-control" id="BookmarkName" placeholder="Name"
value={bookmarkName} onChange={(e) => setNewBookmarkName(e.target.value)} />
</div>
<div className="form-group col-md-4">
<label htmlFor="BookmarkDescription">Description:* </label>
<input type="text" className="form-control" id="BookmarkDescription" placeholder="Description"
value={bookmarkDescription} onChange={(e) => setBookmarkDescription(e.target.value)} />
</div>
<div className="form-group col-md-4">
<label htmlFor="BookmarkType">Type:* </label>
<input type="text" className="form-control" id="BookmarkType" placeholder="Type"
value={bookmarkType} onChange={(e) => setBookmarkType(e.target.value)} />
</div>
<div className="form-group col-md-4">
<label htmlFor="BookMarkChapter">Chapter:* </label>
<input type="text" className="form-control" id="BookMarkChapter" placeholder="Chapter"
value={bookmarkChapOrEp} onChange={(e) => setBookmarkChapOrEp(e.target.value)} />
</div>
<div className="form-group col-md-4">
<label htmlFor="BookmarkLink">Link:* </label>
<input type="text" className="form-control" id="BookmarkLink" placeholder="Link"
value={bookmarkLink} onChange={(e) => setBookmarkLink(e.target.value)} />
</div>
</form>
)
}
export default BookmarkComponent
When You get more experience you can use Libraries to manage form they are extremely Helpful when managing complex form, below are libraries I used which works very well
React Hook Form
Formik

Formik - name and ID are not being passed to Field

I have a component like below, and when I pass name prop or an ID, it is not being passed to a component and I can check two radio buttons at once. Also ,how can I update a predefined field in initial values with value of a checked radio button?
In React Devtools it looks ok
I get the following error
Warning: Formik called `handleChange`, but you forgot to pass an `id` or `name` attribute to your input:
import React from "react";
import { Heading, OptionCard } from "../..";
import data from "../../../../lang/pl.json";
import { Field } from "formik";
export const Step1 = ({
errors,
touched,
values,
handleChange,
handleBlur,
handleSubmit
}) => {
console.log(values, errors, touched);
return (
<>
<div className="bottomSeparator mb-4">
<Heading variant="h3">{data["global.offer-details"]}</Heading>
</div>
<Heading variant="h5" bsClasses="my-1" info>
{data["global.category-offer"]}
</Heading>
<div className="row">
<div role="group" className="col-12">
<div className="col-xs-6 col-lg-3">
<Field
type="radio"
component={OptionCard}
value={data["create.blade.text-1"]}
name={values.kategoria}
onChange={handleChange}
id={data["create.blade.text-1"]}
name={data["create.blade.text-1"]}
/>
</div>
<div className="col-xs-6 col-lg-3">
<Field
type="radio"
component={OptionCard}
value={data["create.blade.text-2"]}
name={values.kategoria}
onChange={handleChange}
id={data["create.blade.text-2"]}
name={data["create.blade.text-2"]}
/>
</div>
</div>
<div className="row mb-3">
<div className="col-md-12 col-sm-12 col-xs-12">
<div className="form-group">
<label>
{data["global.category-offer"]}{" "}
<span className="red">
{data["global.required"]}
</span>
</label>
<select
id="type"
name="type"
className="d-block"
required="yes"
>
<option value="course">
{data["create.blade.text-1"]}
</option>
<option value="private_lesson">
{data["create.blade.text-2"]}
</option>
</select>
</div>
</div>
</div>
</div>
</>
);
};
thanks
You have values formik variable.
You defined name as name={values.kategoria} (example). When page renders, you have name={values.kategoria} equals to "".
You have also doubled name props, make always according to your data variable as you import it, as you did here name={data["create.blade.text-1"]}
Also good thing to do:
You define initialValues and assign your data to your initialValues. After, you are using {values} variable inside render component, example here:

React JS - Proper way to show the values from json array

I have a requirement to fetch and show the values in the form text box. I need to check whether if the value exists and not equal to null then show the values in the text box otherwise show a blank field.
The existing code implementation shows something like this :
{
this.state.testJson.Names ? this.state.testJson.Names.length > 0 ? this.state.testJson.Names.map(response =>
<div className="form-group col-md-3" key={response.nameId}>
<label htmlFor="firstName">{Liferay.Language.get('first-name')}</label>
<input name="firstName" value={response.otherName} type="text" className="form-control" id="firstName" />
</div>
):
<div className="form-group col-md-3">
<label htmlFor="firstName">{Liferay.Language.get('first-name')}</label>
<input name="firstName" value='' type="text" className="form-control" id="firstName" />
</div> :
<div className="form-group col-md-3">
<label htmlFor="firstName">{Liferay.Language.get('first-name')}</label>
<input name="firstName" value='' type="text" className="form-control" id="firstName" />
</div>
}
I somehow feel this is not the best way to implement it as I need to avoid code repetition. Could someone tell me what is the better way to achieve this?
Thanks
There's a nice sintactic sugar for modern JavaScript and React (if you are using React, you must likely have it), you simply add a question mark before the object you're not sure it exists like:
this.state?.testJson?.Names?.length > 0
Also, you could have default values of a nullish variable like:
// names will be an empty array if it doesn't exist or if it's nullish
const names = this.state?.testJson?.Names ?? [];
All together is:
const names = this.state?.testJson?.Names ?? [];
return(
names.map(response =>
<div className="form-group col-md-3" key={response?.nameId}>
<label htmlFor="firstName">{Liferay.Language.get('first-name')}</label>
<input
name="firstName"
value={response?.otherName}
type="text"
className="form-control"
id="firstName"
/>
</div>
) : ....rest of the code!
);
You can fetching inside some cards
<div className="row">
{!names
? "Loading..."
: names.map((name) => {
return (
<div className="col">
<div className="card-body">
<h5 className="card-title">{name.firstname}</h5>
</div>
</div>
</div>
);
})}
</div>

Trying to have multiple input elements

But when i use handle input change function it erases the entire object and replaces it with one property
Also If someone can help me reset the form data? because I'm setting state to the initial state value but the text fields arent erasing.
const initialState = {
name: '',
number: '',
message: '',
email: '',
messageSent: false,
};
//State After typingState
{email: "2"}
I was using a class-based component and it was working fine I switched over and now I am getting one property on submit instead of 4
I would like for the handle change to change a particular property and not the entire object
stepped away from react for a while an d not sure what to google fo this fix. Tried
Handling Multiple inputs Functional Components React etc..
let handleInputChange = (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
setstate({
[name]: value,
});
}
return (
<section
id="contact-form"
className={
GrayBg === true
? 'contact-form-area_3'
: 'contact-form-area_3 contact-page-version'
}
>
<div className="container">
<div className="section-title mb45 headline text-center">
<span className="subtitle text-uppercase">Send us a message</span>
<h2>
Send Us A<span> Message.</span>
</h2>
</div>
<div className="contact_third_form">
<form
className="contact_form"
encType="multipart/form-data"
onSubmit={ sendEmail}
>
<div className="row">
<div className="col-md-4">
<div className="contact-info">
<input
className="name"
name="name"
type="text"
value={state.value}
onChange={handleInputChange}
placeholder="Your Name."
/>
</div>
</div>
<div className="col-md-4">
<div className="contact-info">
<input
className="email"
name="email"
type="email"
value={state.value}
onChange={handleInputChange}
placeholder="Your Email"
/>
</div>
</div>
<div className="col-md-4">
<div className="contact-info">
<input
className="number"
name="number"
type="number"
value={state.value}
onChange={handleInputChange}
placeholder="Phone Number"
/>
</div>
</div>
</div>
<textarea
name="message"
placeholder="Message."
value={state.value}
onChange={handleInputChange}
></textarea>
<div className="nws-button text-center gradient-bg text-uppercase">
<button id="contact-button" type="submit">
{state.messageSent ? 'Sent!' : 'Send'}{' '}
<i
className={
state.messageSent
? 'fas fa-check'
: 'fas fa-caret-right'
}
></i>
</button>
</div>
</form>
</div>
</div>
State updates are not merged with hooks unlike in class components, you need to do so yourself.
Update your state using a functional approach to setState and by spreading the rest of the values within the returned object like
setstate(prev => ({
...prev,
[name]: value,
}));

How do I use a row of custom buttons in a form tag instead of a select drop down menu?

I have a form component which filters data based on the name and category of an asset (ignore the name). I have used the tag to create a dropdown menu but I want to replace this with an array of custom buttons on a seperate row which filter the data when clicked to give assets of that category. Below is the relevant code I have used.
<div className="row">
<div className="col">
<div className="content-block">
Reset filter
<h3>
Filter
</h3>
<form onSubmit={ (e) => this.props.onTriggerFilter(e) } autoComplete="off">
<div className="form-row">
<div className="form-group col-md-3">
<label htmlFor="name">Name</label>
<input
type="text"
className="form-control"
id="name"
value={this.props.filterData.name}
placeholder="Eg, PDF asset..."
onChange={({target}) => this.props.onFilterInputChange('name', target.value)}
/>
</div>
<div className="form-group col-md-3">
<label htmlFor="supplier">Category</label>
<select
name="asset_type"
className="form-control"
value={this.props.filterData.asset_type}
onChange={({target}) => this.props.onFilterInputChange('asset_type', target.value, 'select')}
>
<option value="">All</option>
{ this.props.assetTypeOptions.map((o, key) => (
<option key={ key } value={ o.value }>{ o.label }</option>
))}
</select>
</div>
</div>
<button type="submit" style={{visibility:'hidden'}}>Submit</button>
</form>
</div>
</div>
</div>
I want to replace the select field with a row of buttons in the form
<button type="button" class="btn btn-primary btn-rounded waves-effect">Generic</button>
and get the data to filter when one is pressed. How do I do that?

Resources