there are three drop down menus in the initial state.
after I select the first drop down the second drop down values gets loaded
after I select the second drop down values.
a new set of drop down loads.
when I select remove button of the second set.
it doesnt remove that set but it removes the first set.
when I debugged removeSelectedValue method there slices are happening correctly but its not updating in the updating
can you tell me how to pass the queryComponents values so that it will update in the UI.
can you tell me how to fix it.
so that in future I will fix it myself.
providing my relevant code snippet and sandbox below.
all my code is in demo.js
https://codesandbox.io/s/4x9lw9qrmx
removeSelectedValue = index => {
console.log("removeSelectedValue--->", index);
let seletedValues = this.state.queryComponents;
seletedValues.splice(index, 1);
console.log("spliced Values--->", seletedValues);
this.setState({ queryComponents: seletedValues });
};
render() {
let queryComp = this.state.queryComponents.map((value, index) => {
return (
<AutoCompleteComponent
key={index}
value={value}
index={index}
valueSelected={this.getSelectedValue}
removeSeleted={this.removeSelectedValue}
/>
);
});
return <div>{queryComp}</div>;
}
When you do let seletedValues = this.state.queryComponents;
you're creating a reference to that variable, instead of making a copy.
You need to make sure you replace your state with a new object/array for the re-render to happen.
Please try this:
removeSelectedValue = index => {
this.setState(prevState => ({
queryComponents: prevState.seletedValues.filter((a, i) => (i !== index));
});
};
That filter function is equivalent to the splice you were using, but returns a new array instead of modifying the original one.
On the other hand, I 'm passing setState a function that uses prevState making the code shorter.
Related
Edit Note:Link with working version at bottom of post
I have run into some really weird behavior with my project
I have the parent which gets some data from a database and passes it to the child (which is a popup). The child then uses useEffect to detect when the data is ready (black part) and sets the value in the child (blue part).
useEffect(() => {
console.log(datachannels.length)
if (datachannels.length > 1) {
let newValueState = []
let c = 0
datachannels.forEach(element => {
newValueState.push({ id: c, dc: element.dataChannel, axis: "y" })
c += 1
});
setValueState(newValueState)
console.log(newValueState)
console.log("trigger")
}
}, [datachannels])
Then when a user changes a option this code runs to update it(Green part)
const handler = (event, id) => {
console.log(valueState)
const value = event.value
console.log(valueState.length)
if (valueState.length > 0) {
let newValueState = valueState
newValueState.forEach(key => {
if (key.id == id) {
console.log(key)
key.axis = value
}
});
setValueState(newValueState)
}
}
With inspect i can see that the value is not empty but the handler function trying to use it is telling me its empty. When giving valueState a default value and after useEffect code runs, I try and access it but it just returns the default value. I have no idea what is causeing this or what to google so if anyone can shead some light on this, I would be very happy
Update 1: Codesandebox (new link below) I have not been able to get the bug to show yet
Update 2:Code in sandbox now has error. As you can see when useEffect runs the object has 3 values but when the user changes the dropdown it only has one value
Update 3: To see the bug in the sandbox change one of the axis drop downs
Update 4: Porok12 answer works in the sand box but is not working in my actully project and i have no idea what is going on
Update 5: So when using Porok12 code and feeding in datachannels the same way as the sandbox it does work but with some other weird behavior that im hopping can point to the problem. When changing a dropdown this is outputted to the console. From input was changed to (3) [{},{},{}] is the correct behavior but then everything rerenders as seen from the other messages. I have no idea why it is behaving differently from porok12 code as it is the same. But this does point out a problem with how datachannels is being passed in? In the parent object I am storing the channels like this
if (data !== undefined) {
let parseData = JSON.parse(data);
let toPush = []
Object.keys(parseData).forEach(key => {
toPush.push({ "dataChannel": key })
});
setDataChannels(toPush);
And feeding it to the child like this <BuilderTable handleupdate={handleUpdate} datachannels={dataChannels} show={modalShow} onHide={() => setModalShow(false)} />
Update 6: So its now working with Porok12 code after i changed the useMemo [] to use valueState instead of datachannel. The only problem now is that the dropdown reset to the starting value every time you change it
Update 7: With the change from update 6 and feeding valueStatus into the dropdowns the problem is not fixed.
Update 8: https://codesandbox.io/s/spring-water-2ucq8u?file=/src/GraphBuilderFull.js Here is the sandbox link with a working version
Side note: you can change
let c = 0
datachannels.forEach(element => {
newValueState.push({ id: c, dc: element.dataChannel, axis: "y" })
c += 1
});
to
const newValueState = datachannels.map((element, index) => ({id: index, dc: element.dataChannel, axis: "y"}))
The problem is that you use useMemo that way:
const selectChannelTableColumn = React.useMemo(
() => [
{
Header: "Axis Selection",
Cell: ({ row }) => (
<div>
<Dropdown
id={row.id}
options={axis}
onChange={(e) => handler(e, row.id)}
placeholder="y"
/>
</div>
)
}
],
[] // missing `datachannels` xor `valueState` dependency
);
If you will add datachannels to dependencies it will work. You should also move handler definition to this useMemo code.
Here is fixed sandbox example
The changes I made:
Move handler definition to selectChannelTableColumn code
Fix handler logic
Move selectChannelTableColumn under (meaning below not inside) your useEffect
Add datachannels to useMemo hook dependencies
I have a table with checkboxes and I want to save selected checkboxes' ids in state. So here's my code.
Input looks like this (I use coreui so this is the inside of a table's scopedSlots):
selected: (item) => {
return (
<td style={{ width: '40px' }}>
<CInputCheckbox
className="mx-auto"
id={item.id}
onChange={(e) => handleSelect(e)}
/>
</td>
);}
And this is the rest:
const [selectedRows, setSelectedRows] = useState([]);
const handleSelect = (e) => {
const id = e.target.id;
const index = selectedRows.indexOf(id);
const rows = [...selectedRows];
if (index === -1) {
rows.push(id);
} else {
rows.split(index, 1);
}
setSelectedRows(rows);
};
And the weirdest thing happens - in the chrome's react devtools I see that the first id is being added to the selectedRows array and then when I select another row - the previous item in the array is being overwritten. When i console.log my selectedRows array it shows empty array always (even if I see in the devtools that there's one item). I have no idea what I'm doing wrong here.
There is not a lot of information to work with in your question, so I had to make many assumptions about how are they suppose to work, but this is what I got so far:
the handleSelect function has a little error when it comes to removing items from the list, your function is using split but I think what you meant was slice
I think the scopes of handleSelect and const [selectedRows, setSelectedRows] = useState([]); are wrong. For what I can see, they are at the same level of selected component, which will fail as they will only be able to keep track of one selected component at the time, and I think you are needing to keep track of multiple selected components.
Here is a working version of what I think you are trying to do
https://codepen.io/richard-unal/pen/eYWXXOo?editors=1111
If you need clarification on something, please let me know, I'm aware that I'm not the best explainer.
I am using Ant Design for the first time in a project. Using the <Option> and <Select> component I have run into the strangest bug I've ever had. The pure existence if code, which is NOT EXECUTED changes the render behavior of the <Option>.
The difference is, that the first time (with the code) it displays a number (3) in the <Option> and the second time (without the code) it displays a string ("Schraube").
I don't know how much code I am allowed to share, but I recorded a short video which shows the bug.
(The code which prints "GETS CALLED" only runs after I change the select option, during the render process it doesn't get executed.)
https://www.youtube.com/watch?v=aY2NPgP5x6A
I'd like to hear your thoughts on it.
In the first case, you have:
if (a) {
console.log("GETS CALLED");
return false;
}
return true;
When this code gets executed, your option isn't formatted to a user-friendly display.
In the second you have just:
return true;
In which case it is being formatted correctly.
Note this is not just removing the console.log statement, and you're not just enabling some trivial if statement. You're changing the the return value of the function. Since this is in a call to .filter you're changing what values get passed to .map which actually formats the option for display purposes.
If you were to try:
if (a) {
console.log("GETS CALLED");
}
return true;
You'd see that the value gets formatted, and your console.log statement gets hit.
Update What I think you're trying to accomplish is to have the second dropdown exclude the option that was selected in the first dropdown. In that case you'll need to render different option arrays for each item. Also be sure to include the item that is selected for the current dropdown, so that the display is correct on the selected item. Something along these lines should help:
const orderForm = this.state.order.map((o, i) => {
const options = this.state.parts
.filter(p => {
// look for any *other* order that has this part id selected
const a = this.state.order.find((o2) => o2 !== o && o2.partId === p.id);
return !a;
})
.map((p) => (<Option ... />));
return (
<Select>
{options}
</Select>
);
});
Or if you want to render the <Option>'s only once, you could do something like this:
const allOptions = this.state.parts.map((p) => [p.id, (<Option ... />)]);
const orderForm = this.state.order.map((o, i) => {
const options = allOptions
.filter(([partId]) => {
// look for any *other* order that has this part id selected
const a = this.state.order.find((o2) => o2 !== o && o2.partId === partId);
return !a;
})
.map(([_, opt]) => opt);
return (
<Select>
{options}
</Select>
);
});
This is a really straight forward question. Notice I have the multiple attribute. When I select a value it just pushes a value into an array. Hence, if I have multiple values it will give me an array with multiple values but not the specific option I'm choosing.
Now my question is how do I get that specific value when I remove it from the Dropdown? I've searched google for an hour and haven't found an answer.
<Dropdown
options={options}
onChange={this.onChange.bind(this)}
search fluid multiple selection/>
onChange(e, { value } ) {
e.preventDefault();
// e.target.value DOESN'T WORK???!!!
}
So Dropdown from Semantic UI doesn't seem to provide this functionality out of the box. However, there's a decent way of achieving this. First, create a state like:
this.state = {
selected: [],
}
Then, bind a function to the Dropdown component like so:
<Dropdown
placeholder="Skills"
fluid
multiple
selection
options={options}
onChange={this.handleChange}/>
Once that's done, write the handleChange function to compare array lengths on every change. If state array is longer than whatever array you get from the dropdown, the item has been removed and you can check which one. Otherwise, dump the array into the state.
handleChange = (e, { value }) => {
if (this.state.selected.length > value.length) { // an item has been removed
const difference = this.state.selected.filter(
x => !value.includes(x),
);
console.log(difference); // this is the item
return false;
}
return this.setState({ selected: value });
};
You'll need a polyfill to run in IE8 and below though.
See this gist for the complete picture.
Basically I will have this form:
When you click the plus, another row should appear with a drop down for day and a time field.
I can create the code to add inputs to the form, however I'm having trouble with the individual components (selectTimeInput is a row) actually updating their values.
The onChange in the MultipleDayTimeInput is receiving the correct data, it is just the display that isn't updating. I extremely new to react so I don't know what is causing the display to not update....
I think it is because the SelectTimeInput render function isn't being called because the passed in props aren't being updated, but I'm not sure of the correct way to achieve that.
Thinking about it, does the setState need to be called in the onChange of the MultipleDayTimeInput and the input that changed needs to be removed from the this.state.inputs and readded in order to force the render to fire... this seems a little clunky to me...
When you update the display value of the inputs in state, you need to use this.setState to change the state data and cause a re-render with the new data. Using input.key = value is not the correct way.
Using State Correctly
There are three things you should know about
setState().
Do Not Modify State Directly
For example, this will not re-render a
component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
The only place where you
can assign this.state is the constructor.
read more from Facebook directly here
I would actually suggest a little bit of a restructure of your code though. It's not really encouraged to have components as part of your state values. I would suggest having your different inputs as data objects in your this.state.inputs, and loop through the data and build each of the displays that way in your render method. Like this:
suppose you have one input in your this.state.inputs (and suppose your inputs is an object for key access):
inputs = {
1: {
selectedTime: 0:00,
selectedValue: 2
}
}
in your render, do something like this:
render() {
let inputs = Object.keys(this.state.inputs).map((key) => {
let input = this.state.inputs[key]
return (<SelectTimeInput
key={key}
name={'option_' + key}
placeholder={this.props.placeholder}
options={this.props.options}
onChange={this.onChange.bind(this, key)}
timeValue={input.selectedTime}
selectValue={input.selectedValue}
/>)
)}
return (
<div>
<button className="button" onClick={this.onAddClick}><i className="fa fa-plus" /></button>
{ inputs }
</div>
);
}
Notice how we're binding the key on the onChange, so that we know which input to update. now, in your onChange function, you just set the correct input's value with setState:
onChange(event, key) {
this.setState({
inputs: Immutable.fromJS(this.state.inputs).setIn([`${key}`, 'selectedTime'], event.target.value).toJS()
// or
inputs: Object.assign(this.state.inputs, Object.assign(this.state.inputs[key], { timeValue: event.target.value }))
})
}
this isn't tested, but basically this Immutable statement is going to make a copy of this.state.inputs and set the selectedTime value inside of the object that matches the key, to the event.target.value. State is updated now, a re-render is triggered, and when you loop through the inputs again in the render, you'll use the new time value as the timeValue to your component.
again, with the Object.assign edit, it isn't tested, but learn more [here]. 2 Basically this statement is merging a new timeValue value in with the this.state.inputs[key] object, and then merging that new object in with the entire this.state.inputs object.
does this make sense?
I modified the onChange in the MultipleDayTimeInput:
onChange(event) {
const comparisonKey = event.target.name.substring(event.target.name.length - 1);
const input = this.getInputState(comparisonKey);
input.selected = event.target.value;
input.display = this.renderTimeInput(input);
let spliceIndex = -1;
for (let i = 0; i < this.state.inputs.length; i++) {
const matches = inputFilter(comparisonKey)(this.state.inputs[i]);
if (matches) {
spliceIndex = i;
break;
}
}
if (spliceIndex < 0) {
throw 'error updating inputs';
}
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
}
The key points are:
// re render the input
input.display = this.renderTimeInput(input);
// set the state by copying the inputs and interchanging the old input with the new input....
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
Having thought about it though, input is an object reference to the input in the this.state.inputs so actually [...this.states.inputs] would have been enough??