I want to update array of object value triggered by onChange event in React. In this case, I want to update "NOTE" data depends on input (depends on DETAIL_REQUEST_ID). When I input for the first time, "NOTE" has updated well. but after entering the next data, previous data is lost.
Here's my code, sorry for my bad English. Hope you get what I mean.
import React, { useState } from 'react';
import { render } from 'react-dom';
const App = () => {
const [data, setData] = useState(null);
const dummy = [
{ DETAIL_REQUEST_ID: 1, STATUS: "REJECT", NOTE: "" },
{ DETAIL_REQUEST_ID: 2, STATUS: "REJECT", NOTE: "" },
{ DETAIL_REQUEST_ID: 3, STATUS: "REJECT", NOTE: "" },
];
const handleBtn = () => {
console.log("DATA: ", data);
};
const handleChange = (e, id) => {
let newArr = [...dummy];
const updatedArr = newArr.map(el => {
if (id === el.DETAIL_REQUEST_ID) {
el = { ...el, NOTE: e.target.value };
}
return el;
});
setData(updatedArr);
};
return (
<div>
<table>
<tr>
<th>Detail Request Id</th>
<th>Note</th>
</tr>
{dummy.map(val => {
return (
<tr>
<td>{val.DETAIL_REQUEST_ID}</td>
<td>
<input type="text" onChange={(e) => handleChange(e, val.DETAIL_REQUEST_ID)} />
</td>
</tr>
)
})}
</table>
<button type="submit" onClick={handleBtn}>Submit</button>
</div>
);
}
render(<App />, document.querySelector('#app'));
You are basing your state updates off the same constant dummy object each time. This resets the state. You should use data instead.
import React, { useState } from 'react';
import { render } from 'react-dom';
const App = () => {
const dummy = [
{ DETAIL_REQUEST_ID: 1, STATUS: "REJECT", NOTE: "" },
{ DETAIL_REQUEST_ID: 2, STATUS: "REJECT", NOTE: "" },
{ DETAIL_REQUEST_ID: 3, STATUS: "REJECT", NOTE: "" },
];
// use the dummy object to initialize the state <============= !!
const [data, setData] = useState(dummy);
const handleBtn = () => {
console.log("DATA: ", data);
};
const handleChange = (e, id) => {
// use the actual state to base your changes off of <============= !!
let newArr = [...data];
const updatedArr = newArr.map(el => {
if (id === el.DETAIL_REQUEST_ID) {
el = { ...el, NOTE: e.target.value };
}
return el;
});
setData(updatedArr);
};
return (
<div>
<table>
<tr>
<th>Detail Request Id</th>
<th>Note</th>
</tr>
{/*
render the actual state (using data, not dummy) <============= !!
*/}
{data.map(val => {
return (
<tr>
<td>{val.DETAIL_REQUEST_ID}</td>
<td>
<input type="text" onChange={(e) => handleChange(e, val.DETAIL_REQUEST_ID)} />
</td>
</tr>
)
})}
</table>
<button type="submit" onClick={handleBtn}>Submit</button>
</div>
);
}
render(<App />, document.querySelector('#app'));
Related
I want to be able to type into my input fields, and then have a button show up beside it upon typing that says submit edit. right now, I have a button that always is there, but I want it to only show up upon typing. this is all in react btw. so far, I have tried jquery, but react doesn't like it.
here's the whole page, to avoid any confusion of what I am doing and where my stuff is located.
import React, { Component } from "react";
import axios from "axios";
import "../styles/TourPage.css";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true,
};
}
componentDidMount() {
axios
.get("/getResults")
.then((res) => {
this.setState({
myData: res.data
});
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
deleteById = (id) => {
console.log(id)
axios
.post(`/deleteDoc`, {id: id} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
editById = (id, siteLocation, Services, cnum) => {
console.log(id, siteLocation, Services, cnum)
axios
.post(`/editDoc`, JSON.stringify({id: id, location: siteLocation, Services: Services, cnum: cnum}),{
headers: {
"Content-Type": "Application/json"
}
} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
render() {
// You can handle the loader part here with isLoading flag. In this case No data found will be shown initially and then the actual data
let { myData, isLoading } = this.state;
return (
<table id="customers">
<tr>
<th>siteLocation</th>
<th>Services</th>
<th>cnum</th>
</tr>
{myData.length > 0
? myData.map(({ location, Services, cnum, _id }, index) => (
<tr key={index}>
<td><input type="text" placeholder={location} name="location" id="location" /> </td>
<td><input type="text" placeholder={Services} name="Services" id="Services" /> </td>
<td><input type="text" placeholder={cnum} name="cnumhide" id="cnumhide" /> </td>
<td><input type="hidden" placeholder={cnum} name="cnum" id="cnum" /> </td>
<button
onClick={(e) => {
e.preventDefault();
this.deleteById(_id);
}}
disabled={isLoading}
>
Delete
</button>
<button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>
</tr>
))
: "No Data Found"}
</table>
);
}
}
const script = document. createElement("script"); $('input').keyup(function(){
if($.trim(this.value).length > 0)
$('#location').show()
else
$('#location').hide()
});
export default TourPage;
thanks 4 the help in advance.
You can use onfocus() in the text element. If you want to hide the button, use onfocusout() or in case if you want to track only after input has changed, use onchange() event
...
//class function
onTyping =()=>{
this.setState({
showSubmit:true
})
}
...
//render part
render(){
...
//input which you want to track typing
<input type="text" onfocus={()=>this.onTyping()} placeholder={location} name="location" id="location" />
...
//element submit button
{this.state.showSubmit && <button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>}
...
Here is an example that helps you,
const {
useState
} = React;
const Test = () => {
const [show, setShow] = useState(false);
const handleChange = (event) => {
if (event.target.value.length > 0)
setShow(true);
else
setShow(false)
}
return ( <div>
<input type = "text"
onChange = {
(event) => handleChange(event)
}/>
{show && < button > Submit changes now! </button>}
</div>
)
}
ReactDOM.render( < Test / > ,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
There is a way to avoid jquery and continue using your react class component to achieve this.
Map over state.myData to render each item with an input and a button.
Use the array index with the input's onChange event callback to add the inputValue into the correct array item's object within state.
Use the array index with the button's onClick event callback to get the item from state.myData before sending it to the server.
If there is an inputValue for the item, you can conditionally render the button.
import React, { Component } from "react";
import axios from "axios";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
myData: res.data.results
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
newData[index].inputValue = target.value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(
`Clicked on 'submit edit' for ${item.name} with value ${item.inputValue}`
);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<div>
{myData.map(({ name, status, species, inputValue }, index) => {
return (
<div key={index}>
<p>{`${name} - ${species} - ${status}`}</p>
<input
type="text"
onChange={(e) => this.handleChangeInput(e, index)}
value={inputValue || ""}
/>
{inputValue && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</div>
);
})}
</div>
);
}
}
export default TourPage;
If you wanted to have an input per field within each row, you could make some small changes and save your edits to the item's state within a nested object.
Then you could check if there was anything inside that row's edits object to conditionally show the submit button per row.
import React, { Component } from "react";
import axios from "axios";
import isEmpty from "lodash.isempty";
import pick from "lodash.pick";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
// here we create an empty 'edits' object for each row
myData: res.data.results.map((d) => ({
...pick(d, ["name", "status", "species"]),
edits: {}
}))
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
const { value, name } = target;
newData[index].edits[name] = value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(`Clicked on 'submit edit' for ${item.name} with edits:`);
console.log(item.edits);
console.log("Updated item: ");
const { edits, ...orig } = item;
const newItem = { ...orig, ...edits };
console.log(newItem);
// Once saved to api, we can update myData with newItem
// and reset edits
const newData = [...this.state.myData];
newData[index] = { ...newItem, edits: {} };
this.setState({
myData: newData
});
};
showButton = (index) => {
const { edits } = this.state.myData[index];
return !isEmpty(edits);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<table>
<tbody>
{myData.map((row, index) => {
const { edits, ...restRow } = row;
return (
<tr key={index}>
{Object.keys(restRow).map((col) => {
return (
<td>
<label>
{col}:
<input
name={col}
value={edits[col] || restRow[col]}
onChange={(e) => this.handleChangeInput(e, index)}
/>
</label>
</td>
);
})}
<td>
{this.showButton(index) && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
);
}
}
export default TourPage;
In similar question, the solution is to use an unique value instead of index of map as the 'key' prop when looping through the array to render components. However, it not working for me as the remaning components still rerender after i delete an element from the array. What am i missing here?
Here is my attempts,which is not working as expected:
App.js
import { useState } from "react";
import Column from "./Column.js";
export default function App() {
const data = [
{
name: "lebron",
age: 36,
team: "lakers"
},
{
name: "chris",
age: 36,
team: "suns"
},
{
name: "james",
age: 32,
team: "nets"
}
];
const [players, setPlayers] = useState(data);
const handleClick = () => {
return function () {
const p = [...players];
p.splice(0, 1);
setPlayers(p);
};
};
return (
<>
<table>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
{players.map((p) => (
<Column key={p.name} player={p} onClick={handleClick(p.name)} />
))}
</table>
<button onClick={handleClick()}>Delete</button>
</>
);
}
Column.js:
import React from "react";
export default function Column({ player, onClick }) {
console.log(player.name, "update", Date());
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.team}</td>
</tr>
);
}
That's the default React behavior.
The children updates because an update on the father state occurred.
If you want to re-render child components only if the props has changed you need to wrap your child component with React.memo HOC.
Briefly, with React.memo React renders your component the first time, memoize the result and then reuse the last rendered result (skipping unnecessary re-renders).
React.memo only checks for prop changes. If your function component wrapped in React.memo has a useState, useReducer or useContext Hook in its implementation, it will still re-render when state or context change.
In your case:
Column.js file
import { memo } from "react";
function Column({ player, onClick }) {
console.log(`Render player ${player.name}`);
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.team}</td>
<button onClick={() => onClick(player)}>Delete</button>
</tr>
);
}
export default memo(Column);
App.js file
import { useState, useCallback } from "react";
import Column from "./Column";
const data = [
{
name: "lebron",
age: 36,
team: "lakers"
},
{
name: "chris",
age: 36,
team: "suns"
},
{
name: "james",
age: 32,
team: "nets"
}
];
export default function App() {
const [players, setPlayers] = useState(data);
const handleClick = useCallback((deleted) => {
setPlayers((prevState) =>
prevState.filter((player) => player.name !== deleted.name)
);
}, []);
return (
<>
<table>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
{players.map((p) => (
<Column key={p.name} player={p} onClick={handleClick} />
))}
</table>
{/* This button makes no sense */}
<button onClick={() => handleClick(players[0])}>Delete</button>
</>
);
}
Code sandbox
I make a few changes in your code:
handleClick function is not right. When you use splice you are modifyng the players array and in React state must be modified only using the dispatch function (setPlayers). In your case, you should use filter method because it returns a new Array.
const handleClick = (deleted) => {
setPlayers((prevState) =>
prevState.filter((player) => player.name !== deleted.name)
);
};
wrap your Column component with React.memo HOC.
export default React.memo(Column);
wrap your handleClick function with useCallback. This is because you need to memoize your function due to you are using React.memo.
const handleClick = useCallback((deleted) => {
setPlayers((prevState) =>
prevState.filter((player) => player.name !== deleted.name)
);
}, []);
Hope this solves your problem
import { useState } from "react";
function Column({ player, onClick }) {
console.log(player.name, "update", Date());
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.team}</td>
<td onClick={onClick}>x</td>
</tr>
);
}
export default function App() {
const data = [
{
name: "lebron",
age: 36,
team: "lakers"
},
{
name: "chris",
age: 36,
team: "suns"
},
{
name: "james",
age: 32,
team: "nets"
}
];
const [players, setPlayers] = useState(data);
const handleClick = (index) => {
return function () {
if(index===-1)
return setPlayers([])
const p = [...players];
p.splice(index, 1);
setPlayers(p);
};
};
return (
<>
<table>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
{players.map((p, index) => (
<Column key={p.name} player={p} onClick={handleClick(index)} />
))}
</table>
<p> Click x to delete</p>
<button onClick={handleClick(0)}>Delete First</button>
<button onClick={handleClick(players.length-1)}>Delete Last</button>
<button onClick={handleClick(-1)}>Delete All</button>
</>
);
}
https://codesandbox.io/s/objective-sky-77bwz?file=/src/App.js
I will assume that you want to delete the last element of your Array for that there is the pop() method
And not call directly function in onClick
Also use thead and tbody in your table
const handleClick = () => {
const p = [...players];
p.pop();
setPlayers(p);
};
return (
<>
<table>
<thead>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
</thead>
<tbody>
{players.map((p) => (
<Column
key={p.name}
player={p}
onClick={() => handleClick(p.name)}
/>
))}
</tbody>
</table>
<button onClick={handleClick}>Delete</button>
</>
);
i am using table input field to update state under map function to render it according to number of elements in the state.But when I used value={item.account} values are not updated in the state.which works fine when I use **value={accountCounter.account} where accountCounter is reactjs hook of type
const[accountCounter,setAccountCounter]=useState([
{ id: 1, account:"" ,accountOwner:""},
{ id: 2, account: "hi",accountOwner:"" },
{ id: 3, account: "bu" ,accountOwner:""}
And here is my rendering function
accountCounter.map((item,key)=>{
return(
<tr key={key}>
<td><input type="text" value={item.account}
name="account" onChange={(e)=>handleAccountCounter(e,item)}/></td>
<td><input type="text" value={item.accountOwner}
name="accountName" onChange={(e)=>handleAccountCounter(e,item)}/></td>
<td><span onClick={()=>deleteAccount(item.id)}>X</span></td>
</tr>
)
})}
here is my handleAccountCounter
const handleAccountCounter=(event,counter)=>{
const index = accountCounter.indexOf(counter);
accountCounter[index][event.target.name]=event.target.value;
setAccountCounter(accountCounter)
}
But the state is not getting modified when in input field value={item.account}.dont know why..will you help me out
Use the previous state values to create a new array:
const App = () => {
const [accountCounter, setAccountCounter] = useState([
{ id: 1, account: "", accountOwner: "" },
{ id: 2, account: "hi", accountOwner: "" },
{ id: 3, account: "bu", accountOwner: "" }
]);
const handleAccountCounter = (event, counter) => {
setAccountCounter((prevAccountCounter) => {
const newCounter = [...prevAccountCounter];
newCounter[prevAccountCounter.indexOf(counter)][event.target.name] =
event.target.value;
return newCounter;
});
};
const deleteAccount = (id) => {
setAccountCounter((prevAccountCount) =>
prevAccountCount.filter((item) => item.id !== id)
);
};
return accountCounter.map((item, index) => (
<tr key={index}>
<td>
<input
type="text"
value={item.account}
name="account"
onChange={(e) => handleAccountCounter(e, item)}
/>
</td>
<td>
<input
type="text"
value={item.accountOwner}
name="accountOwner"
onChange={(e) => handleAccountCounter(e, item)}
/>
</td>
<td>
<span onClick={() => deleteAccount(item.id)}>X</span>
</td>
</tr>
));
};
Instead of this
const handleAccountCounter = (event,counter) => {
const index = accountCounter.indexOf(counter);
accountCounter[index][event.target.name]=event.target.value;
setAccountCounter(accountCounter)
}
Do like this
const handleAccountCounter = (event, counter) => {
let temp = [...accountCounter] // Make a copy of state and then perform operations
const index = temp.indexOf(counter);
temp[index][event.target.name] = event.target.value;
setAccountCounter(temp)
}
Using Kartikey's answer, but you should use a callback because state updates are asynchronous:
const handleAccountCounter = (event, counter) => {
setAccountCounter((prev) => {
let newCounter = [...prev];
newCounter[prev.indexOf(counter)][event.target.name] = event.target.value;
return newCounter;
});
};
This ensures that the state updates in the correct order. See here for more info:
https://dmitripavlutin.com/how-react-updates-state/
Say I have a table:
<div>
<tr>
<td>
<p id='1' className="foo"> Boom </p>
</td>
<td>
<p id='2' className="foo"> Bang </p>
</td>
<td>
<p id='3' className="foobar"> Pew Pew </p>
</td>
</tr>
</div>
I want the data inside it to be editable in-place. Thus I want to substitute <p> element with an <input> and then substitute it with <p> again, but with new value. I've been doing it with jQuery and now made it with what seems to me as plain JS but with React. Code looks like this:
import React, { Component } from "react";
import "./App.css";
class App extends Component {
handleClick(e) {
if (e.target.className === 'foo'){
let element = document.getElementById(e.target.id)
let element_value = element.innerText
let parent_element = element.parentNode
let new_element = document.createElement('input')
parent_element.removeChild(element)
parent_element.appendChild(new_element)
new_element.setAttribute('class', 'input')
new_element.setAttribute('id', e.target.id)
new_element.setAttribute('value', element_value)
} else if (e.target.className === 'input') {
let element = document.getElementById(e.target.id)
let element_value = element.value
let parent_element = element.parentNode
let new_element = document.createElement('p')
parent_element.removeChild(element)
parent_element.appendChild(new_element)
new_element.setAttribute('class', 'foo')
new_element.setAttribute('id', e.target.id)
new_element.innerText = element_value
}
};
componentDidMount() {
document.addEventListener('dblclick', this.handleClick)
}
componentWillUnmount() {
document.removeEventListener('dblclick', this.handleClick)
}
render() {
return (
<div>
<tr>
<td>
<p id='1' className="foo"> Boom </p>
</td>
<td>
<p id='2' className="foo"> Bang </p>
</td>
<td>
<p id='3' className="foobar"> Pew Pew </p>
</td>
</tr>
</div>
)
}
}
export default App
However this doesn't seem to me as a good practice. Could you give me hints on how to improve/change my approach? Thank you.
The best approach is probably to create a controlled component that handles all the logic for the editable cell, and store the values in the parent. I made a sandbox that you can check out here, but I'll add the code here as well.
That way the cell component provides all the view stuff needed, and the parent controls the logic and data for all the cells.
So, the editable cell handles the functionality of switching between views:
const EditableCell = ({ id, onEdit, className, value }) => {
const [isEditing, setIsEditing] = useState(false);
const onClick = useCallback(() => {
setIsEditing(true);
}, []);
const onFinishedEditing = useCallback(() => {
setIsEditing(false);
}, []);
const onKeyDown = useCallback(
(e) => {
if (e.key === "Enter") {
onFinishedEditing();
}
},
[onFinishedEditing]
);
return (
<td>
{isEditing ? (
<input
value={value}
onChange={(e) => onEdit(e.target.value, id)}
onBlur={onFinishedEditing}
onKeyDown={onKeyDown}
autoFocus
/>
) : (
<p {...{ id, className, onClick }}>{value}</p>
)}
</td>
);
};
And then the app stores the cells' data and renders an EditableCell for each one:
export default function App() {
// This stores the cells values and properties, you can
// add or remove cells here are needed
const [cellValues, setCellValues] = useState([
{ id: "1", class: "foo", value: "Boom" },
{ id: "2", class: "foo", value: "Bang" },
{ id: "3", class: "foobar", value: "Pew Pew" }
]);
const onEdit = (value, id) => {
setCellValues(
cellValues.map((cellVal) =>
cellVal.id === id ? { ...cellVal, value } : cellVal
)
);
};
return (
<div>
Click a cell to edit
<tr>
{cellValues.map((cellVal) => (
<EditableCell
id={cellVal.id}
value={cellVal.value}
className={cellVal.class}
onEdit={onEdit}
/>
))}
</tr>
</div>
);
}
This might not perfectly match with the functionality you're wanting, but should give you a starting point
I've been promise to myself that i will made good deed at least one per day.I know that you write in class way but i stick to hooks so much that... sorry man :P
it call onChange when during editing you will press enter.
import React, { Component, useEffect, useMemo, useRef, useState } from "react";
import "./styles.css";
const Td = ({ children, editable = false, onChange, className, id }) => {
const cell = useRef();
const [edit, setEdit] = useState(false);
const [value, setValue] = useState(() => {
while (typeof children !== "string") {
children = children.props.children;
}
return children;
});
const [oldValue, setOldValue] = useState(value);
useEffect(() => {
if (!cell.current) return;
const onEditMode = () => editable && setEdit(true);
const target = cell.current;
target.addEventListener("click", onEditMode);
return () => {
target.removeEventListener("click", onEditMode);
};
}, [cell, setEdit, editable]);
const paragraph = useMemo(() => (
<p id="1" className="foo">
{value}
</p>
),[value]);
const input = useMemo(() => {
const update = (value) => {
setEdit(false);
if (onChange && typeof onChange === "function") {
onChange({
id,
newValue: value,
oldValue: oldValue
});
setOldValue(value);
}
}
return (
<input
value={value}
onChange={ e => setValue(e.target.value)}
onKeyDown={ e => e.key === "Enter" && update(value)}/>
)
},[value, setEdit, onChange, id, oldValue, setOldValue]);
return (
<td ref={cell} className={className}>
{edit ? input : paragraph}
</td>
);
};
class App extends Component {
componentDidMount() {
}
componentWillUnmount() {
}
tableCellValueChange({ id, newValue, oldValue }) {
console.log(
`table cell id: ${id} value changed from ${oldValue} to ${newValue}`
);
}
render() {
return (
<div>
<table>
<thead></thead>
<tbody>
<tr>
<Td
onChange={this.tableCellValueChange}
id="special"
editable>
<p>Bang </p>
</Td>
<Td onChange={this.tableCellValueChange} editable>
<p>Bang</p>
</Td>
<Td editable={false} className="forbar">
Pew Pew
</Td>
</tr>
</tbody>
</table>
</div>
);
}
}
export default App;
here you have sandbox
live example
I want to filter over an array using react hooks. It should be a fairly straight forward task, but I am assuming it is something to do with hooks being updated asynchronously, although this could be wrong.
I am fairly stumped, but have included a code sandbox and my code below:
const teams_data = [
"tottenham",
"arsenal",
"man utd",
"liverpool",
"chelsea",
"west ham"
];
function App() {
const [teams, setTeams] = React.useState(teams_data);
const [search, setSearch] = React.useState("");
return (
<div className="App">
<input
onChange={e => {
const test = teams.filter(team => {
return team.toLowerCase().includes(e.target.value.toLowerCase());
});
console.log("test: ", test);
// uncomment line below and teams is logged as I want
setTeams(test);
setSearch(e.target.value);
}}
type="text"
value={search}
/>
{teams.map(team => (
<p>{team}</p>
))}
</div>
);
}
You need to filter the original data :
const test = teams_data.filter(team => {
return team.toLowerCase().includes(e.target.value.toLowerCase());
});
https://codesandbox.io/s/thirsty-austin-uqx8k
You just need to add another state for search results
const [data , setData] = useState(teams);
const [query, setQuery] = useState('')
const[res , setRes] = useState([]);
return (
<div className="App container">
<form onSubmit = {(e) => e.preventDefault()}>
<input type = "search" className = "srh" placeholder = "search about..."
onChange = {(e) => {
const test = data.filter(team => {
return (
team.toLowerCase().includes(e.target.value.toLowerCase())
)
})
setRes(test)
if(e.target.value === '') setRes([])
}}
/>
</form>
<div>
{
res.map((item , i) => (
<p key = {i}>{item}</p>
))
}
</div>
</div>
);
I've made custom hook.
It receives the array as a first param
the search variable as a second
and the property you want to filter by
I hope it's helpfull
export function useSearch(array: any[], search: string, field: string) {
const filteredArray = array.filter((entry) => {
if (search === "") return entry;
else if (
entry[field].toLocaleLowerCase().includes(search.toLocaleLowerCase())
)
return entry;
});
return {
filteredArray
};
}
Them apply the filtered array to your map function
import { useSearch } from "./useSearch";
import { useState } from "react";
const array = [
{
id: 1,
name: "Humberto Guenzo Yoshimoto"
},
{
id: 2,
name: "Diego Braga"
},
{
id: 3,
name: "Hudson Teixeira"
},
{
id: 4,
name: "Matheus Doimo"
}
];
type FilteredArrayTypes = {
id: number;
name: string;
};
export default function App() {
const [searchByFullName, setSearchByFullName] = useState("");
const { filteredArray } = useSearch(array, searchByFullName, "name");
return (
<div className="App">
<h1>Search list</h1>
<input
onChange={(e) => setSearchByFullName(e.target.value)}
type="text"
value={searchByFullName}
placeholder="search"
/>
{filteredArray.map((entry: FilteredArrayTypes) => {
return (
<ul>
<li>{entry.name}</li>
</ul>
);
})}
</div>
);
}
Here goes a sandbox with the code: here