I'm trying to update the state, the current func (handleLabelChange) theoretically works (updating state), but I want to add name instead of textOne and value "????" dynamically from the textarea field or any other field. Some kind of target.name and target.value, but I don't know how to deal with it.
handleLabelChange = id => {
const updatedItems = this.state.items.map(item => {
if (item.id === id) {
return {
...item,
textOne: "????" // grab textarea name and value here
};
} else {
return item;
}
});
this.setState({
items: updatedItems
});
};
JSX inside map function:
{this.state.items.map(el => (
<textarea rows="5" placeholder="Text here..." name="textOne" onChange={() => this.handleLabelChange(el.id)}></textarea>
}
I would do something like this on the textarea:
onChange={this.handleLabelChange(el.id, 'name')}
Where the second argument is the property, and then handleLabelChange looks like this
function handleLabelChange(id, property) {
return ev => {
const newVal = ev.target.value;
const updatedItems = this.state.items.map(item => {
if (item.id === id) {
const newItem = Object.assign({}, item);
newItem[property] = newVal;
return newItem;
} else {
return item;
}
});
this.setState({
items: updatedItems
});
}
}
Your handleLabelChange returns the callback function, rather than being the callback function
In the end I was able to solve it, if someone have a similar problem, it is my solution.
As TKoL suggested function needed args: id, name, value, ({target: {id, value, name}}), then in my case, I needed to change the id from string to number.
handleLabelChange = ({target: {id, value, name}}) => {
const idToNumber = Number(id);
const updatedItems = this.state.items.map(item => {
if (item.id === idToNumber) {
return {
...item,
[name]: value
};
} else {
return item;
}
});
this.setState({
items: updatedItems
});
}
JSX
{this.state.items.map(el => (
<textarea rows="5" placeholder="Text here..." id={el.id} name="textOne" onChange={this.handleLabelChange}></textarea>
}
Related
Panel is a datamodel fetched from database. avialablePanels is a dropdown where I can select an option I want. PanelCode dropdown is populated using a lookup table because it acts as a form where the displayed value is what Panel['PanelCode'] has and other values with which I can update. When I update a value of Panel[PanelCode] with the help of indexing using the PanelCode dropdown form it initially updates the value in the Panel['PanelCode'] array. Now lets say I want to update another value in the Panel['PanelCode'] and save them together as soon as I select another option from avialablePanels the first updated value of Panel['PanelCode'] is lost.
Panel: {
PanelCode: [ 1, 4 ]
}
availablePanels:[
{ OptionCode: 'R1-1', OptionKey: 1, OptionValue: 'Stop' },
{ OptionCode: 'R1-3P',OptionKey: 4,OptionValue: 'All Way (plaque)'}
]
export default class PanelTest extends Component {
constructor(props) {
super(props);
console.log(this.props.pointModel)
this.state = {...this.props.pointModel,
availablePanels:[],
selectedIndex: 0,
selectedPanel: null,
tempPanelCode: this.props.pointModel.Panel.PanelCode[0]===null?0:
this.props.pointModel.Panel.PanelCode[0],
}
}
render() {
return(
<Container>
{this.state.availablePanels.length>0 &&
<PtSelect label="Available Panel"
options={this.state.availablePanels}
name="selectedPanel" defaultVal={this.state.selectedPanel}
onChange={this.onChangeSelectedPanelDropdown} />}
{this.renderPanelinfo()}
</Container>
)
}
onChangeSelectedPanelDropdown = (e) => {
const { target } = e;
const {name, value} = target;
let indexVal = this.state.Panel.PanelCode.indexOf(parseInt(value))
this.setState({ [name]: parseInt(value),
selectedIndex:indexVal,
tempPanelCode: this.props.pointModel.Panel.PanelCode[indexVal]===null?0:
this.props.pointModel.Panel.PanelCode[indexVal]
});
}
renderPanelinfo = () =>{
const {typeOptions} = DropdownLib.getSignNum().Signs_Types;
/* typeOptions looks like availablePanels but with more options */
return (
<div>
<PtSelect label="Panel Code" options={typeOptions}
disabled={this.props.disabled}
name="PanelCode" defaultVal={this.state.tempPanelCode}
onChange={this.onChangeDropdown} />
</div>
)
}
getAvaialablePanels=()=>{
const availablePanelOptions = []
const optionKey = []
//const optionvalue = []
fetch(`${config.server}/getavailablepanels/`+this.state.Support.SignId)
.then(response=>
{
return response.json();
})
.then(data=>{
for (var i =0;i<data.length;i++){
availablePanelOptions.push(data[i]['OptionCode'])
optionKey.push(data[i]['OptionKey'])
//optionvalue.push(data[i]['OptionValue'])
}
let dropOptions = availablePanelOptions.map((option,idx)=>{
return {key:optionKey[idx],value: optionKey[idx], label:option}
});
this.setState({
availablePanels:dropOptions
});
})
.catch(error=>{
console.log(error);
});
}
onChangeDropdown = (e) => {
const { target } = e;
const {name, value} = target;
this.props.triggerNeedSave();
// eslint-disable-next-line
let stateVariable = 'temp'+[name]
this.setState({
[stateVariable]: parseInt(value)
});
this.props.pointModel.Panel[name][this.state.selectedIndex] = parseInt(value);
console.log(this.state)
}
componentDidMount(){
this.getAvaialablePanels()
}
}
Any help is really appreciated.
I want to ask if then is a way to update the whole object in immerjs
For example
I want to set my redux state to the output of this function
Sample state
{
"1":{
// properties
},
"2":{
// properties
}
}
In the Code below mainly, there are 3 function
ObjectPush: what it does is basically shift all properties down by one(eg 1 becomes 2)from the index(param)
ObjectRemove: Remove particular index and change indexes
deleteStrip:Reducer
const ObjectPush = (obj, index) => {
return produce(obj, (state) => {
const keys = Object.keys(state)
.filter((v) => v !== 'misc')
.map(Number);
const draft = Object.assign({}, state);
keys.forEach((key) => {
if (key >= index) {
state[key + 1] = draft[key];
}
});
delete state[index];
});
};
export const ObjectRemove = (obj, index) => {
const state = {};
const updatedState = { ...ObjectPush(obj, index)
};
const keys = Object.keys(updatedState)
.filter((v) => v !== 'misc')
.map(Number);
const temp = Object.assign({}, obj);
keys.map((key) => {
if (temp[key]) {
if (key > index) {
state[key - 1] = temp[key];
} else {
state[key] = temp[key];
}
}
});
return state;
};
// inside immer produce
deleteStrip: (state, action) => {
const { index } = action.payload;
state = ObjectRemove(state, index);
}
I am trying to add a FontAwesome arrow next to each item in my menu that has children (i.e. I want to indicate that you can click the element to display more data within that category). The menu is populated with json data from an API, and because it is so many nested objects, I decided to use recursion to make it work. But now I am having trouble adding an arrow only to the elements that have more data within it, instead of every single element in the menu.
Does anyone have an idea of how I could change it so the arrow only shows up next to the elements that need it? See below for image
class Menu extends React.Component {
state = {
devices: [],
objectKey: null,
tempKey: []
};
This is where I'm currently adding the arrow...
createMenuLevels = level => {
const { objectKey } = this.state;
const levelKeys = Object.entries(level).map(([key, value]) => {
return (
<ul key={key}>
<div onClick={() => this.handleDisplayNextLevel(key)}>{key} <FontAwesome name="angle-right"/> </div>
{objectKey[key] && this.createMenuLevels(value)}
</ul>
);
});
return <div>{levelKeys}</div>;
};
handleDisplayNextLevel = key => {
this.setState(prevState => ({
objectKey: {
...prevState.objectKey,
[key]: !this.state.objectKey[key]
}
}));
};
initializeTK = level => {
Object.entries(level).map(([key, value]) => {
const newTemp = this.state.tempKey;
newTemp.push(key);
this.setState({ tempKey: newTemp });
this.initializeTK(value);
});
};
initializeOK = () => {
const { tempKey } = this.state;
let tempObject = {};
tempKey.forEach(tempKey => {
tempObject[tempKey] = true;
});
this.setState({ objectKey: tempObject });
};
componentDidMount() {
axios.get("https://www.ifixit.com/api/2.0/categories").then(response => {
this.setState({ devices: response.data });
});
const { devices } = this.state;
this.initializeTK(devices);
this.initializeOK();
this.setState({ devices });
}
render() {
const { devices } = this.state;
return <div>{this.createMenuLevels(devices)}</div>;
}
}
This is what it looks like as of right now, but I would like it so items like Necktie and Umbrella don't have arrows, since there is no more data within those items to be shown
You could check in the map loop from createMenuLevels if the value is empty or not and construct the div based on that information.
createMenuLevels = level => {
const { objectKey } = this.state;
const levelKeys = Object.entries(level).map(([key, value]) => {
//check here if childs are included:
var arrow = value ? "<FontAwesome name='angle-right'/>" : "";
return (
<ul key={key}>
<div onClick={() => this.handleDisplayNextLevel(key)}>{key} {arrow} </div>
{objectKey[key] && this.createMenuLevels(value)}
</ul>
);
});
return <div>{levelKeys}</div>;
};
Instead of just checking if the value is set you could check if it is an array with: Array.isArray(value)
I have initialized some const, lets say A, using getDerivedStateFromProps. Now I want to update the value on some action using setState but it's not working.
constructor(props) {
super(props)
this.state = {
A: []
}
static getDerivedStateFromProps(nextProps, prevState) {
const A = nextProps.A
return {
A
}
}
handleDragStart(e,data) {
e.dataTransfer.setData('item', data)
}
handleDragOver(e) {
e.preventDefault()
}
handleDrop(e, cat) {
const id = e.dataTransfer.getData('item')
const item = find(propEq('id', Number(id)), this.state.A)
const data = {
...item.data,
category: cat,
}
const val = {
...item,
data
}
this.setState({
A: item,
})
}
}
**Listing the items and Drag and Drop to Categorize**
{this.state.A.map((item, index) => (
<ListRow
key={`lm${index}`}
draggable
name={item.name ? item.name : ''}
description={item.data ? item.data.description : ''}
type={item.data ? item.data.target_types : ''}
source={item.data ? item.data.source : ''}
stars={item.data ? item.data.stars : []}
onDragStart={e => this.handleDragStart(e, item.id)}
onDragOver={e => this.handleDragOver(e)}
onDrop={e => this.handleDrop(e, 'process')}
onModal={() => this.handleToggleModal(item)}
/>
))}
I expect the value of A to be an item from HandleDrop but it's returning the same value that is loaded from getDerivedStateFromProps.
Here's how I solved this problem.
I used componentDidUpdate instead of getDerivedStatesFromProps.
componentDidUpdate(prevProps) {
if (!equals(this.props.A, prevPropsA)) {
const A = this.props.A
this.setState({
A
})
}
}
And the handleDrop function as
handleDrop(e, cat) {
const id = e.dataTransfer.getData('item')
const item = find(propEq('id', Number(id)), this.state.A)
const data = {
....data,
category: cat,
}
const val = {
...quota,
data
}
let {A} = this.state
const index = findIndex(propEq('id', Number(id)), A)
if (!equals(index, -1)) {
A = update(index, val, A)
}
this.setState({
A
})
}
Thank you very much for all of your help. Any suggestions or feedback for optimizing this sol will be highly appreciated. Thanks
Below code doesn't look good to me, as I have to declare a temp variable, any shorter code to achieve the same result?
handleChange = (e, index1, innerIndex) => {
const temp_values = this.state.values
temp_values.map((value, index) => {
if (index === innerIndex) {
temp_values[index].args[index1] = e.target.value
}
})
this.setState({
values: temp_values
})
}
Yes, you can simplify it like this:
handleChange = (e, index1, innerIndex) => {
this.setState({
values: this.state.values.map((value, index) => {
const args = [].concat(value.args);
if (index === innerIndex) {
args[index1] = e.target.value;
}
return {
...value,
args,
};
}),
});
}