How to stop rendering every keystroke input field onChange in React - reactjs

Is there a better way to stop rendering every keystroke input field onChange in React... I noted that if I changed the value to onBlur() on input field, however it doesn't dispatch AddReservation function the second part to clear the input field (setReservationCardInput('')).
Or I cannot stop rendering onChange due to setReservationCardInput update reservationCardInput with useState() function?
My application is below, appreciate your feedback, thank you!
import React, {useState} from 'react'
import {useSelector, useDispatch} from 'react-redux'
import ReservationCard from '../../components/ReservationCard'
import {addReservation} from '../reservation/reservationsSlice'
const ReservationsList = () => {
const reservations = useSelector(state => state.reservations.value)
const [reservationCardInput, setReservationCardInput] = useState('')
const dispatch = useDispatch()
const inputOnChange = (e) => {
setReservationCardInput(e.target.value)
}
console.log('reservations:', reservationCardInput)
const AddReservation =() => {
if(!reservationCardInput) return
dispatch(addReservation(reservationCardInput))
setReservationCardInput('')
}
return (
<div className="reservation-cards-container">
{
reservations.map((name, index) => {
return (
<ReservationCard name={name} key={index}/>
)
})
}
<div className="reservation-input-container">
<input value={reservationCardInput} onChange={inputOnChange}/>
<button onClick={AddReservation}>Add Customer</button>
</div>
</div>
)
}
export default ReservationsList

Related

useEffect fails on page refresh

I am an infant programmer and I am trying to fetch an api and style the results using React. My page works fine on the initial load and subsequent saves on VScode,but when I actually refresh the page from the browser I get the error thats posted on imageenter image description here:
Here is my code: App.js
```import React, { useEffect, useState } from 'react';
import './App.css';
import Students from './components/Students';
import styled from 'styled-components';
function App() {
const [studentInfo, setStudentInfo] = useState({});
const [searchResult, setSearchResult] = useState({});
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
getStudents();
}, []);
useEffect(() => {
getStudents();
console.log('useEffect');
}, [searchTerm]);
const getStudents = async () => {
const url = 'https://api.hatchways.io/assessment/students';
console.log(url);
fetch(url)
.then((res) => res.json())
.then((data) => {
console.log(data);
searchTerm != ''
? setStudentInfo(filterStudents(data.students))
: setStudentInfo(data.students);
});
};
const filterStudents = (studentsArray) => {
return studentsArray.filter((info) => {
return (
info.firstName.toLowerCase().includes(searchTerm) ||
info.lastName.toLowerCase().includes(searchTerm)
);
});
};
console.log(searchTerm);
return (
<div className="App">
<Students
studentInfo={studentInfo}
setSearchTerm={setSearchTerm}
searchTerm={searchTerm}
/>
</div>
);
}
export default App;```
here is my component Students.js:
```import React, { useState } from 'react';
import styled from 'styled-components';
import GradeDetails from './GradeDetails';
const Students = ({ studentInfo, searchTerm, setSearchTerm }) => {
console.log(typeof studentInfo);
console.log(studentInfo[0]);
const [isCollapsed, setIsCollapsed] = useState(false);
const handleDetails = () => {
setIsCollapsed(!isCollapsed);
};
const average = (arr) => {
let sum = 0;
arr.map((num) => {
sum = sum + parseInt(num);
});
return sum / arr.length.toFixed(3);
};
console.log(isCollapsed);
return (
<Container>
<Input
type="text"
value={searchTerm}
placeholder="Search by name"
onChange={(e) => setSearchTerm(e.target.value.toLowerCase())}
/>
{studentInfo?.map((student) => (
<Wrapper key={student.id}>
<ImageContainer>
<Image src={student.pic}></Image>
</ImageContainer>
<ContentContainer>
<Name>
{student.firstName} {student.lastName}{' '}
</Name>
<Email>Email: {student.email}</Email>
<Company>Company: {student.company}</Company>
<Skills>Skill: {student.skill}</Skills>
<Average>Average:{average(student.grades)}%</Average>
</ContentContainer>
<ButtonContainer>
<Button onClick={handleDetails}>+</Button>
</ButtonContainer>
{isCollapsed && <GradeDetails studentInfo={studentInfo} />}
</Wrapper>
))}
</Container>
);
};```
Every time I have the error, I comment out the codes in Students.js starting from studentInfo.map until the and save and then uncomment it and save and everything works fine again.
I am hoping someone can help me make this work every time so that I don't have to sit at the edge of my seat all the time. Thank you and I apologize for the long question.
You are using an empty object as the initial state for studentInfo (the value passed to useState hook will be used as the default value - docs):
const [studentInfo, setStudentInfo] = useState({});
.map is only supported on Arrays. So this is failing when the component is rendering before the useEffect has completed and updated the value of studentInfo from an object, to an array. Try swapping your initial state to be an array instead:
const [studentInfo, setStudentInfo] = useState([]);

