react search filter with select option - javascript

i have implemented search filter to my react app, everything worked fine till i made select tag, to change search filter criteria.
class BookList extends Component {
state = {
search: '',
selectedValue: 'name',
options: [
{
name: 'Name',
value: 'name',
},
{
name: 'Author',
value: 'author',
},
{
name: 'ISBN',
value: 'isbn',
}
]
}
updateSearch (e) {
this.setState({search: e.target.value});
}
selectedValueHandler (e) {
this.setState({selectedValue: e.target.value});
}
render () {
if (this.state.selectedValue === 'name') {
let filteredBooks = this.props.books.filter(book => {
return book.name.toLowerCase().indexOf(this.state.search) !== -1;
})
} else if (this.state.selectedValue === 'author') {
let filteredBooks = this.props.books.filter(book => {
return book.author.toLowerCase().indexOf(this.state.search) !==
-1;
})
} else if (this.state.selectedValue === 'isbn') {
let filteredBooks = this.props.books.filter(book => {
return book.isbn.indexOf(this.state.search) !== -1;
})
}
return (
<div>
<div className='SearchInput'>
<input type='text'
value={this.state.search}
onChange={this.updateSearch.bind(this)} />
<select
id="searchSelect"
name="searchSelect"
onChange={this.selectedValueHandler.bind(this)} >
{this.state.options.map(item => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</select>
</div>
<div className='BookList'>
<ul>
{filteredBooks.map(book => {
return <Book key={book.book_id} name={book.name} author={book.author} isbn={book.isbn} />
})}
</ul>
</div>
</div>
)
}
};
export default BookList;
when i implement this code i am getting error: Line 69: 'filteredBooks' is not defined no-undef.
Tried to put this.state.selectedValue instead of name but it also doesn't work.
Any ideas how to fix issue?

let variables are locally scoped to the nearest wrapping curly braces. Define the variable above the if statements.
render () {
let filteredBooks;
if (this.state.selectedValue === 'name') {
filteredBooks = this.props.books.filter(book => {
return book.name.toLowerCase().indexOf(this.state.search) !== -1;
})
...
Unrelated, here's one way you could shorten your code:
const { books } = this.props;
const { search } = this.state;
const filteredBooks = books.filter(book =>
book[search].toLowerCase().includes(search)
)

Related

REACT-SELECT defaultValue in CustomDropdown not working

I want the default value of my dropdown to be defaultValue={item.taste} (value from match.json) but it's not working... (go to /menu/Menu1 and Pea Soup)
import Select from "react-select";
export default function CustomDropdown({ style, options, defaultValue }) {
return (
<div style={style}>
<Select options={options} defaultValue={defaultValue} />
</div>
);
}
MenuItemDisplay:
export default function MenuItemDisplay() {
const { menuId, itemId } = useParams();
const { match } = JsonData;
const matchData = match.find((el) => el._id_menu === menuId)?._ids ?? [];
const item = matchData.find((el) => el._id === itemId);
const styles = {
select: {
width: "100%",
maxWidth: 150
}
};
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
...
return (
<>
<div className="TextStyle">
{"Taste "}
<CustomDropdown
style={styles.select}
options={TASTE}
defaultValue={item.taste}
//The default value is not working only if it's
//TASTE[0]
/>
</div>
...
</>
);
}
Here the link for the code
As defaultValue you need to pass one of the objects of the TASTE array. You can do this:
<CustomDropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find(t => t.label === item.taste)}
/>

How to scroll a list item into view using scrollintoview method using reactjs?

i want to move a list item into view using scrollIntoView method using reactjs.
What i am trying to do?
i have an array of objects stored in variable some_arr and i display those values in a dropdown menu. when user presses down key then the dropdown item gets highlighted. also using up arrow key to navigate up in the dropdown menu.
and clicking enter key will select the dropdown item and replaces the value in the input field.
I have implemented code below and it works fine. but when user presses down arrow key and highlighted dropdown menu is not in view i want it to be visible to user.
So to implement that i have used ref (this.dropdown_item_ref) to the dropdown item. however this ref always points to last item in the dropdown menu. meaning consider i have
some_arr = [
{
id:1,
name: somename,
},
{
id: 2,
name: fname,
},
{
id: 3,
name: lname, //ref is always pointing to this item
},
],
so here the ref is always pointing to lname in the dropdown menu.
Below is what i have tried and is not working,
class Dropdownwithinput extends React,PureComponent {
constructor(props) {
super(props);
this.list_item_ref = React.createRef();
this.state = {
input_val: '',
dropdown_values: [],
dropdown_item_selection: 0,
};
}
componentDidMount = () => {
const values = [
{
id:1,
name: somename,
},
{
id: 2,
name: fname,
},
{
id: 3,
name: lname, //ref is always pointing to this item
},
],
this.setState({dropdown_values: values});
}
handle_key_down = (event) => {
if (this.state.dropdown_values > 0) {
if (event.keyCode === 38 && this.state.dropdown_item_selection
> 0) {
this.setState({dropdown_item_selection:
(this.state.dropdown_item_selection - 1) %
this.state.dropdown_values.length});
this.list_item_ref.current.scrollIntoView();
} else if (event.keyCode === 40) {
this.setState({dropdown_item_selection:
(this.state.dropdown_values_selection + 1) %
this.state.dropdown_values.length});
this.list_item_ref.current.scrollIntoView();
}
if (event.keyCode === 13) {
event.preventDefault();
const selected_item =
this.state.dropdown_values[this.state.user_selection];
const text = this.replace(this.state.input_val,
selected_item);
this.setState({
input_val: text,
dropdown_values: [],
});
}
}
replace = (input_val, selected_item) => {
//some function to replace value in input field with the
//selected dropdown item
}
render = () => {
return (
<input
onChange={this.handle_input_change}
onKeyDown={this.handle_key_down}/>
<div>
{this.state.dropdown_values.map((item, index) => (
<div key={index} className={"item" + (index ===
this.state.dropdown_item_selection ? ' highlight'
: '')}>
{item.name}
</div>
))}
</div>
)
};
}
}
Could someone help me fix this. thanks.
I have adapted a bit your code:
import React from "react";
class Example extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
const dropdownValues = Array.from({ length: 100 }, (_, k) => k).reduce(
(acc, curr) => {
return acc.concat([{ id: curr, name: `${curr}.so` }]);
},
[]
);
this.state = {
input_val: "",
dropdownValues,
selectedItem: 0
};
this.listRefs = dropdownValues.reduce((acc, current, index) => {
acc[index] = React.createRef();
return acc;
}, {});
}
componentDidMount() {
window.addEventListener("keydown", this.handleKeyDown);
}
componentWillUnmount() {
window.removeEventListener("keydown", this.handleKeyDown);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.selectedItem !== this.state.selectedItem) {
this.listRefs[this.state.selectedItem].current.scrollIntoView();
}
}
handleKeyDown = event => {
const keyCodes = {
up: 38,
down: 40
};
if (![38, 40].includes(event.keyCode)) {
return;
}
this.setState(prevState => {
const { dropdownValues, selectedItem } = prevState;
let nextSelectedItem;
if (keyCodes.up === event.keyCode) {
nextSelectedItem =
dropdownValues.length - 1 === selectedItem ? 0 : selectedItem + 1;
}
nextSelectedItem =
selectedItem === 0 ? dropdownValues.length - 1 : selectedItem - 1;
return { ...prevState, selectedItem: nextSelectedItem };
});
};
replace = (input_val, selected_item) => {
//some function to replace value in input field with the
//selected dropdown item
};
render() {
return (
<>
<input
onChange={this.handle_input_change}
onKeyDown={this.handle_key_down}
/>
<button
type="button"
onClick={() => this.setState({ selectedItem: 50 })}
>
Focus element 50
</button>
<div ref={this.listRef}>
{this.state.dropdownValues.map((item, index) => (
<div key={index} ref={this.listRefs[index]}>
<div
style={
this.state.selectedItem === index
? { background: "yellow" }
: {}
}
>
{item.name}
</div>
</div>
))}
</div>
</>
);
}
}
export default Example;

