onClick in reactjs not showing items - reactjs

I have to show a list of defaultValues in the search list and when I click on any of those item then it should take me to that item's component but it's not going anywhere. It's only happening with the defaultValues because as soon as I start typing, then if I click on any search result then it takes me to the desired component. what is wrong with my code?
here's the code
const [search, setSearch] = useState("");
const [showDefaultValues, setShowDefaultValues] = useState(false);
const [defaultValues] = useState({
Mumbai: true
});
{!search.length && showDefaultValues ? (
<div className="result-box">
{data
.filter((item, idx) => defaultValues[item.district])
.map((dist, idx) => (
<div
key={idx}
className="search-result"
onClick={() => {
onResultClick(dist.district);
}}
>
{highlightedText(dist.district, search)}
</div>
))}
</div>
) : null}

Just change the codes at components/search/search.js line 39 to 49
<input
placeholder="Search for a District..."
type="text"
className="search-input"
value={search}
onChange={onSearchInputChange}
onFocus={() => {
toggleDefaultValues(true);
}}
onBlur={onBlurInput}
/>
To
<input
placeholder="Search for a District..."
type="text"
className="search-input"
value={search}
onChange={onSearchInputChange}
onFocus={() => {
toggleDefaultValues(true);
}}
/>
Or simply remove line 48
To compensate this, you can add below inside your useEffect (similar to componentDidMount)
document.addEventListener("mousedown", handleInputClickOutside);
add function handleInputClickOutside to set the state to false/hide

You forgot to implement the onClick logic on the default search result items and that's why the search results work fine, while the default search items do not.
Check this link to the working codesandbox.
All i did was invoke the same onResultClick function onClick of 'District' component.
<div
className="dist"
onClick={() => {
this.props.onResultClick(item.district);
}}
>
...
</div>
Hope this solves your problem.

Related

Input field not cleared after using useState with onClick

I have a React app, where I'm using an input field as a searchbar, which upon typing, returns a list of products, and upon clicking any product takes you to that product page. I want to clear the typed text in the searchbar after the redirection to new page has happened but I haven't been able to achieve this yet.
I've tried many methods and went over similar posts, but I don't know what am I doing wrong as text is never cleared.
I'm using Material UI for rendering the list and have imported everything as needed.
Below is my code:
Navbar component (contains searchbar)
const Navbar = () => {
const [text, setText] = useState('');
const [liopen, setLiopen] = useState(true);
const getText = (text) => {
setText(text);
setLiopen(false);
};
const handleClick2 = (e) => {
setText('');
setLiopen(true)
};
return (
<header>
<nav>
<div className="middle">
<div className="nav_searchbar">
<span className="search_icon">
<SearchIcon id="search" />
</span>
<input
type="text"
onChange={(e) => getText(e.target.value)}
name=""
placeholder="Search for products, categories, ..."
id=""
/>
</div>
{text && (
<List className="extrasearch" hidden={liopen}>
{products
.filter((product) =>
product.title.toLowerCase().includes(text.toLowerCase())
)
.map((product) => (
<ListItem>
<NavLink
to={`/getproductsone/${product.id}`}
onClick={(e) => {handleClick2(e)}}
>
{product.title}
</NavLink>
</ListItem>
))}
</List>
)}
</nav>
</div>
</header>
);
};
export default Navbar;
You need to set the value of the input if you want it controlled by the component state.
<input value={text}
type="text"
onChange={(e) => getText(e.target.value)}
name=""
placeholder="Search for products, categories, ..."
id=""
/>

Set item in onclick is not logging expected output

I'm writing a simple react code that adds a value to a list onClick of a button. and after adding, I'm logging it in the same block. Currently, my issue is, that the logging is happening with n-1 entered string. i.e. If I enter egg and then add milk, after adding milk, I see egg logged and so on. Here is my code.
function App() {
const [list, setList] = useState([]);
const [gItem, setGItem] = useState("");
const AddItem = (e) => {
e.preventDefault();
setList([...list, gItem]);
console.log(list);
};
return (
<>
<form className="grocery-form">
<h3>grocery bud</h3>
<div className="form-control">
<label htmlFor="name"></label>
<input
type="text"
placeholder="e.g. eggs"
className="grocery"
name="name"
id="name"
onChange={(e) => setGItem(e.target.value)}
/>
<button className="submit-btn" type="submit" onClick={AddItem}>
Submit
</button>
</div>
</form>
<div className="grocery-container">
<List items={list} />
</div>
</>
);
}
I'm unable to understand where I'm going wrong.
setList updates state asynchronously so if you log state after using it the previous value will be displayed, to make it log the current state after this list was changed you can use useEffect hook like this:
useEffect(() => {
console.log(list);
}, [list])

React re renders everything every time key is pressed