Cannot close an antd modal using reactJS library

I need your helps, when I do click those 3 buttons on the red circles, It threw an error that's called '... is not a function' though I think closing a modal needs an useState hook which set boolean to close it. Am I wrong? If I'm not wrong so how to solve it, please help me!~~~
Images show error 1
Images show error 2
Here is my code
import {createContext, useState, useContext, useMemo} from 'react'
import { AuthContext } from './AuthProvider'
import useFirestore from '../hooks/useFirestore'
export const AppContext = createContext()
function AppProvider ({children}) {
const [isAddRoomVisible, setIsAddRoomVisible] = useState(false)
const user = useContext(AuthContext)
const {uid} = user
const roomsCondition = useMemo(() => {
return {
fieldName: 'members',
operator: 'array-contains',
value: uid
}
}, [uid])
const rooms = useFirestore('rooms', roomsCondition)
return (
<AppContext.Provider value={[rooms, isAddRoomVisible, setIsAddRoomVisible]}>
{children}
</AppContext.Provider>
)
}
export default AppProvider
import {Modal, Form, Input} from 'antd'
import { useState, useContext } from 'react'
import { AppContext } from '../../Context/AppProvider'
import { AuthContext } from '../../Context/AuthProvider'
import { addDocument } from '../../firebase/service'
export default function AddRoomModal() {
const [isAddRoomVisible, setIsAddRoomVisible] = useContext(AppContext)
const user = useContext(AuthContext)
const {uid} = user;
const [form] = Form.useForm()
const handleOk = () => {
// console.log({
// formData: form.getFieldsValue()
// })
addDocument('rooms', {...form.getFieldsValue(), members: [uid]})
setIsAddRoomVisible(false)
}
const handleCancel = () => {
setIsAddRoomVisible(false)
}
return (
<div>
<Modal
title="Create room"
visible={isAddRoomVisible}
onOk={handleOk}
okCancel={handleCancel}
>
<Form form={form} layout="vertical">
<Form.Item label="Room's name" name="name">
<Input placeholder="Enter room's name here"/>
</Form.Item>
<Form.Item label="Description" name="description">
<Input.TextArea placeholder="Enter description"/>
</Form.Item>
</Form>
</Modal>
</div>
)
}
shouldn't the first row of AddRoomModal be :
const [room, isAddRoomVisible, setIsAddRoomVisible] = useContext(AppContext)
You are destructuring an array not an object, so you cannot skip any element of the value prop of your context
EDIT :
Ok so i'll try to give you more info.
The first thing I notice is you are using a difficult way to handle your modal using a context. it would be easier to include your component in another that will contain the opening button and the modal open state :
const ModalParent = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<>
<button onClick={() => setIsModalOpen(true)}>openModal</button>
<Modal
title="Create room"
visible={isModalOpen}
onOk={(e) => {
setIsModalOpen(false);
}}
okCancel={(e) => {
setIsModalOpen(false);
}}
>
Hi my Modal !
</Modal>
</>
);
};
But you can still use the context approach, it allows you to control your modal from anywhere in your app, which can be pretty useful.
When you are using your context provider, you are passing it an array containing three values :
<AppContext.Provider value={[rooms, isAddRoomVisible, setIsAddRoomVisible]}>
When you are using the useContext hook, you are retrieving this array in your component.
const myAppContextValues = useContext(AppContext)
const rooms = myAppContextValues[0]
const isAddRoomVisible = myAppContextValues[1]
const setIsAddRoomVisible= myAppContextValues[2]
In you're case, you are using a syntax to define more quickly variables from an array called destructuring https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment .
But you are skipping an element of the array.
So if we translate your code in vanilla JS you are actually doing :
// Here you are retrieving the element 0 & 1 and putting them in variable isAddRoomVisible, setIsAddRoomVisible
// const [isAddRoomVisible, setIsAddRoomVisible] = useContext(AppContext)
// Under is the vanilla JS example
const myAppContextValues = useContext(AppContext)
const isAddRoomVisible = myAppContextValues[0]
const setIsAddRoomVisible= myAppContextValues[1]
So what you need to do is to also assign the room element, even if you don't use it
const [room, isAddRoomVisible, setIsAddRoomVisible] = useContext(AppContext)
You could also use an object as value of your context and destructuring it with the object syntax :
// notice that here i'm using the shorthand syntax to create an object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#new_notations_in_ecmascript_2015
<AppContext.Provider value={{rooms, isAddRoomVisible, setIsAddRoomVisible}}>
// and here i'm using the object destructuring
const {isAddRoomVisible, setIsAddRoomVisible} = useContext(AppContext)
Hope it helps you and it is clearer !

