I am using react-selectize component for customizable dropdown which allows users to add new options.
<Dropdown
options={myOptions}
value={selectedValue}
onValueChange={value => {
this.valueUpdated(emptyStringToNull(value));
}}
createFromSearch={this.createFromSearch}
/>
My createFromSearch and onValueChange functions are as below;
createFromSearch: function(options, search){
if (search.length === 0 || (options.map(function(option){
return option.label;
})).indexOf(search) > -1)
return null;
else {
return {'label': search, 'value': search};
}
},
onValueChange: function(text) {
// update the value in state
},
Everything works fine other than this small UI issue. It shows duplicate options soon after I click .
When I click anywhere in the screen it removes this duplicate layover and showing properly. Can anyone please suggest is it styling issue or any other thing I need to do?
I able to fix this issue by trying several things. I was overriding onValueChange method of the component and passed only the value to the actual onValueChange method as below;
const onValueChangeInDropdown = props => value => {
if (value) {
props.onValueChange(value.value);
} else {
props.onValueChange(null);
}
};
This cause the above styling issue since component couldn't find out item.newOption attribute. So solution is when adding newly created item for the option list add it as item.newOption = 'true' and pass the whole item object to onValueChange method.
Related
This is something quite simple but somehow resulted in a crazy rabbit hole.
This link shows what I want:
https://www.w3schools.com/howto/howto_js_active_element.asp
Nothing special, now the thing becomes hairy for me when the elements in the navbar are rendered from an array of objects (from the specs). The approach I am following is basically rendering a list of buttons, this list of buttons is the state, since supposedly when you update a state it triggers a re-render, then when a button is clicked it "sets" the active class to false on the entire array-state then activates it only for the clicked one. So far it works.
The problem is that the active class is rendered two steps behind. One for the moment when the class in the array-state's elements are set to false, the other when the clicked element gets updated.
As far as I understand useState and setState are queues, hence those are applied asynchronously on each render, in order to avoid that and get the renders to show the current state, useEffect is utilized.
Now the thing is that I am not sure how to apply useEffect in order to achieve the immediate render of the "active" class.
This is the code I have:
import { options } from 'somewhere...'
export default function SideMenu(props){
let auxArr = []
let targetName
const [stateOptions, setStateOptions] = useState([...options])
const [currentOption, SetCurrentOption] = useState({})
function activeOption(e){
// this helps with event bubbling
if (e.target.tagName == "P" || e.target.tagName == "SPAN"){
targetName = e.target.parentElement.id
} else if (e.target.tagName == "IMG"){
targetName = e.target.parentElement.parentElement.id
} else {
targetName = e.target.id
}
// since the main state is an array of objects I am updating it
// in three steps, first the current object is "activated"
// then the main array-state gets "inactivated" to erase all
// the previous "active" classes, finally the activated object
// replaces the corresponding inactive object in the main state.
let targetElement = stateOptions.filter(e => e.id==targetName)[0]
SetCurrentOption({
id: targetElement.id,
activity:true,
img: targetElement.img,
name: targetElement.name
})
// first the "classes" are set to false, then the
// "activated" object replaces the corresponding one
// in the main object, from here comes the two
// steps delay.
auxArr = [...stateOptions]
auxArr.forEach(e => e.activity=false)
setStateOptions(auxArr)
const newOptions = stateOptions.map(e =>
e.id==currentOption.id ? currentOption : e
)
setStateOptions(newOptions)
}
return(
<aside className={styles.sideDiv}>
<nav>
{stateOptions.map(({id, img, name, activity, link}) => {
return(
<button key={id} id={id} onClick={activeOption} className={activity?styles.active:""}>
<Image src={img}/>
<p className={timeColor.theme}> {name} </p>
</button>
)
})}
</nav>
</aside>
)
}
Thanks in advance for any help you can provide.
i'm trying to develop an App with React using the Open trivia Api. I have mapped a button component (using material ui) to show the different answers for each question. I'm struggling now to target only the clicked one to apply a css property: if the answer is correct should become green, else red. The problem is the fact that once i click, all button become red or green. I tried to store the index in a state and compare the real index, but it doesn't work. here is my code:
in the main APP.js
const [clickedOne, setClickedOne] = useState({
clickedIndex: null,
});
useEffect(() => {
grabData();
}, []);
const handleClick = (choice, ke) => {
setChoice(choice);
if (choice === data.correct_answer) {
setIsCorrect(true);
} else {
setIsCorrect(false);
}
setClickedOne({ clickedIndex: ke });
grabData();
};
The mapped button inside the Render:
{answers.map((answer, index) => {
return (
<ContainedButtons
choice={handleClick}
answer={answer}
correct={data.correct_answer}
isCorrect={isCorrect}
key={index}
id={index}
clicked={clickedOne}
/>
);
})}
Inside the Button component:
const backStyle = () => {
if (clicked === id) {
if (isCorrect) {
return "green";
} else if (isCorrect === false) {
return "red";
} else {
return null;
}
}
};
return (
<div className={classes.root}>
<Button
style={{ backgroundColor: backStyle() }}
value={answer}
onClick={() => choice(answer, id)}
variant="contained"
>
{decodeURIComponent(answer)}
</Button>
When i check now inside the backstyle function if the clicked===id, now nothing happens anymore. Without that if check, i would have all buttons red or green.
Thank you guys for the help!
I have looked at your codesandbox demo, there are alot of other problems apart from the one your question is about.
First of all, each time you make a request to the API to fetch next question, you are making a request to get 10 questions instead of 1. API request URL contains a query parameter named amount which determines how many questions will be fetched on each request. Change its value to 1.
"https://opentdb.com/api.php?amount=1&encode=url3986"
Secondly, there is a lot of unnecessary code and unnecessary use of useState hook. You only need 2 things to be stored in the state, data and answers
const [data, setData] = useState({});
const [answers, setAnswers] = useState([]);
Now, coming to the original problem of detecting which button is clicked and correctly updating its background color.
To achieve the desired functionality, take following steps:
create couple of CSS classes as shown below
button.bgGreen {
background-color: green !important;
}
button.bgRed {
background-color: red !important;
}
pass a handleClick function from App component to ContainedButtons component. When a button is clicked, this click handler will be invoked. Inside the handleClick function, get the text and the button that was clicked using Event.target and depending on whether user answered correctly or not, add appropriate CSS class, created in step 1, on the button that was clicked.
Instead of using index as key for ContainedButtons in map function, use something that will be unique each time. This is needed because we want React to not re-use the ContainedButtons because if React re-uses the ContainedButtons component, then CSS classes added in step 2 will not be removed from the button.
Here's a working codesanbox demo of your app with the above mentioned steps.
In this demo, i have removed the unnecessary code and also changed the key of ContainedButtons inside map function to key={answer.length * Math.random() * 100}. You can change it to anything that will ensure that this key will be unique each time.
I'm having a heck of a time with Material UI's "Select" - about 10 hours into trying to get one working the way I'd like. I'd really appreciate some help.
This question is related to a previous one: Select MenuItem doesn't show when JSX saved to state and I suspect that if the "why" were answered on that, I might get a better idea of what's going on.
What I'm trying to accomplish is having a Select that does the following normal things:
has all the UI goodies (shows the question in the select spot, then
moves the question smaller and out of the way after you select a
non-null selection)
upon selecting something, the label shows up (as one would expect in
a drop down) rather than a blank (as I have been experiencing - check
previous question)
no warnings in the console about 'value' being undefined
when I click away from the select after selecting something, I don't
want the question label to move back on top of the answer like this:
I want a 'none' option that returns the select back to it's "empty"
form (That is to say, the question label shows at normal size in the
select)
I can set a selection to be selected by default
These shouldn't be hard tasks, but I can't for the life of me get it. It' rather embarrassing.
Then, upon selecting something, I want to save that selection (along
with the other selection options) to state (so I can save it to
localStorage so the larger form doesn't 'reset' upon page refresh)
Either way, I've currently got this JSX - effectively a cut-and-paste from the material ui demos with a map for the MenuItems:
<FormControl className={classes.formControl}>
<InputLabel htmlFor={this.props.label}>{this.props.label}</InputLabel>
<Select
value={this.state.selectLabel}
onChange={this.handleSelectChange}
inputProps={{
name: 'selectLabel',
id: this.props.label,
}}
>
{this.props.value.map(valueLabelPair =>
<MenuItem
key={this.props.XMLvalue + "_" + valueLabelPair.label}
value={valueLabelPair.value}
>
{valueLabelPair.label}
</MenuItem>
)}
</Select>
</FormControl>
the handleSelectChange looks like this -- again, exactly the same as the material UI demo.
handleSelectChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
This kind of works except the console gives me the following error:
Failed prop type: The prop value is marked as required in
SelectInput, but its value is undefined.
and the selected option and question label go on top of each other after you click away, like so:
Further, if I try to add in this code (in the componentDidMount function) with the goal of being able to pass in the 'selected'/default option...
componentDidMount() {
for (var i = 0; i < this.props.value.length; i++) {
if(this.props.value[i].selected) {
// *works* console.log("selected found: " + this.props.value[i].label);
this.setState({selectLabel:this.props.value[i].label});
}
}
}
it does not update the give me a default answer and also gives me the following additional error in the console (in addition to all issues above):
Warning: A component is changing an uncontrolled input of type hidden
to be controlled. Input elements should not switch from uncontrolled
to controlled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component.
What am I missing?
Just define selectLabel into constructor:
constructor () {
super()
this.state = {
selectLabel:'',
}
}
I am unsure as to why the above solution did not work.
However, I rebuilt the Select to return "option" elements instead of "MenuItem" elements with the following function:
buildSelectOptions(optionsPairs) { // note, this references props and blank option could be split out for reuse
var JSX_return = [];
if (this.props.includeBlank && this.props.includeBlank === true) {
JSX_return.push(<option key="nada" value="" />);
}
for (var optionLabel in optionsPairs) {
JSX_return.push(<option key={optionLabel} value={optionsPairs[optionLabel]}>{optionLabel}</option>);
}
return JSX_return;
}
My render now looks like this:
<FormControl className={classes.formControl}>
<InputLabel htmlFor="age-native-simple">{this.props.label}</InputLabel>
<Select
native
value={this.state.value}
onChange={this.handleSelectChange('value')}
inputProps={{
name: this.props.label,
id: this.props.id,
}}
>
{this.buildSelectOptions(this.props.options)}
</Select>
<FormHelperText>{this.props.helperText}</FormHelperText>
</FormControl>
And the event handler looks like this:
handleSelectChange = name => event => { //FUTURE: combine the handlers (or split out question types to sub-components)
this.setState({ [name]: event.target.value },
() => this.props.stateChangeHandler(this)
);
};
the props passed to this object look like this:
{
"key": "test4",
"id": "test4",
"label": "Question Label 4",
"XMLValue": "XMLQ4",
"type": "DropDown",
"includeBlank": true,
"helperText": "PCodes FTW!",
"options": {
"No oe": "NA",
"My1": "One",
"My2": "Two",
"My3": "three"
},
"value": "One"
}
One of the key concepts for me was to learn that the value field on the Select element should be pointed at an item in this.state. Then, the onChange needs to pass the name of that state variable (which, confusingly, I have named 'value') to the eventHandler function.
The double arrow function header (curried function) of the handleSelectChange function still confuses me... (I don't understand how the 'event' property is getting there, given I'm calling this function with a single parameter)... but this works for now and I can try to refactor into syntax I am comfortable with (ie: function(a, b) { *do something* } ) at some future date. (yeah....)
Add class radio_label :
<FormControlLabel value="male" label="Male" control={<Radio />} className="radio_label"/>
Add css property :
.radio_label{
color:black
}
I'm trying to create filters in react, where i manipulate the url to return me products based on colours, cost etc
its working so if you change ?color=red to ?color=white in the url it will display different products on the page
it's also working whereby if you select the colours in my checkbox filter component it will update the url and then display the new products. i.e click on red will change the url from /sport to /sport?color=red and then returns me just the products with red
however this is my problem
if I manually change the url, I then want the checkbox checked so I tried to do this:
checked={option.text === this.getParams() ? true : false}
this does actually work but then I lose the ability to actually select and deselect the checkbox. any ideas how I can get it to do both? I guess making it a controlled and uncontrolled component simultaneously??
You need to store the filters in the state. like in your constructor you can init your state with the query parameter and then change the state upon checkbox change.
You could try something like this. You will need to change this code according to your usage, here I am assuming, this.getParams('color') will return an array of all the selected colors.
constructor state init
constructor(props) {
super(props);
this.state = {
filters: this.getParams('color') // consedering, it will return array
}
}
default check the checkbox
defaultChecked ={this.state.filters.indexOf(option.text) === -1 ? false : true}
onChange={() => this.toggleCheckbox(option.text)}
for toggling it
// if not present, then add it
// else remove it
toggleCheckbox(option) {
if (this.state.filters.indexOf(option) === -1) {
this.setState({
filters: [...this.state.filters, option]
})
} else {
this.setState({
filters: this.state.filters.filter(text => text !== option)
})
}
}
You should set the state of the checkbox in the component state, and then update that state when it's clicked. You can set the initial state based on the url on construct or mount.
Something like this:
constructor(props) {
super(props);
const isChecked = this.props.match.params.checkbox === 'true';
this.state = {
checkbox: isChecked
}
}
And then in your checkbox:
<input type="checkbox" checked={this.state.checkbox} onChange={() => this._toggleCheckbox()} />
And the method to turn it on and off would be something like:
toggleCheckbox() {
this.setState({
checkbox: !this.state.checkbox // will toggle to the opposite of the current state
});
}
Note that this is has not been tested but has been written based on the information you gave. The principle behind this is what you need to do. It may also be useful to set the state of the checkbox initially within componentDidMount(), rather than constructor(), but that's up to you. The onChange function of the checkbox uses ES6, but you could bind the function if you prefer or do not use ES6 with this._toggleCheckbox().bind(this)
Edit
To update the checkbox when the url is changed, rather than updating it on click, you could change the toggle method to redirect the browser, and then update the checkbox within componentWillReceiveProps.
Taken from my own code with react-router you can use 'this.props.match.params' to find the url parameters. I use react-router-dom package to update the url. So for instance:
This will give you access to this.props.history.
import { withRouter } from 'react-router-dom';
toggleCheckbox() {
// Check the current state of the checkbox and update the url to the opposite
let toCheck = this.props.match.params.checkbox === 'true' ? 'false' : 'checked';
this.props.history.push('/?checkbox=' + toCheck);
}
componentWillReceiveProps(newProps) {
// Check the new url and update the checkbox if it is different from the checkbox state
if(newProps.match.params.checkbox != this.state.checkbox) {
this.setState({checkbox: newProps.match.params.checkbox});
}
}
So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>