I have a chart and an input field below. Basically the user can ask a question and the chart will change accordingly. Here is how it looks like
Here is the sample of the code below (ignore CoreUI syntaxes)
<CRow>
<CCol xs="12" sm="12" lg="12">
<CCard id="answerScreen" style={{height: "500px"}}>
{
!tempResponse.loading? tempResponse.data.map(value => (
<ChartRender
key={uuidv4()}
data={value}
/>
))
:
<Loader/>
}
</CCard>
</CCol>
</CRow>
<CRow>
<CCol xs="12" sm="12" lg="12">
<CCard>
<CCardBody>
<CForm className="form-horizontal">
<CFormGroup>
<CInputGroup size="lg" className="input-prepend">
<CInputGroupPrepend>
<CInputGroupText className="">#Ask</CInputGroupText>
</CInputGroupPrepend>
<CInput
size="16"
type="text"
value={userInput || ""}
onChange={e=> handleTextBoxInput(e)}
onClick={e=> handleTextBoxClick(e)}
onKeyPress={e => handleTextBoxEnter(e)}
id="userQuery"
/>
<CInputGroupAppend>
<CButton color="primary">Send</CButton>
</CInputGroupAppend>
</CInputGroup>
</CFormGroup>
</CForm>
</CCardBody>
</CCard>
</CCol>
</CRow>
This is how I have defined my states
const [userInput, setUserInput] = React.useState("");
const [tempResponse, setTempResponse] = React.useState({
data: []
})
I suspect the problem in this part of the code
<CInput
size="16"
type="text"
value={userInput || ""}
onChange={e=> handleTextBoxInput(e)}
onClick={e=> handleTextBoxClick(e)}
onKeyPress={e => handleTextBoxEnter(e)}
id="userQuery"
/>
I even tried adding useCallback to onChange function like this
const handleTextBoxInput = useCallback(e =>{
e.preventDefault();
setUserInput(e.target.value)
}, [])
But no help. I even read about memo but not sure where or how to apply it in my situation. What am I doing wrong?
OBSERVATION
As mentioned by #Matthew ,arrow syntax creates a different callback each time which contributes to re rendering and hence must be removed.
But even after removing it, the chart is getting re rendered every time a key is pressed. I am using Chartjs which uses canvas internally. Is Chartjs a problem?
You are correct about the problemetic code.
<CInput
size="16"
type="text"
value={userInput || ""}
onChange={e=> handleTextBoxInput(e)} // performance issue
onClick={e=> handleTextBoxClick(e)} // performance issue
onKeyPress={e => handleTextBoxEnter(e)} // performance issue
id="userQuery"
/>
When the above code is run multiple times, the functions will be re-created every time. So instead of doing that, the following is enough
<CInput
size="16"
type="text"
value={userInput || ""}
onChange={handleTextBoxInput}
onClick={handleTextBoxClick}
onKeyPress={handleTextBoxEnter}
id="userQuery"
/>
The useCallback hook returns, well, a callback function. You can simply use the return values as normal event callbacks.
On your input, you have two events firing on every keypress - onKeyPress and onChange - remove the onKeyPress.
I suspect that handleTextBoxEnter calls setTempResponse which updates tempResponse. Setting state that the UI depends on will trigger a re-render.
You should also handle form submissions using the onSubmit event. If an element has focus inside of a form and the enter button is pressed - it will fire onSubmit.
<form onSubmit={handleTextBoxEnter}></form>
Also, if a key changes React will re-render. You are calling a function in your key so it is updated on every update.
tempResponse.data.map((value, i) => (
<ChartRender key={`chart-${i}`} data={value} />
))
FYI manually creating a UUID for a key overkill.
export const YourComponent = (): JSX.Element => {
const [userInput, setUserInput] = useState('');
const [tempResponse, setTempResponse] = useState({ data: [], loading: true });
useEffect(()=>{
// handle initial data loading and set loading to false
}, [])
const handleSubmit = (e) => {
e.preventDefault();
setTempResponse(your_state_data);
};
// e.preventDefault shouldn't be used here and is not required
const handleChange = ({ target }) => setUserInput(target.value);
if (tempResponse.loading) {
return <Loading />;
}
// action is set to # for iOS - an action is required to show the virtual submit button on the keyboard
return (
<>
<form action="#" onSubmit={handleSubmit}>
<input defaultValue={userInput} onChange={handleChange} type="text" />
<button type="submit">Submit</button>
</form>
{!!tempResponse.length &&
tempResponse.data.map((value, i) => (
<ChartRender key={`chart-${i}`} data={value} />
))}
</>
);
};

how to use useRef for the attribute htmlFor?