onClickHandler sometimes work, sometimes not - React

The onClickHandler in the following code, in this component, 'SearchResult', sometimes work and sometimes not.
I can't figure out any logic that can explain why it works when it works, and why it's not working, when it's not working.
I've put a debugger inside the onClickHandler, at the beginning of it, and when it's not working, it doesn't get to the debugger at all - what indicates that the function sometimes isn't even called, and I can't figure out why.
Furthermore, I've tried to move all the code in function to the onClick, inline, but then, it's not working at all.
In addition, I've tried to use a function declaration instead of an arrow function, and it still behaves the same - sometimes it works, and sometimes it's not...
This is the site, you can see the behavior for yourself, in the search box.
This is the GitHub repository
Here you can see a video demonstrating how it's not working, except for one time it did work
Please help.
The problematic component:
import { useDispatch } from 'react-redux'
import { Col } from 'react-bootstrap'
import { getWeatherRequest } from '../redux/weather/weatherActions'
import { GENERAL_RESET } from '../redux/general/generalConstants'
const SearchResult = ({ Key, LocalizedName, setText }) => {
const dispatch = useDispatch()
const onClickHandler = () => {
dispatch({ type: GENERAL_RESET })
dispatch(
getWeatherRequest({
location: Key,
cityName: LocalizedName,
})
)
setText('')
}
return (
<Col className='suggestion' onClick={onClickHandler}>
{LocalizedName}
</Col>
)
}
export default SearchResult
This is the parent component:
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Form } from 'react-bootstrap'
import { getAutoCompleteResultsRequest } from '../redux/autoComplete/autoCompleteActions'
import { AUTO_COMPLETE_RESET } from '../redux/autoComplete/autoCompleteConstants'
import SearchResult from './SearchResult'
const SearchBox = () => {
const [text, setText] = useState('')
const dispatch = useDispatch()
const autoComplete = useSelector((state) => state.autoComplete)
const { results } = autoComplete
const onChangeHandler = (e) => {
if (e.target.value === '') {
dispatch({ type: AUTO_COMPLETE_RESET })
setText('')
}
setText(e.target.value)
dispatch(getAutoCompleteResultsRequest(e.target.value))
}
const onBlurHandler = () => {
setTimeout(() => {
dispatch({ type: AUTO_COMPLETE_RESET })
setText('')
}, 100)
}
return (
<div className='search-box'>
<Form inline>
<div className='input-group search-md search-sm'>
<input
type='search'
name='q'
value={text}
onChange={onChangeHandler}
onBlur={onBlurHandler}
placeholder='Search Location...'
className='mr-sm-2 ml-sm-3 form-control'
/>
</div>
</Form>
<div className='search-results'>
{results &&
results.map((result) => {
return (
<SearchResult key={result.Key} {...result} setText={setText} />
)
})}
</div>
</div>
)
}
export default SearchBox
I played a bit with your code and it looks like a possible solution may be the following addition in the SearchResult.js:
const onClickHandler = (e) => {
e.preventDefault();
...
After some tests
Please remove the onBlurHandler. It seams to fire ahaed of the onClickHandler of the result.
Can you put console.log(e.target.value) inside the onChangeHandler,
press again search results and make sure that one of it doesn't working and show us the console.
In searchResult component print to the console LocalizedName as well

Ternary condition on onclick event

I am trying to create a to-do application in React. Code I have so far adds to-do items to the to-do list. When I click on the edit icon I had put turnery condition for the done icon but it's not working. Can someone explain what is wrong with my code?
App.js
import './App.css';
import React, { useState } from 'react';
import TodoList from './TodoList';
import { v4 as uuidv4 } from 'uuid';
function App() {
// const [input, setInput] = useState('');
const [todos, setTodo] = useState([]);
const input = React.useRef();
const addTodo = (e) => {
e.preventDefault();
const id = uuidv4();
setTodo([...todos, { id: id, text: input.current.value }])
input.current.value='';
}
const deleteTodo = (id) => {
setTodo(todos.filter(todo => todo.id !== id));
}
const editTodo = (id) => {
}
return (
<div className="App">
<form>
<input type="text" ref={input}/>
<button type="submit" onClick={addTodo}>Enter</button>
</form>
<TodoList todos={todos} deleteTodo={deleteTodo} editTodo={editTodo}/>
</div>
);
}
export default App;
TodoItem.js
import React from 'react'
import DeleteIcon from '#material-ui/icons/Delete';
import EditIcon from '#material-ui/icons/Edit';
import CheckBoxOutlineBlankIcon from '#material-ui/icons/CheckBoxOutlineBlank';
import DoneIcon from '#material-ui/icons/Done';
const TodoItem = ({todo, deleteTodo, editTodo}) => {
return (
<>
<div>
<CheckBoxOutlineBlankIcon/>
<input type="text" value={todo.text} readOnly={true}/>
</div>
<div>
{ <EditIcon/> ? <EditIcon onClick={editTodo}/> : <DoneIcon/> }
<DeleteIcon onClick={deleteTodo}/>
</div>
</>
)
}
export default TodoItem
There are a few problems with your code. One, also pointed out by Abu Sufian is that your ternary operator will always trigger whatever is immediately after ?, because <EditIcon/> is just a component and will always be true.
But more fundamentally, to do what you want, you will need to add another properly to your todo list, say, status. So when a task goes in for the first time, it will be in pending status, then once you click your Edit icon, it will change to done. And that's how we will toggle that icon with a ternary operator.
So I would change your addTodo function to
const addTodo = (e) => {
e.preventDefault();
const id = uuidv4();
setTodo([
...todos,
{ id: id, text: input.current.value, status: "pending" }
]);
input.current.value = "";
};
Then I would change your editTodo to:
const editTodo = (id) => {
console.log(id);
setTodo(
todos.map((todo) => {
if (todo.id === id) todo.status = "done";
return todo;
})
);
};
And finally, I would change your ternary part to:
{todo.status === "pending" ? (
<EditIcon onClick={() => editTodo(todo.id)} />
) : (
<DoneIcon />
)}
Here is a complete Sandbox for you. Sorry I don't have your CSS so I can't make it look super pretty.
Maybe you are looking for something like this.
{ !todo.done ? <EditIcon onClick={editTodo}/> : <DoneIcon/> }
I believe checking whether a todo item is done or not should happen with a property of todo object itself.
In a ternary you need to start with a condition.
condition ? do something when true : do something when false
So you have to have a condition in the first place. In your case EditIcon is not a condition.
If you are looking for a way to mark a todo as completed so you need to do more things.
const markAsCompleted = id => {
const todo = todos.find(todo => todo.id !== id);
setTodo([...todos, {...todo, done: true }]);
}
Then you can decide based on whether a todo is done or not.

lodash debounce in React functional component not working

I have a functional component built around the React Table component that uses the Apollo GraphQL client for server-side pagination and searching. I am trying to implement debouncing for the searching so that only one query is executed against the server once the user stops typing with that value. I have tried the lodash debounce and awesome debounce promise solutions but still a query gets executed against the server for every character typed in the search field.
Here is my component (with irrelevant info redacted):
import React, {useEffect, useState} from 'react';
import ReactTable from "react-table";
import _ from 'lodash';
import classnames from 'classnames';
import "react-table/react-table.css";
import PaginationComponent from "./PaginationComponent";
import LoadingComponent from "./LoadingComponent";
import {Button, Icon} from "../../elements";
import PropTypes from 'prop-types';
import Card from "../card/Card";
import './data-table.css';
import debounce from 'lodash/debounce';
function DataTable(props) {
const [searchText, setSearchText] = useState('');
const [showSearchBar, setShowSearchBar] = useState(false);
const handleFilterChange = (e) => {
let searchText = e.target.value;
setSearchText(searchText);
if (searchText) {
debounceLoadData({
columns: searchableColumns,
value: searchText
});
}
};
const loadData = (filter) => {
// grab one extra record to see if we need a 'next' button
const limit = pageSize + 1;
const offset = pageSize * page;
if (props.loadData) {
props.loadData({
variables: {
hideLoader: true,
opts: {
offset,
limit,
orderBy,
filter,
includeCnt: props.totalCnt > 0
}
},
updateQuery: (prev, {fetchMoreResult}) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
[props.propName]: [...fetchMoreResult[props.propName]]
});
}
}).catch(function (error) {
console.error(error);
})
}
};
const debounceLoadData = debounce((filter) => {
loadData(filter);
}, 1000);
return (
<div>
<Card style={{
border: props.noCardBorder ? 'none' : ''
}}>
{showSearchBar ? (
<span className="card-header-icon"><Icon className='magnify'/></span>
<input
autoFocus={true}
type="text"
className="form-control"
onChange={handleFilterChange}
value={searchText}
/>
<a href="javascript:void(0)"><Icon className='close' clickable
onClick={() => {
setShowSearchBar(false);
setSearchText('');
}}/></a>
) : (
<div>
{visibleData.length > 0 && (
<li className="icon-action"><a
href="javascript:void(0)"><Icon className='magnify' onClick= {() => {
setShowSearchBar(true);
setSearchText('');
}}/></a>
</li>
)}
</div>
)
)}
<Card.Body className='flush'>
<ReactTable
columns={columns}
data={visibleData}
/>
</Card.Body>
</Card>
</div>
);
}
export default DataTable
... and this is the outcome: link
debounceLoadData will be a new function for every render. You can use the useCallback hook to make sure that the same function is being persisted between renders and it will work as expected.
useCallback(debounce(loadData, 1000), []);
const { useState, useCallback } = React;
const { debounce } = _;
function App() {
const [filter, setFilter] = useState("");
const debounceLoadData = useCallback(debounce(console.log, 1000), []);
function handleFilterChange(event) {
const { value } = event.target;
setFilter(value);
debounceLoadData(value);
}
return <input value={filter} onChange={handleFilterChange} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
To add onto Tholle's answer: if you want to make full use of hooks, you can use the useEffect hook to watch for changes in the filter and run the debouncedLoadData function when that happens:
const { useState, useCallback, useEffect } = React;
const { debounce } = _;
function App() {
const [filter, setFilter] = useState("");
const debounceLoadData = useCallback(debounce(fetchData, 1000), []);
useEffect(() => {
debounceLoadData(filter);
}, [filter]);
function fetchData(filter) {
console.log(filter);
}
return <input value={filter} onChange={event => setFilter(event.target.value)} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
You must remember the debounced function between renders.
However, you should not use useCallback to remember a debounced (or throttled) function as suggested in other answers. useCallback is designed for inline functions!
Instead use useMemo to remember the debounced function between renders:
useMemo(() => debounce(loadData, 1000), []);
I hope this post will get you to the solution ,
You don't have to use external library for Debouncing you can create your own custom hook follow my steps
step(1):- Create the custom hook of Debouncing
import { useEffect ,useState} from 'react';
export const UseDebounce = (value,delay)=>{
const [debouncedValue,setDebouncedValue]= useState();
useEffect(()=>{
let timer = setTimeout(()=>setDebouncedValue(value),delay)
return ()=> clearTimeout(timer);
},[value])
return debouncedValue
}
step(2) :- Now create the file in which you want to add throttle
import React from 'react'
import { useEffect } from 'react';
import { useState } from 'react';
import {UseDebounce} from "./UseDebounce";
function Test() {
const [input, setInput] = useState("");
const debouncedValue = UseDebounce(input,1000);
const handleChange = (e)=>{
setInput(e.target.value)
}
useEffect(()=>{
UseDebounce&& console.log("UseDebounce",UseDebounce)
},[UseDebounce])
return (
<div>
<input type="text" onChange={handleChange} value={input}/>
{UseDebounce}
</div>
)
}
export default Test;
NOTE:- To test this file first create react app then embrace my files in it
Hope this solution worthwhile to you

Resources