How to prevent a double click in ReactJS - javascript

I am working on search filter in ReactJS and I face a problem. Problem is about when User does some search and wants to click on next (because I have pagination in app) then the User will click twice to load other pages of data.
I need to avoid this behaviour: I mean when user does single click on next it should be "load a data" instead of "double click".
I am new to ReactJS, please expert help me
Code
btnClick() {
const { Item,skip , filtered, data } = this.state
if(filtered.length>0 || !data.length ){
window.alert("Hello,")
this.setState({
filtered:[],
skip:0
},()=>this.getData());
return false
}else {
window.alert("Hello , Dear")
this.setState(
{
Item,
skip: skip + pageSize
},
() => this.getData()
);}
}

You can have a isLoading state and set the disabled prop on the button when in this state which will not allow the button to be clicked again while the data is being fetched.
btnClick() {
const {
Item,
skip,
filtered,
data
} = this.state;
if (filtered.length > 0 || !data.length) {
window.alert("Hello,")
this.setState({
filtered: [],
skip: 0
}, () => this.fetchData());
return false
} else {
window.alert("Hello , Dear")
this.setState({
Item,
skip: skip + pageSize
},
() => this.fetchData()
);
}
}
fetchData() = async() => {
this.setState({ isLoading: true });
await this.getData();
this.setState({ isLoading: false });
}
render() {
const {
isLoading
} = this.state;
const buttonProps = isLoading ? { disabled: true} ? {};
return (
<button onClick={this.btnClick} { ...buttonProps }>
Click to fetch
</button>
);
}

Related

How and when to check if all object values in an array are not null?

I have a React component with multiple dynamic input fields with different data types. I want to save the input values in state (answers) like this:
{ [id]: value }
Example of possible data output:
[
{
'72ebbdc4-8001-4b53-aac0': 'John doe'
},
{
'dd3179c1-90bc-481c-a89e':
'5b6d2f55-8ed0-4f76-98e69'
},
{
'5acff3c7-02f8-4555-9232': 4
},
{
'877817a8-6890-464b-928e': false
},
{
'69e11e5a-613f-46ac-805d': []
},
{
'0bb9c2f3-eda7-4e96-90f6': [
'ad9d4c72-0972764cf9b71c42',
'da788b55-3b68-a9c669c0ec1a'
]
},
{
'e9c2196f-871f-25e6efb2551f': '2020-12-23'
},
];
My React component is as follows. The InputField is a switch based on the questions type. When an input changes updateState is called and this.state.answers is updated. All of the question need to be filled in before the users can navigate to the next screen -> this.state.answeredAllQuestions.
export default class EditComponent extends Component {
state = {
questions: [],
answers: [],
answeredAllQuestions: false
};
async componentDidMount() {
await this.fillQuestions();
}
// I think need a working alternative for this part
componentDidUpdate() {
if (!this.state.answeredAllQuestions) {
this.checkRequiredQuestions();
}
}
fillQuestions = async () => {
const {
response: { questions }
} = await getQuestions();
// Turn questions from api into answers -> [key]:value
const answers = questions.map(el => {
return { [el.uuid]: el.value };
});
this.setState({
questions,
answers
});
};
checkRequiredQuestions = async () => {
const { answers } = this.state;
if (answers) {
const values = answers.map(x => Object.values(x)[0]);
if (
values.every(answer => {
(answer.required && answer !== null) || answer !== '';
})
) {
this.setState({ answeredAllQuestions: true });
} else {
this.setState({ answeredAllQuestions: false });
}
}
};
updateState = (value, id, nestedId) => {
const { answers } = this.state;
if (answers) {
// Check if answer already exists in the state, if so then replace it
this.setState({
answers: this.state.answers.map(el =>
Object.keys(el)[0] === id ? { [id]: value } : el
)
});
} else {
this.setState({
answers: [{ [id]: value }]
});
}
};
render() {
const { questions, answers } = this.state;
return (
<View>
<FlatList
data={questions}
renderItem={({ item: question }) => (
<View key={question.id}>
<Text>{question.label}</Text>
<InputField
type={question.type}
answers={answers}
updateState={this.updateState}
question={question}
/>
</View>
)}
/>
</View>
);
}
}
The big problem I have with this code is that when all input fields are filled in, the this.state.answeredAllQuestions is set too true. But when the user then removes a value from an input field it won't update back to false.
I don't expect someone to fix my code, but I could really use some help at the moment.
if (values.every(answer =>
(answer.required && (answer !== null || answer !== '' )) || answer === ''))
If the answer is required you need to check if isn't an empty string.
Fixed by passing a HandleInput function to all the inputs components, that checks for every data type if true or false and puts this value in the state of the EditComponent.

