I am wondering why React is not updating the state after a method onChange is called.
Summary: A simple input element with two float-right icons to display. One icon to display if the length of the input text is 0 while the other if the input text length > 0. But it seems React is updating the state after I enter the second text in my input element.
What I need is:
Display % if length == 0 and display X is length is > 0.
And if the length > 0 then user on click of X set the input text == "" OR input.length = 0.
Problem is: Though I am able to clear the input but the icon % is not displayed.
const onChange = (e: any) => {
//setting user input
if (userInput.length > 0)
setDisplayIcon({ default: "d-none", clear: "d-block" });
else setDisplayIcon({ default: "d-block", clear: "d-none" });
};
const clearText = (e: any) => {
setUserInput("");
};
return (
<label id="default" className={`${displayIcons.default}`}>
%
</label>
<label className={`${displayIcons.clear}`} onClick={clearText}>
X
</label>
);
}
Add setting display icon state:
const clearText = (e: any) => {
setUserInput("");
setDisplayIcon({ default: "d-block", clear: "d-none" });
};
You will not be able to console.log the updated state value inside of the same function. Try placing your console.log outside of the function and you will see that it does in fact update.
"setState is asynchronous call means if synchronous call get called it may not get updated at right time like to know current value of object after update using setState it may not get give current updated value on console. To get some behavior of synchronous need to pass function instead of object to setState." - https://www.geeksforgeeks.org/reactjs-setstate/
Related
I have a <TextField> component from Material UI that's supposed to allow a user to choose a numerical value. The field is for use on a medical product so we cannot have a default value that's displayed; rather, the value is initially an empty string and onInput we update it accordingly. The min/max props don't work as needed when passed to InputProps, so we manually check if the value passed is less than or equal to zero ('-' also evaluates to true here), and if so we reset the value to 1. However, when I try to instead reset the value to an empty string (i.e. clear the input), the actual state value changes, but the text displayed in the TextField component remains the same for some reason. If anyone knows why the text displayed is not clearing and what I could do to fix this, please let me know!
Code below:
<TextField
disabled={!!requirement.id}
type="number"
value={requirement.val}
variant="outlined"
InputProps={{ inputProps: { min: 1, max: 1000000 } }}
placeholder={'Title!'}
onInput={(e) =>
handleRequirementChange(
Number(e.target.value),
index
)}
/>
const handleRequirementChange = (val, index) => {
const requirement = [...requirement]
if (val <= 0) val = 1
requirement[index] = val
setRequirement(requirement)
}
I am new to ReactJS and pairing it with Material UI is really causing me some roadblocks. I have created a reusable search filter component for my data tables and it worked exactly the way I wanted, but now I want to add a button to clear the field and show the unfiltered results, as well as return the InputSearch component back to its default state so it will display the label inside the field again, not up in the field’s border as these Material UI TextFields do then they are focused or have a current value. This is where I am hitting my roadblock. I have tried multiple solutions I found online, like using the inputRef/useCallback method to change the values, but it didn’t seem to work…or maybe I misunderstood and did it wrong. I was also recommended to put my search values to state. As happens with state my searches are now always one render behind (I.E. , results matching ‘US’ for ‘USA’ , ‘USA’ for ‘USAF’, etc…). Then when I run the handleFilterReset function to set the filter values back to an empty string, nothing happens. I just want my search filter to work instantly (like it did before I moved the value to state [commented out]) and be able to be cleared, resetting the table back to its default display.
Can someone please help me figure this out? Suggestions are appreciated, but code snippets are much more helpful since I am really new to React and especially Material UI.
dataTable.js
const [inputValue, setInputValue] = useState('')
const [searchFn, setSearchFn,] = useState({ fn: items => { return items; } });
// Searching Data
const handleSearch = e => {
setInputValue(e.target.value) // value displayed in input field
let query = (e.target.value).toString().toLowerCase();
setSearchFn({
fn: items => {
if (query === "")
return items;
else
return items.filter(x =>
(x.tankName !== null && x.tankName.toLowerCase().includes(query)) ||
(x.dimensions !== null && x.dimensions.toLowerCase().includes(query))
)
}
})
}
// Clearing Filters
const handleFilterReset = () => {
setInputValue('');
setSearchFn({fn: items => {return items;}})
};
// Search and filter Inputs
<div>
<InputSearch
value={inputValue}
onChange={handleSearch}
/>
<Button
text="Reset"
onClick={handleFilterReset}
/>
</div>
InputSearch.js
export default function InputSearch(props) {
const { inputRef, name, value, error=null, onChange, ...other } = props;
return (
<TextField
label="Search..."
name={name}
value={value}
onChange={onChange}
{...other}
{...(error && {error:true, helperText:error})}
>
</TextField>
)
}
You need to pass the value to InputSearch
Heres an example:
https://codesandbox.io/s/morning-brook-durbvd?file=/demo.tsx
React has a pretty good introduction on its site.
https://reactjs.org/docs/components-and-props.html
The code has been updated with a solution to this issue. I created a display value for the input that I passed to state, which was set to a blank string when the reset is pressed as well as passing an unfiltered data set.
My requirement: when pageSize changes (with onShowSizeChange), reset the current to 1, instead of preserving the current (like the default behavior). How to do that with Ant Design Pagination?
E.g: I'm in pageSize=10,current=2, when changing pageSize from 10 to 20, I want current to be reset to 1, instead of still being 2.
My code:
function App() {
const [current, setCurrent] = React.useState(1);
return (
<Pagination
current={current}
onChange={setCurrent}
showSizeChanger={true}
onShowSizeChange={() => setCurrent(1)}
total={1000}
/>
);
}
CodeSandbox
Its a simple issue that gets really annoying with AntD
The onChange fires whenever anything changes in the pagination not only when changing page number. Meaning, when you change the page size, it triggers both onShowSizeChange AND onChange. But onChange has highter priority hence it triggers last. So it sets the page back to the same one.
Now there are multiple fixes to do. For me, personalty, I do not use antd provided page sizer in pagination and i use my own custom made using select. But there are other solutions.
If you use onChange purply only for page changing then there is a simple solution - filter if the page actually changed.
In this case, if you change your page, the provided variable on onChange and your state variable current will not match, but if you change the page size, the page will remain the same with current. This allows to do a simple if check.
onChange={(pageNum) => pageNum !== current && setCurrent(pageNum)}
This way if the page number and the current page number is the same, onChange will not change the page number (to the same one) and trigger a re-render. Its good, because it also saves on performance to not re-trigger same re-renders when nothing changed. This also allows for the onShowSizeChange to kick in without being overridden.
<Pagination
current={current}
onChange={(pageNum) => pageNum !== current && setCurrent(pageNum)}
showSizeChanger={true}
onShowSizeChange={() => setCurrent(1)}
total={1000}
/>
BUT there is an issue. If you are on the last page and you change the page size - the page number will be different, by antD calculations, and hence the if will not prevent it. So the first method would be a better one.
There is, also, a 3rd method, that I can think of. But its a hacky one. Would not use it in a big App. But its not bad. Adding 2 new methods. sizeChangeLogic that is responsible for size change logic code.
const sizeChangeLogic = (current, size) => {
setSize = true;
setCurrent(1);
};
and onChangeLogic method for on change logic code
const onChangeLogic = (current, size) => {
!setSize && setCurrent(current);
};
and also include a new variable let setSize = false; after useState.
NOTE this new variable canNOT be a state. Ill explain why soon.
and the pagination itself:
<Pagination
current={current}
onShowSizeChange={sizeChangeLogic}
onChange={onChangeLogic}
showSizeChanger={true}
total={1000}
/>
The logic here is, since onShowSizeChange triggers first, will set the locla variable setSize to true and set the page num to 1. Then onChange triggers and gets the setSize value that is, rn true, invert it and check if it can change the number if its false then it can. If its true (after a size change) it will not change it. This prevents the previously mentioned bug. After the page number (current) changes, it triggers a re-render that triggers the setSizeto go back tofalse` state hence the cycle continues.
The reason you can NOT have setSize as a state is because the state value changes AFTER a re-render. So the value from false to true would not change as both onchange methods would be ran before a re-render.
Heres a 3rd method full code:
function App() {
const [current, setCurrent] = React.useState(1);
let setSize = false;
const sizeChangeLogic = (current, size) => {
setSize = true;
setCurrent(1);
};
const onChangeLogic = (current, size) => {
!setSize && setCurrent(current);
};
return (
<Pagination
current={current}
onShowSizeChange={sizeChangeLogic}
onChange={onChangeLogic}
showSizeChanger={true}
total={1000}
/>
);
}
Choose what fits you best.
Many thanks to all the answerers, all are very insightful and really helped me to solve the problem. Cause my real code involves storing pageSize as a state as well, here's my final solution based on their answers (which will get rid of onShowSizeChange altogether):
function App() {
const [current, setCurrent] = React.useState(1);
const pageSizeRef = React.useRef(10);
return (
<Pagination
current={current}
defaultPageSize={pageSizeRef.current}
onChange={(newCurrent, newPageSize) => {
const pageSizeChange = pageSizeRef.current !== newPageSize;
if (pageSizeChange) {
setCurrent(1);
} else {
setCurrent(newCurrent);
}
pageSizeRef.current = newPageSize;
}}
showSizeChanger={true}
total={1000}
/>
);
}
Re-explanation:
Look at the internal implementation, when changePageSize, it'll call props.onShowSizeChange and props.onChange with the order of props.onShowSizeChange first and props.onChange later. And I had setCurrent inside props.onChange as well so it overrode the setCurrent(1) in props.onShowSizeChange. So the possible solutions are:
Make the setCurrent inside props.onShowSizeChange being fired after with setTimeout (thanks #nearhuscarl)
Ignore handling for pageSize changes inside onChange and only handle it in onShowSizeChange (thanks #lith)
Handle pageSize changes inside onChange instead of handling it in onShowSizeChange (what I ended up with)
This is what happens inside Pagination when the pageSize value is changed:
this.props.onShowSizeChange(current, size);
if ('onChange' in this.props && this.props.onChange) {
this.props.onChange(current, size);
}
Here is a picture to illustrate what happens:
Because onChange callback notifies when the value of either the current page or pageSize changes, you can control the pageSize value and only reset the current page if the pageSize is different in the last render:
const [current, setCurrent] = React.useState(1);
const [pageSize, setPageSize] = React.useState(10);
console.log({ current });
return (
<Pagination
current={current}
pageSize={pageSize}
onChange={(newPage, newPageSize) => {
// react batches all setState calls internally so this only costs 1 render
setPageSize(newPageSize);
setCurrent(pageSize !== newPageSize ? 1 : newPage);
}}
showSizeChanger={true}
total={1000}
/>
);
I want to be able to display a ul tag with all the fields missing a value.
I want to check if the question, category, and gender are empty. As well as the consent is false.
Then I want to display a message like this
<h2>missing fields</h2>
<ul>
<li>question</li/>
<li>category</li/>
<li>gender</li/>
<li>consent</li/>
</ul>
Right now I have these functions:
let handleInputChange = event => {
const target = event.target;
const value = target.value;
const name = target.name;
setValues({
...values,
[name]: value,
});
};
let handleCheckboxChange = event => {
const target = event.target;
const value = target.checked;
const name = target.name;
setValues({
...values,
[name]: value,
});
};
If for example the consent is set to true, then it should not appear when submitting the form
I think the following should work for you :-
<h2>missing fields</h2>
<ul>
{Object.keys(values).filter(key=>key==='')).map(val=><li>val</li>)}
{!values.consent && <li>consent</li>}
</ul>
Here we are getting all the keys of your values state in an array and filtering out only the ones which are equal to '' and then map over them to display as li elements.
For consent we can explicitly handle it using && i.e. only render this li element when consent is false.
Or you can also have one dedicated function like renderMissingFields where if you expect your fields to increase overtime you can write some custom logic like above but for this particular use case, above should suffice.
Also you can follow the rendering approach for other fields (for which I used Object.keys) same as consent.
In case you want all of them to fit the constraints as defined by you and only then render , you can do something like this :-
{!(question!=='' || gender!=='' || category!=='' || consent) &&
(<h2>missing fields</h2>
<ul>
<li>question</li/>
<li>category</li/>
<li>gender</li/>
<li>consent</li/>
</ul>
)
}
I have a React Native form that allows me to add an Input UI in the form, by clicking a button with this function. This allow me to generate it on the fly. The code for that is this.
addClick() {
this.setState(prevState => ({ values: [...prevState.values, ""] }));
console.log(this.values[0].name);
}
That part works well, but I'm having a problem extracting the data from the dynamic inputs, and add it to an array. So far I have tried this
setVal = value => {
const values = this.state.values[0];
if (values[0].name === "" || values[0].description === "") return;
[...this.state.values, value];
this.setState(values);
console.log(values);
};
How do I organize my states properly so that I can add as many inputs I need, and when I'm finished, I can update the state, and access the new data in my list component?
How do I update my state to the new Array? at the moment, this.state only shows the initial state set at the top.
I'm missing a few things
Please take a look at the full code sandbox HERE so you can see:
See...your created isssue is not so obvious we need to see where you call setVal() function but....
i think you will be more comfortable if you render your <input/> s directly from your state array, not from const x = [] variant. because it seems like you want a dynamic view and in such a cases you will need to bind your loop quantity from state. so:
this.state = {
x: [0,1,2,3,4]
}
and inside your render :
{this.state.x.map(x => {
return (
<TextInput
placeholder={`name${x}`}
value={values[x.toString()]}
handleBlur={() => handleBlur(x.toString())}
onChangeText={handleChange(x.toString())}
style={styles.input}
/>
);
})}