I'm new to React, so please bear with me. For a onChange function, I need to check if a selected value in a UI is in an array.
export const getRestrictedChoices = () => {
return (<>{RestrictedChoices}</>)
};
const Choices = (
<>
// lots of choices
</>
)
const RestrictedChoices = (
<>
//subset of Choices
<Option value="Choice1">Choice1</Option>
<Option value="Choice2">Choice2</Option>
<Option value="Choice3">Choice3</Option>
<Option value="Choice4">Choice4</Option>
</>
)
I'd like to check if a value selected by user from Choices (dropdown) is in RestrictedChoices and then update state. I've tried something like this, which results in an error:
const onChoiceChange = (value) => {
if (value && getRestrictedChoices().includes(value)) {
// do something
}
}
I also tried in vain to refactor getRestrictedChoices, but with similar results:
export const getRestrictedChoices = () => {
return (<>{Choices.values()}</>)
};
Both Choices and RestrictedChoices are in a separate config file.
I'm I approaching the problem in the right way, and if so, what's the best way to test for membership in RestrictedChoices?
Hi I not 100% what your exact need but please see the below example if this helps you:
let choices = ["Ivan", "Dragmon", "Guilmon", "Voz Rasposa", "Omar"];
let restrictedChoices = ["Dragmon", "Guilmon"];
class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "" };
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const newName = e.target.value;
let msg = "";
if (restrictedChoices.find((x) => x === newName))
msg = "Your choice is " + newName;
else msg = "Your choice is " + newName + ". But it is rectricted.";
this.setState({ message: msg });
}
render() {
return (
<div>
<h1>{this.state.message}</h1>
<select onChange={this.handleChange}>
{choices.map((n) => (
<option key={n} value={n}>
{n}
</option>
))}
</select>
</div>
);
}
}
You can run this on CodeSandbox
Give this a shot to see if it gets you on the right track.
let choices = ["Ivan", "Dragmon", "Guilmon", "Voz Rasposa", "Omar"];
let restrictedChoices = ["Dragmon", "Guilmon"];
const isInRestrictedChoices = (value) => restrictedChoices.indexOf(value) > -1;
console.log(isInRestrictedChoices('Dragmon')) // true
console.log(isInRestrictedChoices('Omar')) // False
Related
I have the following code:
Parent component:
class App extends Component {
state = {
colors: []
};
async componentDidMount() {
const fetched = await fetch("./response.json");
const fetchedJson = await fetched.json();
const res = fetchedJson.colors;
this.setState({
colors: res
});
}
filterItems = (name) => {
const lowerCaseName = name.toLowerCase();
const newColors = [...this.state.colors];
const res = newColors.filter((color) => {
const lowerCaseColorName = color.name.toLowerCase();
return lowerCaseColorName.includes(lowerCaseName);
});
this.setState({
colors: res
});
};
render() {
const { colors } = this.state;
return (
<div>
<InputText filterItems={this.filterItems} />
<AllData colors={colors} />
</div>
);
}
}
And this is my Child component:
class Filter extends Component {
state = {
inputVal: ""
};
onChange = (e) => {
this.setState({
inputVal: e.target.value
});
this.props.filterItems(e.target.value);
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input
type="text"
onChange={this.onChange}
value={this.state.inputVal}
/>
</form>
);
}
}
export default Filter;
There's also another child component called AllData but its job is just displaying out data and put styling on it, so I'm not including it here.
Currently the data displayed are just:
Fire Dragon
Water Horse
Earth Bird
Wood Dog
Wind Cat
Here are my questions:
The filter function works fine when I type in a word into the search box in filter. However, when I backtrack and remove a previous character down to the whole input string, the res array doesn't return its whole original arrays but instead retains the result of the filter only when I type more.
Ex:
When I type in the string "cat", res becomes: [{name: "Wind Cat", id: 5}
However I remove that string by backtracking on the keyboard, res is still at [{name: "Wind Cat", id: 5}. Why is it not going back to returning all of the items, and how do I fix this?
I currently have this code:
onChange = (e) => {
this.setState({
inputVal: e.target.value
});
this.props.filterItems(e.target.value);
};
However, if I change it to:
this.props.filterItems(this.state.inputVal);
and console.log(name) out in the parent component at filterItems, every time I type in a string, it seems like the console.logged name only display the character before.
Ex:
If I type in the string c -> name would be "" (empty)
If I type in the string ca -> name would be c
Why is this happening?
class App extends Component {
state = {
colors: [],
filtered: [],
};
async componentDidMount() {
const fetched = await fetch("./response.json");
const fetchedJson = await fetched.json();
const res = fetchedJson.colors;
this.setState({
colors: res
});
}
filterItems = (name) => {
const lowerCaseName = name.toLowerCase();
const newColors = [...this.state.colors];
const filtered = newColors.filter(color => {
const parts = color.split(' ').map(part => part.toLowerCase());
return parts.reduce((carry, part) => {
return carry ? carry : part.startsWith(lowerCaseName);
}, false);
});
this.setState({
filtered,
});
};
render() {
const { colors, filtered } = this.state;
return (
<div>
<InputText filterItems={this.filterItems} />
<AllData colors={filtered ? filtered : colors} />
</div>
);
}
}
this happens because you filter the array and thus lose the objects. what you can do is have 2 arrays, one with all the data and one with the filtered data. apply the filter to the array with all the data and set the filtered array to the result
I have several select element generated by a map. Aside from usingĀ jQuery to access the dom elements is there a way to get all the selected values onChange
changed = () => {
// Keep track of all the selected options
}
[1,2,3].map(value => (
<select onChange={changed}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>)
Whenever I select an option I would like to keep track of the option selected in an array. If in the first select I chose 1 then the second one 2 I'd like to have an array [1,2] representing the options picked. If I then select the third option to be 3 then the new array should be [1,2,3].In this case I want three separate select and I want to keep track the options selected in each
Assume you use hooks in your code. This should be like
import React, { useState, useEffect } from "react";
const App = () => {
useEffect(() => {
console.log("selections: ", selections);
});
const [selections, setSelections] = useState({});
const changed = (value, e) => {
setSelections({ ...selections, [`${value}`]: e.target.value });
};
return (
<div>
{[1, 2, 3].map(value => (
<select key={value} onChange={e => changed(value, e)}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
))}
</div>
);
};
export default App;
UPDATE: I updated my solution, sorry for missunderstand your questions, in case of multi dropdown list, your state should construct this way:
{ "1": 1, "2": 3, "3": 1}
Key is dropdown identifier and value is the selected option for it.
I've written examples using both React class and functional component for you.
If you want to use select with multiple values, you will have to set select multiple attribute to true. Note that select is very difficult to style and you may consider using a custom Dropdown instead.
import React from 'react';
import ReactDOM from 'react-dom';
class ClassExample extends React.Component {
state = {
value: [],
}
handleOnChange = (e) => {
const { value: selectedValue } = e.target;
const { value } = this.state;
const newValue = [].concat(value);
const index = newValue.findIndex(v => v === selectedValue);
if (index > -1) {
newValue.splice(index, 1);
} else {
newValue.push(selectedValue);
}
this.setState({ value: newValue });
}
render() {
const { value } = this.state;
return (
<div>
<select
value={value}
multiple
onChange={this.handleOnChange}
>
{[1, 2, 3].map(v => <option key={v} value={v}>{v}</option>)}
</select>
<pre>
{JSON.stringify(value, null, 2)}
</pre>
</div>
)
}
}
const FunctionExample = () => {
const [value, setValue] = React.useState([]);
const handleOnChange = (e) => {
const { value: selectedValue } = e.target;
const newValue = [].concat(value);
const index = newValue.findIndex(v => v === selectedValue);
if (index > -1) {
newValue.splice(index, 1);
} else {
newValue.push(selectedValue);
}
setValue(newValue);
}
return (
<div>
<select
value={value}
multiple
onChange={handleOnChange}
>
{[1, 2, 3].map(v => <option key={v} value={v}>{v}</option>)}
</select>
<pre>
{JSON.stringify(value, null, 2)}
</pre>
</div>
)
}
const App = () => (
<>
<label>
Class : <ClassExample />
</label>
<label>
Function : <FunctionExample />
</label>
</>
)
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
Here is a working demo: https://codesandbox.io/s/react-controlled-multiple-select-g7shd?fontsize=14&hidenavigation=1&theme=dark
I have a form with select element. The values for options for select element comes from an API. So, I have to dynamically create the options. But, I am unable to get the select element from DOM.
Following is the code that have tried. I tried to access select ID element with findDOMNode. None of this is getting the element.
What do I need to do to get the element selected?
componentDidMount() {
companyUserNames()
.then(result => {
const companyUsername = result;
console.log(result);
//output ==> [ { userName: "ABC",fullName: "ABC XYZ"}, {userName:
// "DEF",fullName: "DEF QRW"}]
companyUsername.forEach(role => {
console.log(role);
const roledynamic1 = document.getElementById("name1");
console.log(roledynamic3);
//output = null
const roledynamic2 = this.refs.name1
console.log(roledynamic3);
//output = undefiend
const roledynamic3 = ReactDOM.findDOMNode(this.refs.name1)
console.log(roledynamic3);
//output = null
const newchild1 = document.createElement("option");
newchild1.value = role.userName;
newchild1.text = role.fullName;
roledynamic3.add(newchild1);
});
})
.catch(error => {
console.log(error);
});
}
render(){
return(
<form>
//some input field
<div className='select'>
<select
name='userName'
id='name1'
ref="name1"
className='input common-input-style'
maxLength='255'
value={this.state.userName.value}
onChange={this.handleInputChange}
>
<option>Name</option>
</select>
</div>
//some input field
<form/>
)
}
findDOMNode accepts a component as an argument, not a ref.
Try using the ref directly it should hold the DOM node.
Note that findDOMNode is a deprecated API and should be avoided.
Also, as Amin Paks mentioned you should consider switching to the current style of using refs with createRef
Why do you need to access DOM node when you can easily work with state in react.
Below is the working code with codesandbox link:-
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
state = {
names: []
};
companyUserNames = () => {
return new Promise(resolve => {
return resolve([
{ userName: "ABC", fullName: "ABC XYZ" },
{ userName: "DEF", fullName: "DEF QRW" }
]);
});
};
componentDidMount() {
this.companyUserNames()
.then(result => {
this.setState({
names: result
});
})
.catch(error => {
console.log(error);
});
}
render() {
const { names } = this.state;
let namesList =
names.length > 0 &&
names.map((item, i) => {
return (
<option key={i} value={item.userName}>
{item.fullName}
</option>
);
});
return (
<form>
<div className="select">
<select className="input common-input-style" maxLength="255">
{namesList}
</select>
</div>
</form>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
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.
I'm trying to make a search functionality for my app. It works with API:
http://localhost:3005/products?q=[USER INPUT HERE]
and .JSON is returned from this. I already have a working component that I want to duplicate and use it for search results display. It looks like this:
class Item extends Component {
constructor(props) {
super(props);
this.state = {
output: {},
url: {}
}
}
componentDidMount() {
fetch(this.props.url)
.then(response => response.json())
.then(data => this.setState({ output: data }));
}
render() {
const { general = {name:"", description:""} } = this.state.output;
return (
<BoxTitle>{general.name}</BoxTitle>
);
}
}
working alright, rendered this way:
let ChoosePage = (i) => {
ReactDOM.unmountComponentAtNode(document.getElementById('items'))
let urls = [
'http://localhost:3005/products/774944',
'http://localhost:3005/products/774945',
...
'http://localhost:3005/products/738471'];
let urls_sliced = urls;
if (i === 0) {
urls_sliced = urls.slice(0, 4);
} else if (i === 1) {
urls_sliced = urls.slice(4, 8);
} else if (i === 2) {
urls_sliced = urls.slice(-2);
}
let show_items = () => {
ReactDOM.render(urls_sliced.map((url)=>{
return(
<Item url={url}/>
)
}), document.getElementById('items'));
}
show_items()}
this is my input field:
const search_box = (
<form>
<Icon>search</Icon>
<input placeholder={'Search...'}></input>
</form>
);
I'm looking for a way to pass value inputted by the user to function that will convert it to link and use for getting .JSON from API and then render components mapped with this data. Managed to make only this:
let url_s = 'http://localhost:3005/products?q=' + input;
let show_results = () => {
ReactDOM.render(urls_sliced.map((url)=>{
return(
<Item url={url_s}/>
)
}), document.getElementById('items'));
}
show_results()
Help is very appreciated here :)