how to stop a timer in setInterval by click then resume by click again?

I am new to react, I am trying to write a react component, component has several features.
user can input a random number, then number will be displayed in the
page too.
implement a button with text value 'start', once click the button,
the number value displayed will reduce one every 1second and the
text value will become 'stop'.
continue click button, minus one will stop and text value of button
will become back to 'start'.
when number subtract to 0 will automatically stop itself.
I have implemented the first and second feature. but when I try to click stop to stop number from reducing 1, it does not work.
I am wondering since I used type=true/false to indicate the state of type is start or stop. Because in the start state, number should automatically reduce 1. And on the stop state, reducing 1 should stop. So, the timer function should accurate according to the state of type.
Also I am not sure if I used clearInterval method right.
I really appreciate if someone could give me a hand.
code is here:
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [{ id: 1, number: "" }],
type: false
};
this.handleClick = this.handleClick.bind(this);
}
changeNumber = (e, target) => {
this.setState({
details: this.state.details.map(detail => {
if (detail.id === target.id) {
detail.number = e.target.value;
}
return detail;
})
});
};
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
var myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
} else if (this.state.type === true) {
clearInterval(myTimer);
}
};
render() {
return (
<div>
{this.state.details.map(detail => {
return (
<div key={detail.id}>
Number:{detail.number}
<input
type="number"
onChange={e => this.changeNumber(e, detail)}
value={detail.number}
/>
<input
type="button"
onClick={() => this.handleClick()}
value={this.state.type ? "stop" : "start"}
/>
</div>
);
})}
</div>
);
}
}
export default App;
You need to declare var myTimer outside of the handleClick() function.
So it's something like:
var myTimer;
...
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
} else if (this.state.type === true) {
clearInterval(myTimer);
}
};

How to handle/select all checkboxes - ReactJS

