I am having trouble coming up with a method that allows me to toggle multiple displays on and off without hard-coding a function for each displayed component.
I was wondering if there is a way to toggle these display states with one function kind of like and onChange event.
this.state = {
showOne: false,
showTwo: false
}
display = () => {
let { name, value } = e.target
this.setState({ [name]: !value })
}
return(
<button name={showOne} value={this.state.showOne} onClick={this.display}>
{!showOne
? (
null
) : (
<div><ComponentOne/></div>
)}
</button
<button name={showtwo} value={this.state.showTwo} onClick={this.display}>
</button
{!showTwo
? (
null
) : (
<div><ComponentTwo/></div>
)}
this only somewhat works the issue is that it changes the state to a string instead of a Boolean. aka showOne: false => showOne: 'false'.
I know the name and value properties are for inputs but I was wondering if there was something similar along these lines so I can have one function that allows the displaying/un-displaying of multiple components.
You can set them up with an inline function that passes in an identifier, which is then used as a key in the state:
onClick={() => toggle(itemName)}
toggle = (itemName) => {
this.setState({ [itemName]: !this.state[itemName] });
}
Another option is to define a component for the button that takes name and onClick props, and have the component’s internal onClick invoke the prop onClick with the name. (It’s hard to write example code from the phone. I can update this if it isn’t clear.)
toggleDisplay = (name) => {
this.setState({
[name]: !this.state[name]
})
}
onClick={()=> this.toggleDisplay('showOne')}
Worked Tyvm
Related
I have a Material UI Autocomplete combo-box child component class that fetches results as the user types:
...
fetchIngredients(query) {
this.sendAjax('/getOptions', {
data: {
q: query
}
}).then((options) => {
this.setState({
options: options
});
});
}
...
<Autocomplete
options={this.state.options}
value={this.state.value}
onChange={(e, val) => {
this.setState({value: val});
}}
onInputChange={(event, newInputValue) => {
this.fetchIngredients(newInputValue);
}}
renderInput={(params) => {
// Hidden input so that FormData can find the value of this input.
return (<TextField {...params} label="Foo" required/>);
}}
// Required for search as you type implementations:
// https://mui.com/components/autocomplete/#search-as-you-type
filterOptions={(x) => x}
/>
...
This child component is actually rendered as one of many in a list by its parent. Now, say I want the parent component to be able to set the value of each autocomplete programmatically (e.g., to auto-populate a form). How would I go about this?
I understand I could lift the value state up to the parent component and pass it as a prop, but what about the this.state.options? In order to set a default value of the combo-box, I'd actually need to also pass a single set of options such that value is valid. This would mean moving the ajax stuff up to the parent component so that it can pass options as a prop. This is starting to get really messy as now the parent has to manage multiple sets of ajax state for a list of its Autocomplete children.
Any good ideas here? What am I missing? Thanks in advance.
If these are children components making up a form, then I would argue that hoisting the value state up to the parent component makes more sense, even if it does require work refactoring. This makes doing something with the filled-in values much easier and more organized.
Then in your parent component, you have something like this:
constructor(props) {
super(props);
this.state = {
values: [],
options: []
};
}
const fetchIngredients = (query, id) => {
this.sendAjax('/getOptions', {
data: {
q: query
}
}).then((options) => {
this.setState(prevState => {
...prevState,
[id]: options
});
});
}
const setValue = (newValue, id) => {
this.setState(prevState => {
...prevState,
[id]: newValue
};
}
render() {
return (
<>
...
{arrOfInputLabels.map((label, id) => (
<ChildComponent
id={id}
key={id}
value={this.state.values[id]}
options={this.state.options[id]}
fetchIngredients={fetchIngredients}
labelName={label}
/>
)}
...
</>
This is a continuation on from:
How to create a handler function which 'handles' multiple textfields but also character counter textfields as well
I now want to use this handler to 'handle' a drop down along with textfields. I have this so far:
public handleChange = (evt: any) => {
const {value} = (evt.target as any);
const obj = {
[evt.target.name]: value,
};
this.setState(prevState => ({prevState, ...obj}));
}
Which works for textfields.
I now want it to handle a Dropdown component from office-ui-fabric. I've tried creating a completely different handler like this:
public onDropDownChange = (evt: any) => {
const {value} = (evt.target as any);
this.setState({ value: evt.target.value});
}
The above, experimental, onDropDownChange does not work. It gives an error saying value does not exist on event. I'm fairly sure this is because whatever is returned from a drop down is not 'value' and could quite well be 'item'.
I could write a function specifically for this drop down but I'm trying to cut down the amount of code written using handlers.
JSX:
<Dropdown
selectedKey={this.state.SelectedJobPlanDD}
placeholder="Select..."
onChange={this.onDropDownChange}
options={jobPlanDDOptions}
defaultSelectedKey={this.state.JobPlanDD}
/>
State:
SelectedJobPlanDD: undefined,
JobPlanDD: null,
BTW: If I log the change of the drop down I get undefined, so that's the start of the problem.
Also, here is a 'standalone' function that I've used in the past for drop downs, it may help:
private _dropDownFunction = (item: IDropdownOption) => {
this.setState({ SelectedItem: (item && item.key) ? item.key.toString() : null })
Any idea what I could use? My understanding of event handlers and ultimately basic JS logic is limited, but I learn a great deal from any advice given on SO.
UPDATE: From LevelGlow's answer I've been able to return the selected item from the drop down. I'm using this for the handler:
public onDropDownChange = (evt: any, item) => {
// const {item} = (evt.target as any);
console.log(evt+'evt with text');
console.log(evt); // THIS IS THE ONE THAT SHOWS THE SELECTED ITEM!
console.log(item.text+'item.text with text'); //Shows undefined
console.log(item); //shows 3 (I guess this is id of the item)
console.log(item.key+'item.key with text'); //shows undefined.
this.setState({
});
}
Now I'm not sure how I'd implement this into the handler to update the state item.
To obtain the selected item from the Dropdown, structure the Dropdown's onChange handler as the docs say, example below:
public onDropDownChange = (evt, item) => {
console.log(item.key);
console.log(item.text);
};
The onChange event here has a DOM object as target which stores only the text of the dropdown and it can be accessed by
evt.target.querySelector('span').innerText;
You can add the id prop to your Dropdown:
<Dropdown
id="JobPlanDD"
...
/>
Now the component generates a new id for you in the previously mentioned span tag and you can access it by calling
evt.target.querySelector('span').id; which returns "JobPlanDD-option"
And we will be using it as key for the state.
So your selectedKey should be:
selectedKey={(this.state["JobPlanDD-option"]) ? (this.state["JobPlanDD-option"]).key : undefined}
What this line doing is: do we have JobPlanDD-option in out state? if yes, we'll pass the JobPlanDD-option item's key to selectedKey, if we don't, we'll pass undefined.
Structure your shared handler like this:
public handleChange = (evt: any, item): void => {
if(evt.target.value){
//handle textfields here
} else {
//handle dropdowns here
const id = evt.target.querySelector('span').id; //get the span's id
//update state with the generated id and the selected option
this.setState({ [id] : item });
}
}
Since we updated the shared handler, we should update also how TextFields are calling it:
<TextField
onChange={(e) => this.handleChange(e, null)}
...
/>
Now both text fields and dropdowns should work with this shared handler.
I'm attempting to have my onClick method handleClick set the state of the active and groupCardInfo properties. active in particular is a boolean, and I'm using this bool value to determine whether a side menu item should be expanded or not.
SideMenuContainer component that calls handleClick:
render() {
if (this.props.active == true){
return (
<ParentContainer>
<p onClick={this.props.handleClick(this.props.properties)}>{this.props.parentName}</p>
<NestedContainer>
{this.props.properties.map(propertyElement => {
return (
<NestedProperty onClick={() => { this.props.changeInfoList(propertyElement.name, propertyElement.data_type, propertyElement.app_keys)}} >
{propertyElement.name}
</NestedProperty>
);
})}
</NestedContainer>
</ParentContainer>
);
}
The issue is that clicking that <p> results in handleClick running multiple times. so rather than toggling the active value from false to true, it toggles it back and forth multiple times so that it goes from false back to false again.
What's incorrect with the way I'm structuring this method in the parent App.js that's causing this?:
handleClick(properties){
console.log("toggle click!")
// this.setState({active : !this.state.active});
this.setState({
active: !this.state.active,
groupedCardInfo: properties
})
console.log("the active state is now set to: " + this.state.active)
}
It's because you are invoking the function in event handler. The first time render runs it will execute your event handler. You can do this like your other onClick handler:
<p onClick={() => { this.props.handleClick(this.props.properties) }}>{this.props.parentName}</p>
Or you can do it like this:
<p onClick={this.props.handleClick}>{this.props.parentName}</p>
But then you would have to change how you reference properties in your click handler. Like this:
handleClick(){
const properties = this.props.properties
this.setState({
active: !this.state.active,
groupedCardInfo: properties
})
console.log("the active state is now set to: " + this.state.active)
}
Try using arrow function, like you did on the other onClick:
<p onClick={() => this.props.handleClick(this.props.properties)}>
Calling this.props.handleClick... while rendering, invokes it.
Then, setting the state, makes the component to re render.
I'm displaying data in ag-grid-react and grid has come conditional cell rendering based on state. Here is my code. This is working on first run and displaying "YEAH" button. when i clicked button i want to change with NOOO button
This is state
this.changebutton = this.changebutton.bind(this);
this.state= {
isyes = "yes"
}
This is ag-grid-cell-renderer
cellRendererFramework: (params) => {
return <div>
{
this.state.isyes === "yes" ?
<button onClick= {() => this.changebutton()}>YEAH</button>
:
<button onClick= {() => this.changebutton()}>NOOOO</button>
}
</div>
}
this is state changer
changebutton() {
this.setState({isyes: "no" })
console.log(this.state.isyes)
}
I seeing state is changing properly, But doesn't see any change of button. why?
Your code seems incomplete to check your situation. First thing that comes in mind is the expression is evaluated once (maybe, as it appears as a method of an object not directly in render function) and is never retriggered.
Also notiche that setState() is async so you should not:
this.setState({isyes: "no" });
console.log(this.state.isyes);
instead you should:
this.setState(
{isyes: "no" },
() => console.log(this.state.isyes);
);
Try with:
api.refreshCells()
ref: ag-grid.com/javascript-grid-cell-rendering-components
I'm trying to use a text input to filter a ViewList but it seems like because the initial state of 'text' is "", it always loops in a way everytime a type something it goes back to "" deleting everything a type
The filter function goes
filterSearch (texto) {
const newData = this.Data.filter((item) => {
const itemData = item.nombre.toUpperCase()
const textData = this.texto.toUpperCase()
return itemData.indexOf(textData) > -1
})
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newData),
text: texto
})
}
and is called from a textInput
<TextInput
style={styles.busqueda}
placeholder= 'Buscar'
onChangeText={(text) => this.filterSearch.bind(text)}
value={this.state.text}
>
</TextInput>
it seems like filterSearch isnt being called the right way...
There are 2 changes you need to do:
1/ Use the correct syntax to execute your function:
onChangeText={(text) => this.filterSearch(text)}
Actually, the onChangeText function was lexical bound to the component already (by using () => {})
2/ You also need to bind the filterSearch function to the react component too, then you can use this inside it (by either way below):
a) Using lexical binding:
filterSearch = (texto) => {
//...
}
b) Bind the function in your constructor:
constructor(props) {
super(props);
// ...
this.filterSearch = this.filterSearch.bind(this);
}
onChangeText={(text) => this.filterSearch(text)}
or
onChangeText={this.filterSearch.bind(this)}