I want to customize the input tag for file upload.
This is my code. Here for the attribute htmlFor, I am giving id of the input tag.Then it is working. But instead I want to use useRef ref. How can I do that ? If I follow the below method, it will be problematic if I render this component more than once. right ?
const App = () => {
const inputRef = useRef(null);
const [file, setFile] = useState(null);
return (
<>
<input
ref={inputRef}
accept=".pdf"
style={{ display: "none" }}
id="raised-button-file"
multiple
type="file"
onChange={e => {
setFile(e.target.files[0]);
}}
/>
<label htmlFor="raised-button-file">
<button component="span">
<span>file</span>
</button>
</label>
</>
);
};
Another way of using <label> tag is by wrapping your element as a child without specifying an id for it.
<label>
<input
accept=".pdf"
style={{ display: "none" }}
multiple
type="file"
onChange={e => {
setFile(e.target.files[0]);
}}
/>
<span>File</span>
</label>
If you prefer to open your file input dialog with your ref, you can do like this.
const handleOpenFileInput = () => {
inputRef.current.click();
};
<label onClick={handleOpenFileInput}>
<button>file</button>
</label>
<input
ref={inputRef}
accept=".pdf"
style={{ display: "none" }}
multiple
type="file"
onChange={e => {
setFile(e.target.files[0]);
}}
/>
If you use userRef it won't solve your problem. The problem is in the label and the htmlFor attribute. It constantly gets the inputs whose ids match the htmlFor attribute, and since your render the component multiple times, it always gets the first match.
I would simply pass the id of each component as a property, so that each time the label matches the right input. I would change the code to look more like this:
const Form = ({id}) => {
const onChangeInput = e => {
const [file] = e.target.files
}
return (
<form>
<div>
<input type="file" id={id} name="file" className="my-input" accept="application/pdf" style={{display:"none"}} onChange={onChangeInput} multiple/>
<label htmlFor={id}>Upload</label>
</div>
</form>
)
}
function App() {
return (
<div className="App">
<Form id="form1"/>
<Form id="form2"/>
<Form id="form3"/>
</div>
);
}
To make sure that each document has been uploaded correctly, I passed a className attribute to the inputs so I can get all of them. Running this code, I find all files that I have uploaded
Array.from(document.querySelectorAll('.my-input')).map(v => v.files[0].name)

React component second time render issue

I am building a tasks board app and I have an issue with rendering the TasksList component within a board of 3 lists: 1 Board -> 3 lists -> N tasks
It seems like the TasksList component is being rendered twice, which is fine, but on the 2nd time it seems to return different values for each task (which are wrong according to my conditional return, and right on the first render - why would there be a difference?)
I also get this warning. Maybe ts related:
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
Board.js component render function:
const tasksListsArr = Object.entries(this.state.tasks).map(list => {
return (
<TasksList
key={list[0]}
listrole={list[0]}
listTitle={this.state.Lists[list[0]]}
tasks={list[1]}
listTextChangedHandler={event => this.listTextChangedHandler(list[0], event)}
addTaskHandler={() => this.addTaskHandler(list[0])}
taskDeleteHandler={this.taskDeleteHandler}
moveTaskHandler={this.moveTaskHandler}
taskEditHandler={this.taskEditHandler}
taskEditApprove={this.taskEditApprove}
/>
)
})
TaskList.js component:
import React from "react"
import classes from "./TasksList.module.css"
const TasksList = props => {
const tasks = props.tasks.map(task => {
const buttonLeft =
!task.isEdit && (props.listrole === "inprogress" || props.listrole === "done") ? (
<button onClick={() => props.moveTaskHandler(task.id, "left")}>left</button>
) : null
const buttonRight =
!task.isEdit && (props.listrole === "inprogress" || props.listrole === "backlog") ? (
<button onClick={() => props.moveTaskHandler(task.id, "right")}>right</button>
) : null
const taskUtils = task.isEdit ? null : (
<div>
<span onClick={() => props.taskDeleteHandler(props.listrole, task.id)}>X</span>
<span onClick={() => props.taskEditHandler(props.listrole, task.id)}>edit</span>
</div>
)
const taskContent = task.isEdit ? (
<div>
<input
type='text'
onChange={event => props.listTextChangedHandler(props.listrole, event)}
/>
<button onClick={props.taskEditApprove(props.listrole, task.id)}>OK</button>
</div>
) : (
<div>
<div>{task.text}</div>
</div>
)
return (
<div key={task.id} className={classes.Task}>
{buttonLeft}
{taskContent}
{buttonRight}
{taskUtils}
</div>
)
})
console.log(tasks)
return (
<div className={classes.List}>
<h2 className={classes.ListTitle}> {props.listTitle} </h2>
<input type='text' onChange={props.listTextChangedHandler} placeholder='Add task...' />
<button onClick={props.addTaskHandler}>+</button>
<div className={classes.TasksList}>{tasks}</div>
</div>
)
}
export default TasksList
I suspect the issue is in TaskList component. Because the onChange of input and onClick of button getting called on every render but those event handler functions should be called only when user integrated with it. So to fix change them to arrow way so that the function gets called only when we interact.
Following changes required in TaskList.js
Change
<button onClick={props.taskEditApprove(props.listrole, task.id)}>OK</button>
To
<button onClick={() => props.taskEditApprove(props.listrole, task.id)}>OK</button>
And
Change
<input type='text' onChange={props.listTextChangedHandler} placeholder='Add task...' />
<button onClick={props.addTaskHandler}>+. </button>
To
<input type='text' onChange={event => props.listTextChangedHandler(event)} placeholder='Add task...' />
<button onClick={() => props.addTaskHandler()}>+</button>
OK
props.taskEditApprove is being called in render.
Try
props.taskEditApprove(props.listrole, task.id)}>OK
Then function will be called only on interaction.

Resources