I'm trying to handle my select all function , but at the moment i got some issues .I'm trying to fill an object with all check boxes . Can somebody give me a hand ?
This is my function to handle single checkbox :
constructor(props) {
super(props);
this.state = { docList:{} }
handleCheckboxClick = (e) => {
let parsedVal = JSON.parse(e.target.value);
let newDocList = { ...this.state.docList };
if (e.target.checked) {
newDocList[parsedVal.documentId] = parsedVal.documentNumber;
} else {
delete newDocList[parsedVal.documentId];
}
this.setState({
docList: newDocList,
}, () => {
console.log(this.state.docList)
});
};
The render :
<MaterialTable options={{
showSelectAllCheckbox:false,
selection: true,
selectionProps: rowData => ({
onClick: (event , rowData) => this.handleCheckboxClick(event,rowData),
value: JSON.stringify({ documentId: rowData.documentId, documentNumber: rowData.documentNumber })
}),
And this is handle select all :
handleAllCheckboxes = (e) => {
if(e.target.value){
this.setState(state=> ({selected: state.data.map(rowData=> rowData.documentId)
}))
console.log(this.state.selected)
return;
}
this.setState({ selected: [] });
}
And the render :
<Checkbox
onClick={this.handleAllCheckboxes}
indeterminate
/> Select All
Included an example of select all or deselect all. Alternatively, click one at a time :)
https://codesandbox.io/s/select-deselect-checkboxes-jugl2

How to re-render another component after setState in ReactTable getTdProps onClick event?

I have two react-table tables on a page. The first shows a list of saved queries. A user can click on any of the saved queries to run the query results, which are rendered in a second table. I'm trying to show / hide the query results table based on the boolean state of "showQueryResults".
When I setState of showQueryResults to false from within the getTDProps onClick event handler, nothing happens. When I put a test button elsewhere on the page which does the exact same thing, the query results table is successfully hidden.
<ReactTable
data={this.props.data}
columns={columns}
getTdProps={(state, rowInfo, column) => {
return {
onClick: () => {
if (rowInfo){
const row = rowInfo.row
this.setState({selectedListId: row.id, showQueryResults: false},() => {
if(column.id !== 'delete'){
if (row.search_type === 'dynamic'){
this.props.fetchQueryResults(row._original.search_criteria)
.then(this.setState({showQueryResults: true}))
} else {
this.props.fetchStaticResults(row.id)
.then(this.setState({showQueryResults: true}))
}
}
});
}
},
}}}
/>
In my main render function, I'm conditionally rendering the query results as follows:
{
this.state.showQueryResults
? <ListResults
queryResults = {this.props.queryResults}
onModalToggle = {this.handleModalToggle.bind(this)}
showSave = {false}
/>
: null
}
And like I said, the following test button successfully hides the above element:
<button onClick={() => this.setState({showQueryResults: !this.state.showQueryResults})}>Toggle</button>
Any thoughts?
EDIT: I've also tried the following to try to make sure the fetchQueryResults completes before the second setState (per the comments) but this is doing the exact same thing:
<ReactTable
data={this.props.data}
columns={columns}
getTdProps={(state, rowInfo, column) => {
return {
onClick: () => {
if (rowInfo){
const row = rowInfo.row
const handleFetchQuery = (searchCriteria) => {
return new Promise((resolve)=>{
let response = this.props.fetchQueryResults(searchCriteria)
if (response){
resolve('Success')
}
})
}
this.setState({selectedListId: row.id, showQueryResults: false},() => {
if(column.id !== 'delete'){
if (row.search_type === 'dynamic'){
handleFetchQuery(row._original.search_criteria)
.then(() => {this.setState({showQueryResults: true})})
} else {
this.props.fetchStaticResults(row.id)
.then(this.setState({showQueryResults: true}))
}
}
});
}
},
}}}
After this setState
this.setState({selectedListId: row.id, showQueryResults: false}
you trigger another setState in the callback
if(column.id !== 'delete'){
if (row.search_type === 'dynamic'){
this.props.fetchQueryResults(row._original.search_criteria)
.then(this.setState({showQueryResults: true}))
} else {
this.props.fetchStaticResults(row.id)
.then(this.setState({showQueryResults: true}))
}
}
so the showQueryResults is true, then your condition in
{
this.state.showQueryResults
? <ListResults
queryResults = {this.props.queryResults}
onModalToggle = {this.handleModalToggle.bind(this)}
showSave = {false}
/>
: null
}
will always render the true condition. Meaning your table will always be visible.
So I've gotten this to work in the following way using async/await:
<ReactTable
data={this.props.data}
columns={columns}
getTdProps={(state, rowInfo, column) => {
return {
onClick: () => {
if (rowInfo){
const row = rowInfo.row
const handleFetchQuery = async (searchCriteria) => {
await this.props.fetchQueryResults(searchCriteria)
.then(success => {this.setState({showQueryResults: !this.state.showQueryResults})
})}
const handleFetchStatic = async (listId) => {
await this.props.fetchStaticResults(listId)
.then(success => {this.setState({showQueryResults: !this.state.showQueryResults})
})}
this.setState({selectedListId: row.id, showQueryResults: false},() => {
if(column.id !== 'delete'){
if (row.search_type === 'dynamic'){
try {
handleFetchQuery(row._original.search_criteria)
} catch (e) {
console.log(e)
}
} else {
try {
handleFetchStatic(row.id)
} catch (e) {
console.log(e)
}
}
}
});
}
},
}}}
/>
Still not sure why it wasn't working the previous ways, but I'll take it! And hopefully this helps someone else in the future.

Search bar to filter table results in React with timeout

I'm using Semantic React UI Search to filter results of a data table component in React. The table should display all data if search is empty, and display no data or the matching results if search is not empty. My issue is there's always a quick flash of "No data" while you're doing a search.
The original Search code displayed the results as a dropdown, but I modified it to modify the table. Code is below.
class Page extends Component {
resetComponent = () => this.setState({ isLoading: false, results: [], value: '' })
handleSearchChange = (e, { value }) => {
this.setState({ isLoading: true, value })
setTimeout(() => {
if (this.state.value.length < 1) return this.resetComponent()
const re = new RegExp(_.escapeRegExp(this.state.value), 'i')
const isMatch = result => re.test(result.name)
this.setState({
isLoading: false,
results: _.filter(this.props.users, isMatch),
})
}, 200)
}
render() {
const { users } = this.props
const { value, results } = this.state
const dataToShow = _.isEmpty(results) && !value ? users : results
return (
<Container>
<Search
open={false}
loading={isLoading}
onSearchChange={_.debounce(this.handleSearchChange, 500, { leading: true })}
value={value}
{...this.props}
/>
<Table data={dataToShow} />
</Container>
)
}
}
I think the const dataToShow = _.isEmpty(results) && !value ? users : results line is what causes it to flash, but I don't know how else to display no results if no match, or all results if empty.
How can I get this timeout/debounce to work properly on the table?
If I do <Table data={results} /> the debounce works, but the table does not display all data on initial load.
What is actually happening is when you set the this.setState({ isLoading: true, value }) the component will re-render since you changed the state. When this happens this line:
const dataToShow = _.isEmpty(results) && !value ? users : results
would actually show the results - since although the results are empty you do have a value typed. Which is why you get the 'No Data` since you bind to results but they are empty.
Try this there:
const dataToShow = _.isEmpty(results) && !value ? users : this.state.isLoading ? users : results
It should continue to show the users when there is value typed and once you are done loading it should change to the results.
The issue however is (which is why I suggested the easy way out with the spinner) that now you would show results ... then on new search you would go back to the users then go again to the results when done loading.
I would not display at all the <Table> while this.state.isLoading is true and display some "spinner" if it is ... for example:
class Page extends Component {
resetComponent = () => this.setState({ isLoading: false, results: [], value: '' })
handleSearchChange = (e, { value }) => {
setTimeout(() => {
this.setState({ isLoading: true, value })
if (this.state.value.length < 1) return this.resetComponent()
const re = new RegExp(_.escapeRegExp(this.state.value), 'i')
const isMatch = result => re.test(result.name)
this.setState({
isLoading: false,
results: _.filter(this.props.users, isMatch),
})
}, 200)
}
render() {
const { users } = this.props
const { value, results } = this.state
const dataToShow = _.isEmpty(results) && !value ? users : results
return (
<Container>
<Search
open={false}
loading={isLoading}
onSearchChange={_.debounce(this.handleSearchChange, 500, { leading: true })}
value={value}
{...this.props}
/>
{this.state.isLoading && <Spinner />}
{!this.state.isLoading && <Table data={dataToShow} />}
</Container>
)
}
}
But since we disagree on that UX pattern here is another suggestion:
Keep track of the previous results and keep showing them until the new state change happens with the new results:
class Page extends Component {
constructor (props) {
super(props)
this.state = {
isLoading: false,
results: [],
oldResults: this.prop.users || [],
value: ''
}
}
resetComponent = () => this.setState({ isLoading: false, results: [], oldResults: this.prop.users || [], value: '' })
handleSearchChange = (e, { value }) => {
setTimeout(() => {
this.setState({ isLoading: true, value })
if (this.state.value.length < 1) return this.resetComponent()
const re = new RegExp(_.escapeRegExp(this.state.value), 'i')
const filteredResults = _.filter(this.props.users, result => re.test(result.name))
this.setState({
isLoading: false,
results: filteredResults,
oldResults: filteredResults
})
}, 200)
}
render() {
const { users } = this.props
const { value, results } = this.state
const dataToShow = (_.isEmpty(results) && !value) || this.state.isLoading ? oldResults : results
return (
<Container>
<Search
open={false}
loading={isLoading}
onSearchChange={_.debounce(this.handleSearchChange, 500, { leading: true })}
value={value}
{...this.props}
/>
<Table data={dataToShow} />
</Container>
)
}
}

Categories

Resources