Data exists but `Cannot read property 'map' of undefined`

I am trying to create a div for each item in an array that is a property of this.state
However, I am getting Cannot read property 'map' of undefined on the line, return outOfBudget.values.map((val, j)
Most of the posts on this subject have an issue because the data doesn't actually exist. I tried their solutions by wrapping the problematic line in an if(outOfBudget) statement, but the error persisted. I also log outOfBudget to console and see that it indeed exists.
Am I defining it incorrectly?
const BrokeBudget = ({outOfBudget}) => {
return outOfBudget.values.map((val, j) => {
return (
<div>
<p>{val.name}</p>
<p>{val.value}</p>
</div>
);
});
};
class Budget extends React.Component {
state = {
remainingBudget: 1600,
data,
pieChartData: [],
outOfBudget: []
};
handleInputChange = event => {
let { value, id, name } = event.target;
value = parseInt(value, 10);
const selectedQuestions = Object.assign(
{},
this.state.data.selectedQuestions
);
if (!selectedQuestions[name]) {
selectedQuestions[name] = {};
}
selectedQuestions[name][id] = value;
let newBudget = this.state.remainingBudget - value;
if( newBudget >= 0){
let pieSlice =
{
x: name,
y: value
};
console.log(pieSlice);
this.setState({
data: {
...this.state.data,
selectedQuestions
},
remainingBudget: newBudget,
pieChartData: this.state.pieChartData.concat(pieSlice),
});
}
else{
let beyondBudget = {genre: name, amount: value}
this.setState({
data: {
...this.state.data,
selectedQuestions
},
remainingBudget: newBudget,
pieChartData: this.state.pieChartData,
outOfBudget: {...this.state.outOfBudget, beyondBudget}
});
}
};
render() {
const { data, remainingBudget, pieChartData, outOfBudget } = this.state;
const questions = data.questions;
return (
<div>
{questions.map((q, i) =>
<UL key={i}>
<li>
<h4>{q.text}</h4>
</li>
<li>
<Options
state={this.state}
q={q}
i={i}
handler={this.handleInputChange}
/>
</li>
</UL>
)}
{Object.keys(data.selectedQuestions).length === 3 &&
<div>
<VictoryPie
colorScale = "blue"
data = {this.state.pieChartData}
labels= {d => `${d.x}: ${d.y}%`}
style={{ parent: { maxWidth: '50%' } }}
/>
< BrokeBudget
outOfBudget={outOfBudget}
/>
</div>
}
</div>
);
}
}
Please ignore any strange cases (like UL I use Emotion for styling)
What is .values ?
Looks like you need return outOfBudget.map((val, j) => { without the .values
Hope this helps.

How to list all suggestions and filtered suggestions based on user input using reactjs?

i want to show all available usernames when user types # in input field and filtered usernames when user enters anything after # character.
I have implemented like below,
class UserMention extends React.purecomponent {
constructor(props) {
super(props);
this.state = {
text: '',
user_mention: false,
};
this.user='';
}
user_list = [
{name: 'John smith'},
{name: 'Jenna surname2'},
{name: 'Tuija rajala'},
];
get_user = s => s.includes('#') && s.substr(s.lastIndexOf('#') +
1).split(' ')[0];
handle_input_change = (event) => {
let user_mention;
this.user = this.get_user(event.target.value);
if (event.target.value.endsWith('#')) {
user_mention = true;
} else {
user_mention = false;
}
this.setState({
user_mention: user_mention,
[event.target.name]: event.target.value,
});
};
get_text_with_user_mention = (text, selected_user) => {
let user_name = selected_user;
let text_without_user_mention;
text_without_user_mention = text.slice(0,
text.lastIndexOf('#'));
return text_without_user_mention + user_name;
};
handle_select_value = (selected_user) => {
let text;
text = this.get_text_with_user_mention(this.state.text,
selected_user);
this.setState({
text: text,
user_mention: false,
});
this.user = false;
};
render = () => {
let suggested_values = [];
if (this.state.user_mention) {
suggested_values = this.user_list
.map((o) => { return {user_name: o.user_name};});
}
if (this.user) {
suggested_values = this.user_list
.filter(user => user.user_name.indexOf(this.user) !==
-1)
.map((o) => {return {user_name: o.user_name};});
}
return (
<input
required
name="text"
value={this.state.text}
onChange={this.handle_input_change}
type="text"/>
{this.state.user_mention &&
<SelectInput
on_change={this.handle_select_value}
values={suggested_values}/>}
{this.user &&
<SelectInput
on_change={this.handle_select_value}
values={suggested_values}/>}
);
};
}
As you see from above code, i am modifying suggested_values based on this.user and this.state.user_mention state. Can someone help me refactor or modify this a bit more nicer. thanks.
This is another approach using React hooks, instead of classes. If you've never worked with hooks, give it a try. You will enjoy it. It's much simpler in my opinion.
I also added a username property. It's much better if you work with a string that doesn't allow spaces when you're tagging someone. You can also display the full name with spaces along with the username, if you wish.
Ex:
John Smith (#johnsmith)
function App() {
const inputRef = React.useRef(null);
const [inputValue, setInputValue] = React.useState('');
const [userList,setUserList] = React.useState([
{name: 'John smith', username:'johnsmith'},
{name: 'Jenna surname2', username:'jennasurname2'},
{name: 'Tuija rajala', username:'tuijarajala'}
]
);
const [showSuggestions,setShowSuggestions] = React.useState(false);
const [suggestionList,setSuggestionList] = React.useState(
['johnsmith','jennasurname2','tuijarajala']
);
function onChange(event) {
const regexp = /#[a-zA-Z0-9]*$/;
if (regexp.test(event.target.value)) {
setShowSuggestions(true);
}
else {
setShowSuggestions(false);
}
setInputValue(event.target.value);
}
function focusInput() {
inputRef.current.focus();
}
return(
<React.Fragment>
<input ref={inputRef} type='text' value={inputValue} onChange={onChange}/>
{showSuggestions &&
<Suggestions
inputValue={inputValue}
suggestionList={suggestionList}
applyMention={onChange}
focusInput={focusInput}
/>
}
</React.Fragment>
);
}
function Suggestions(props) {
function selectSuggestion(username) {
const regexp = /#[a-zA-Z0-9]*$/;
const newValue = props.inputValue.replace(regexp,username + ' ');
props.applyMention({target: {value: newValue}}); // THIS MIMICS AN ONCHANGE EVENT
props.focusInput();
}
const suggestionItems = props.suggestionList.map((item) =>
<div className="item" onClick={()=>selectSuggestion('#' + item)}>#{item}</div>
);
return(
<div className="container">
{suggestionItems}
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
.container {
border: 1px solid silver;
width: 150px;
}
.item {
cursor: pointer;
}
.item:hover {
color: blue;
}
input {
width: 300px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
You can simplify your code by doing something like this.
See sandbox: https://codesandbox.io/s/react-example-kgm2h
import ReactDOM from "react-dom";
import React from "react";
class UserMention extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
user_list: [
{ name: "John smith" },
{ name: "Jenna surname2" },
{ name: "Tuija rajala" }
],
suggestions: []
};
}
handleOnChange = e => {
const { value } = e.target;
const { user_list } = this.state;
//show all user suggestions
if (value.includes("#") && value.indexOf("#") === value.length - 1) {
this.setState({
text: value,
suggestions: [...this.state.user_list]
});
//show matching user suggesstions
} else if (value.includes("#") && value.length > 1) {
const stringAfterAt = value.slice(value.indexOf("#") + 1).toLowerCase();
const newSuggestions = user_list.filter(user => {
return user.name.toLowerCase().includes(stringAfterAt);
});
this.setState({
text: value,
suggestions: newSuggestions
});
//display no users if they do not use the # symbol
} else {
this.setState({
text: value,
suggestions: []
});
}
};
createSuggestionsList = () => {
const { suggestions } = this.state;
return suggestions.map(user => {
return <div>{user.name}</div>;
});
};
render = () => {
return (
<div>
<input
required
name="text"
value={this.state.text}
onChange={this.handleOnChange}
type="text"
/>
{this.createSuggestionsList()}
{/* <SelectInput value={this.state.suggestions}/> */}
</div>
);
};
}
ReactDOM.render(<UserMention />, document.getElementById("root"));
I'm not entirely sure how you want to render the suggested users, but you can always just pass down this.state.suggestions as a prop to the SelectInput component.
Main takeaway is to use an additional array in our state for suggestions and update it as the user types into the input. We call {this.createSuggestionsList()} inside render to dynamically create the markup for each suggested user. Or as mentioned above, just pass down the suggestions as a prop.

How to create an array based on multiple inputs

I am trying create an array with some objects in it, Im trying to gather the data from multiple inputs. I am creating a restaurant Menu, where I will have different titles such as Breakfasts, Entrees... and under each title I will have different plates.
Im trying to create an array like this:
menu: [
[ 'Lunch',
[{plate: 'Rice and Beans', description: 'Rice and Beans for Lunch', price: 50.49 }]
]
[ 'Dinner',
[{plate: 'Some Dinner', description: 'Dinner Description', price: 35.49 }]
]
]
The question is, how do I add first a Title, and under that title how do I add plates?
I also wanted to know how to make it, so I made it for practice. I hope it helps.
import React from 'react';
class MenuInput extends React.Component {
render() {
const {id, handleInput} = this.props;
return (
<div>
Title : <input name="title" onChange={(e) => handleInput(id, e)}/>
Plate : <input name="plate" onChange={(e) => handleInput(id, e)}/>
Description : <input name="description" onChange={(e) => handleInput(id, e)}/>
Price : <input name="price" onChange={(e) => handleInput(id, e)}/>
</div>
)
}
}
export default class Menu extends React.Component {
state = {
inputCount: 1,
inputData: [[]],
result: []
}
saveData = (e) => {
const {inputData, result} = this.state;
inputData.forEach(input => {
const {title, plate, description, price} = input;
const findInputIndex = result.findIndex(data => data.indexOf(title) >= 0);
if (findInputIndex >= 0) {
const [menuName, menuList] = result[findInputIndex];
result[findInputIndex] = [menuName, [...menuList, {plate, description, price}]]
} else {
result.push([title, [{plate, description, price}]])
}
});
this.setState({
result
})
}
handleInput = (id, e) => {
const {name, value} = e.target;
const {inputData} = this.state;
inputData[id] = {...inputData[id], [name]: value};
this.setState({
inputData
})
}
addInput = () => {
const {inputCount, inputData} = this.state;
this.setState({
inputCount: inputCount + 1,
inputData: [...inputData, []]
})
};
getInputList = () => {
const {inputCount} = this.state;
let inputList = [];
for (let i = 0; i < inputCount; i++) {
inputList.push(<MenuInput id={i} key={i} handleInput={this.handleInput}/>)
}
return inputList
}
render() {
const {result} = this.state;
console.log(result)
return (
<div>
{this.getInputList()}
<button onClick={this.addInput}>Add Plate</button>
<br/>
<button onClick={this.saveData}>save</button>
{
result.length > 0 && result.map(res => {
const [menuName, menuList] = res;
return (
<div key={menuName}>
<strong>Title : {menuName}</strong>
{menuList.map(menu => {
const {plate, description, price} = menu;
return(
<div key={plate}>
<span style={{marginRight : '10px'}}>plate : {plate}</span>
<span style={{marginRight : '10px'}}>description : {description}</span>
<span>price : {price}</span>
</div>
)
})}
</div>
)
})
}
</div>
)
}
}

Categories