Imagen if I have an Array of objects - id and value. Right now the array is empty Array []. But is being fielded anytime the user type and add a name. If a user types Anna, Pedro, and Joana the array is updated to
Array [
Object {
"id": "0.265247357395923",
"value": "Anna",
},
Object {
"id": "0.07416445026556007",
"value": "Pedro",
},
Object {
"id": "0.9097178194832282",
"value": "Joana",
},
]
My task is to add a Search bar where I can search the object value inside the array. Here's my code so far.
My useState
const [folder, emptyFolder] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
My name
const addFolder = (folderName) => {
emptyFolder((currentFolder) => [
...currentFolder,
{ id: Math.random().toString(), value: folderName },
]);
};
My Search. Here is where I want to implement my code so far this is what I am able to do:
const filteredData = folder.filter((item) => {
const textData = searchTerm.toUpperCase();
const nameFolder = item.value.toUpperCase();
return nameFolder.includes(textData);
});
const _searchFilterFunction = (value) => {
//???
};
Here is my Return where I have TextInput as my search bar and {filteredData.map((item, index) => { return {item.value}; })} displaying my name list every time the user creates.
return (
<View style={styles.HomeContainer}>
<TextInput
underlineColorAndroid="transparent"
autoCapitalize="none"
placeholderTextColor="#9a73ef"
style={styles.search}
placeholder="Search"
onChangeText={(value) => {
_searchFilterFunction(value, folder);
}}
/>
<FolderInput myFolder={addFolder} />
{filteredData.map((item, index) => {
return <Text key={item.id}>{item.value}</Text>;
})}
</View>
);
};
PLEASE HELP THANK YOU VERY MUCH.
You can just filter the array of objects(folders) like this:
const filteredFolders = () => {
return state.folders.filter(({ name }) =>
name.toLowerCase().includes(state.search.toLowerCase())
);
}
Where state.search comes from an input. I created a sandbox with working demo
What you are trying is, correct.
const filteredData = folder.filter((item) => {
const textData = searchTerm.toUpperCase();
const nameFolder = item.value.toUpperCase();
return (nameFolder.indexOf(textData) >= 0)
});
includes is used for the array with string, but here actually you are comparing strings right? so you can go with indexOf it will return true if your search string is a substring of the object item.
Related
I am using an MUI Autocomplete to act as an employee search field. A user will start entering the name of an employee which queries an API request on each keystroke. Clicking on an employee name from the search results throws the following error message The 'getOptionLabel' method of Autocomplete returned object ([object Object]) instead of a string for {"Badge":"1234567","Name":"Doe, John[John.Doe#gmail.com]"}. I want to display the employee name in the field after clicking. After clicking on a name, I want to send the Badge number to my on change function as event (e). I need to send the badge as two employees may have the same name, but will always have unique badge numbers.
const { onChange, badge, taxonomyid, label, id, disabled, style, status, ...other } = props;
const [employees, setemployees] = useState([]);
useEffect(() => {
const fetchEmployees = async () => {
var url = `/api/employeesearch?taxonomyid=${taxonomyid}`;
if (badge && +badge) {
url = url + `&badge=${badge}`;
const resp = await fetch(url);
const emp = await resp.json();
setemployees(emp);
}
};
fetchEmployees();
}, [badge, taxonomyid]);
const onNameChange = async (e) => {
e.preventDefault();
var value = e.target.value;
var url = `/api/employeesearch?taxonomyid=${taxonomyid}`;
if (value) {
url = url + `&name=${value}`;
const resp = await fetch(url);
const emp = await resp.json();
if (resp.ok) {
setemployees(emp);
} else {
return null;
}
console.log(value);
}
};
const onBadgeChange = (e) => {
e.preventDefault();
console.log(e.target.value);
console.log(e.target.name);
onChange(e);
};
...
<Autocomplete
id="search-select"
// value={employees?.employees?.Name}
// inputValue={badge} //JSON.stringify(option.Name)
label={badge}
options={employees.employees}
getOptionLabel={(option, i) => (
<div key={i} onClick={(e) => /* onBadgeChange(e) */ setVal(option.Badge)} value={option.Name.toString()}>
{option.Name}
</div>
)}
onInputChange={(e) => onNameChange(e)}
onChange={onBadgeChange}
filterOptions={(x) => x}
variant="outlined"
size="small"
name={id || "Person"}
InputProps={{
classes: {
input: classes.fields,
},
}}
renderInput={(params) => <TextField {...params} label={badge} variant="outlined" />}
/>
Here is a quick example of my employee data:
{
"employees": [
{
"Badge": "1234567",
"Name": "Doe, John[John.Doe#gmail.com]"
}
]
}
I have an input with label "Ilosc osob". When I change it, I want to change the Select's options, depends of number in input. It happens, but with one step gap. What should I do?
matchedTables depends on props.tables, and it is filtered array from parent component.
const ReservationForm = (props) => {
const [enteredSize, setEnteredSize] = useState("");
const [enteredTable, setEnteredTable] = useState("");
const [enteredDate, setEnteredDate] = useState("");
const [enteredTime, setEnteredTime] = useState("");
const [sizeMatchedTables, setSizeMatchedTables] = useState([
{ id: 55, table_size: 1, isBusy: false },
{ id: 56, table_size: 2, isBusy: true },
]);
//some code
const matchingSizeTablesHandler = () => {
const newArray = props.tables.filter((tables) => {
if (tables.table_size >= enteredSize) {
return tables;
}
});
setSizeMatchedTables(newArray);
};
const sizeChangeHandler = (event) => {
setEnteredSize(event.target.value);
matchingSizeTablesHandler();
};
//some code
return(
<div>
<div className="new-reservation__control">
<label>Ilość osób</label>
<input
type="number"
min={1}
max={10}
value={enteredSize}
onChange={sizeChangeHandler}
/>
</div>
<select
className="new-reservation__control"
value={enteredTable}
onChange={tableChangeHandler}
>
<TablesInSelect passedOptions={sizeMatchedTables} />
</select>
</div>
)};
const TablesInSelect = (props) => {
return (
<>
{props.passedOptions.map((option, index) => {
return (
<option key={index} value={option.id}>
{option.id}
</option>
);
})}
</>
);
};
I found workaround this problem, but dont think its best way to do this. I changed matchingSizeTableHandler so it work with argument (and also change filter to reduce but it is not the case):
const matchingSizeTablesHandler = (size) => {
const newArray = props.tables.reduce((newTables, tables) => {
if (tables.table_size >= size) {
var newValue = tables;
newTables.push(newValue);
}
if (size === "") newTables = [];
return newTables;
}, []);
setSizeMatchedTables(newArray);
};
and then, in sizeChangeHandler I changed call out matchingSizeTableHandler with event.target.value parameter
const sizeChangeHandler = (event) => {
setEnteredSize(event.target.value);
matchingSizeTablesHandler(event.target.value);
};
. If someone can explain to me the other way to implement this, using the sizeMatchedTable state as a parameter in matchingSizeTablesHandler function, I would be thankful.
I am trying to figure out a way to do filter by price and name, but somehow the useState won't update. I printed the result out and it worked on both checkboxes. By the way, is it better to use onChange or onClick on checkbox, both look the same to me. I have watched many tutorials and searched for many possible solutions but I still can't solve this problem.
let product = [
{
name: "tesla",
cost: 500,
},
{
name: "benz",
cost: 1000,
},
{
name: "honda",
cost: 200,
},
{
name: "apple",
cost: 400,
},
];
const [data, setData] = useState(product);
const sortbyprice = () => {
const result = product.sort((a, b) => {
return a.cost > b.cost ? 1 : -1;
});
setData(result);
console.log(result);
};
const sortbyname = () => {
const result = product.sort((a, b) => {
return a.name > b.name ? 1 : -1;
});
setData(result);
console.log(data);
};
return (
<div className="App">
<div className="sort price">
<h3>Sort by price</h3>
<input
type="checkbox"
className="searchbar"
onChange={sortbyprice}
></input>
</div>
<div className="sort name">
<h3>Sort by name</h3>
<input
type="checkbox"
className="searchbar"
onChange={sortbyname}
></input>
</div>
<div className="product-container">
{data.map((item, index) => {
return (
<div className="product" key={index}>
<h2>{item.name}</h2>
<p>{item.cost}</p>
</div>
);
})}
</div>
</div>
);
}
I'm updating my answer in order to cover both cases where product is inside the component or outside.
The problem is the array mutation
You can read more here How can you sort an array without mutating the original array?
The correct way is the following and it should work
const sortbyprice = () => {
let dataClone = [...data];
const result = dataClone.sort((a, b) => {
return a.cost > b.cost ? 1 : -1;
});
setData(result);
console.log(result);
};
const sortbyname = () => {
let dataClone = [...data];
const result = dataClone.sort((a, b) => {
return a.name > b.name ? 1 : -1;
});
Check this sandbox
You have to use two different state variables for the this check boxes, and when clicking on one of these you have to update your state accordingly here is CodesandBox.
This all looks good, I run it on codesandbox and it does rerenders. But anyway I would suggest you to move product array out of the component, and just to use it as initial value for useState hook, and on state update use input parameter from setData callback - eg: setData(prev => [...prev.sort(...)]), no need each time to reference initial array since you already used it when calling hook in your component.
My problem is that I'm trying to handle the value of my inputs, which the user defines which input he wants, by an API call.
Here is where I get the values :
const handleClick = buttonTitle => async () => {
await renderField(buttonTitle).then(response => {
navigation.navigate('FormScreen', {
collectionKey: buttonTitle.slice(7),
paramKey: JSON.stringify(response),
});
});
};
Render field is an API call, which returns me {"message": [{"_id": "618e4c23db08f70b719f3655", "author": "adicionarei posteriormente", "ceatedAt": "2021-11-12 08:12:32", "field": "abc", "fieldtype": "Text"}, {"_id": "618e4c9ddb08f70b719fae37", "author": "adicionarei posteriormente", "ceatedAt": "2021-11-12 08:14:35", "field": "Animal", "fieldtype": "Text"}]}
Then I have my Form component, where I get some components in need and display for the user:
const FormScreen = ({route, navigation}) => {
return (
<Container>
<InputBody route={route.params.paramKey} navigation={navigation} />
</Container>
// => handle submit Input it in here ?
);
};
For my inputbody component I have the following code (remembering that (body.map is the api call response):
return (
<>
{Object.keys(Body).length > 0 ? (
Body.map(item => (
<React.Fragment key={uuid.v4()}><Texto>{item.field}</Texto>
{renderContent(item.fieldtype,item.field)}
</React.Fragment>
))
) : (
<ActivityIndicator size="large" color="#eb6b09" />
)}
</>
)
}
Then I have my renderContent( where I get the type of the field as a string and the name of the field that is a string as well).
function renderContent(type,field) {
switch(type) {
case 'Numeric':
return <NumberInput key={field} keyboardType="numeric" />
case 'Text':
return <TextInput key={field} />
}
}
Remembering that: each field type can appear more than once.
(For example: I can have one form with more than 1 Text input), then my question is: how can I handle the values of my input knowing that it can have any kind of input(Numeric or Text) ?
obs: I can show any kind of information.
const Input = ({value,keyboardType,onChange})=>{
return(
<TextInput value={value} keyboardType={keyboardType} onChangeText={onChange} />
)
}
const [payload,setPayload] = useState({});
const onValue=(e,field)=>{
let tempPayload = {...payload};
tempPayload[field] = e;
setPayload(tempPayload)
}
const renderComponent = (fieldObj)=>{
switch(fieldObj.type):
case "Text":
return <Input keyboardType="default" onChange={(e)=>onValue(e,fieldObj.field)} value={payload[fieldObj.field]||""}/>
case "Number":
return <Input keyboardType="numeric" onChange={(e)=>onValue(e,fieldObj.field)} value={payload[fieldObj.field]||""} />
case "Dropdown":
return <Dropdown options={fieldObj.options} /> //if you want to add dropdown, radio buttons etc in future
}
The idea is pretty straight forward. Store the values from the form fields in a object payload. The name is name of the field eg. Animal. The value is the value of that field. You can also initialize the object with all the keys and their values as empty or a default value that you get from the api. So if the fields we have rendered is Animal and Car. The payload will be
{
'Animal':'Tiger',
'Car':'BMW'
}
This is handled using the onValue function. You can also add validation in this function.For example if you pass a regex with your api for that field, the you can validate the value using the regex.
It was a bit hacky so I simplified it, I think you should understand the logic behind it.
import React, { useState } from 'react';
import { TextInput } from 'react-native';
const createInitialState = (inputList) => {
return inputList.reduce((accumulator, currentValue) => {
return {
...accumulator,
[currentValue.field]: '',
};
}, {});
};
const SomeScreen = () => {
const initialDataPassed = [
{
'_id': '618e4c23db08f70b719f3655',
'author': 'adicionarei posteriormente',
'ceatedAt': '2021-11-12 08:12:32',
'field': 'abc',
'fieldType': 'Text',
}, {
'_id': '618e4c9ddb08f70b719fae37',
'author': 'adicionarei posteriormente',
'ceatedAt': '2021-11-12 08:14:35',
'field': 'Animal',
'fieldType': 'Text',
},
{
'_id': '618e4c9ddb08f70b719fae37',
'author': 'adicionarei posteriormente',
'ceatedAt': '2021-11-12 08:14:35',
'field': 'Animal',
'fieldType': 'Number',
},
];
return (
<Form inputList={initialDataPassed} />
);
};
const Form = ({ inputList }) => {
const [formState, setFormState] = useState(createInitialState(inputList));
return (
<>
{inputList.map((item) => {
const handleTextInputValueChange = (text) => {
// this is solution is better if we base on old value
setFormState(oldState => ({
...oldState,
[item.field]: text
}))
};
return <Input key={item.field} value={formState[item.field]} onChangeText={handleTextInputValueChange} fieldType={item.fieldType} />
})}
</>
);
};
const Input = ({value, onChangeText, fieldType}) => {
const keyboardType = fieldType === 'Number' ? 'numeric' : undefined;
return <TextInput value={value} keyboardType={keyboardType} onChangeText={onChangeText} />
};
If you just want this input types, you could do in this way:
First, define and object for mapping your field types to html input types:
const inputTypesMapper = {
Numeric: "number",
Text: "text",
Boolean: "checkbox"
};
And thus you can render them as follow:
<div className="App">
{data.message.map(({ fieldtype, field }) => (
<input type={inputTypesMapper[fieldtype]} defaultValue={field} />
))}
</div>
Here you go an example
But if you want to render different components for each field type, you could do as follow:
First, define and object for mapping your field types to html input types:
const inputTypesMapper = {
Text: ({ value }) => {
return <input type={"text"} defaultValue={value} />;
},
MultipleOptions: ({ value }) => {
return (
<select>
{value.map(({ id, value }) => {
return <option value={id}>{value}</option>;
})}
</select>
);
}
};
And thus you can render them as follow:
return (
<div className="App">
{data.message.map(({ fieldtype, field }) => {
const renderInput = inputTypesMapper[fieldtype];
return renderInput({ value: field });
})}
</div>
);
Here you go an 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