How to set an initial API call in react ES6 - reactjs

So the problem is as follows: I have a search function that get's an default value passed on from another place, the search works, but only when it gets a new input, hence if I'm passing "Dress" it wont call my api function before i change something in the input.
I've tried a bit of everything like setInitialState(), but without any noteworthy success.
As you can see I'm getting a onTermChange from my Searchbar that's passed to handleTermChange which then updates my products:[], but I need this.props.location.query to be the default search term, as this is the passed on variable.
handleTermChange = (term) => {
const url = `http://localhost:3001/products?title=${term.replace(/\s/g, '+')}`;
request.get(url, (err, res) => {
this.setState({ products: res.body })
});
};
render () {
return (
<div className='col-md-12' style={{ margin: '0 auto' }}>
<div className='row searchPageHeader' style={{ padding: '10px', backgroundColor: '#1ABC9C' }}/>
<SideMenu />
<SearchBar onTermChange={this.handleTermChange}
defaultValue={this.props.location.query}/>
<ProductList products={this.state.products}
onProductSelect={selectedProduct => this.openModal(selectedProduct)}/>
<ProductModal modalIsOpen={this.state.modalIsOpen}
selectedProduct={this.state.selectedProduct}
onRequestClose={ () => this.closeModal() }/>
<Footer />
</div>
);
}

I would personally just do the same logic in componentDidMount(), like this:
componentDidMount () {
const url = `http://localhost:3001/products?title=${this.props.location.query}`;
request.get(url, (err, res) => {
this.setState({ products: res.body })
});
}
Note that since you are doing an asynchronous call products won't be populated from the API result until a moment after the component is mounted. Make sure you initialize products in initialState (I assume this returns an array, so initialize it as an empty array).
Opinion: Since you are following the event handler naming conventions (i.e onX followed by handleX) I would avoid calling handleTermChange() inside componentDidMount() because the function name suggests it's bound to an event listener. So calling it directly is just bad practice in my opinion. So if you'd rather call a function in here, rather than writing out the logic like I did above, I would do the following:
componentDidMount() {
this.changeTerm(this.props.location.query);
}
changeTerm = (term) => {
const url = `http://localhost:3001/products?title=${term.replace(/\s/g, '+')}`;
request.get(url, (err, res) => {
this.setState({ products: res.body })
});
};
handleTermChange = (term) => {
this.changeTerm(term);
}
Your render() remains unchanged. Maybe a stretch, but I prefer it this way.

Related

React state not updating when used outside hook

I'm playing around with a hook that can store some deleted values. No matter what I've tried, I can't get the state from this hook to update when I use it in a component.
const useDeleteRecords = () => {
const [deletedRecords, setDeletedRecords] = React.useState<
Record[]
>([]);
const [deletedRecordIds, setDeletedRecordIds] = React.useState<string[]>([]);
// ^ this second state is largely useless – I could just use `.filter()`
// but I was experimenting to see if I could get either to work.
React.useEffect(() => {
console.log('records changed', deletedRecords);
// this works correctly, the deletedRecords array has a new item
// in it each time the button is clicked
setDeletedRecordIds(deletedRecords.map((record) => record.id));
}, [deletedRecords]);
const deleteRecord = (record: Record) => {
console.log(`should delete record ${record.id}`);
// This works correctly - firing every time the button is clicked
setDeletedRecords(prev => [...prev, record]);
};
const wasDeleted = (record: Record) => {
// This never works – deletedRecordIds is always [] when I call this outside the hook
return deletedRecordIds.some((r) => r === record.id);
};
return {
deletedRecordIds,
deleteRecord,
wasDeleted,
} // as const <-- no change
}
Using it in a component:
const DisplayRecord = ({ record }: { record: Record }) => {
const { deletedRecordIds, wasDeleted, deleteRecord } = useDeleteRecords();
const handleDelete = () => {
// called by a button on a row
deleteRecord(record);
}
React.useEffect(() => {
console.log('should fire when deletedRecordIds changes', deletedRecordIds);
// Only fires once for each row on load? deletedRecordIds never changes
// I can rip out the Ids state and do it just with deletedRecords, and the same thing happens
}, [deletedRecordIds]);
}
If it helps, these are in the same file – I'm not sure if there's some magic to exporting a hook in a dedicated module? I also tried as const in the return of the hook but no change.
Here's an MCVE of what's going on: https://codesandbox.io/s/tender-glade-px631y?file=/src/App.tsx
Here's also the simpler version of the problem where I only have one state variable. The deletedRecords state never mutates when I use the hook in the parent component: https://codesandbox.io/s/magical-newton-wnhxrw?file=/src/App.tsx
problem
In your App (code sandbox) you call useDeleteRecords, then for each record you create a DisplayRecord component. So far so good.
function App() {
const { wasDeleted } = useDeleteRecords(); // ✅
console.log("wtf");
return (
<div className="App" style={{ width: "70vw" }}>
{records.map((record) => {
console.log("was deleted", wasDeleted(record));
return !wasDeleted(record) ? (
<div key={record.id}>
<DisplayRecord record={record} /> // ✅
</div>
) : null;
})}
</div>
);
}
Then for each DisplayRecord you call useDeleteRecords. This maintains a separate state array for each component ⚠️
const DisplayRecord = ({ record }: { record: Record }) => {
const { deletedRecords, deleteRecord } = useDeleteRecords(); // ⚠️
const handleDelete = () => {
// called by a button on a row
deleteRecord(record);
};
React.useEffect(() => {
console.log("should fire when deletedRecords changes", deletedRecords);
// Only fires once for each row on load? deletedRecords never changes
}, [deletedRecords]);
return (
<div>
<div>{record.id}</div>
<div onClick={handleDelete} style={{ cursor: "pointer" }}>
[Del]
</div>
</div>
);
};
solution
The solution is to maintain a single source of truth, keeping handleDelete and deletedRecords in the shared common ancestor, App. These can be passed down as props to the dependent components.
function App() {
const { deletedRecords, deleteRecord, wasDeleted } = useDeleteRecords(); // 👍🏽
const handleDelete = (record) => (event) { // 👍🏽 delete handler
deleteRecord(record);
};
return (
<div className="App" style={{ width: "70vw" }}>
{records.map((record) => {
console.log("was deleted", wasDeleted(record));
return !wasDeleted(record) ? (
<div key={record.id}>
<DisplayRecord
record={record}
deletedRecords={deletedRecords} // 👍🏽 pass prop
handleDelete={handleDelete} // 👍🏽 pass prop
/>
</div>
) : null;
})}
</div>
);
}
Now DisplayRecord can read state from its parent. It does not have local state and does not need to call useDeleteRecords on its own.
const DisplayRecord = ({ record, deletedRecords, handleDelete }) => {
React.useEffect(() => {
console.log("should fire when deletedRecords changes", deletedRecords);
}, [deletedRecords]); // ✅ passed from parent
return (
<div>
<div>{record.id}</div>
<div
onClick={handleDelete(record)} // ✅ passed from parent
style={{ cursor: "pointer" }}
children="[Del]"
/>
</div>
);
};
code demo
I would suggest a name like useList or useSet instead of useDeleteRecord. It's more generic, offers the same functionality, but is reusable in more places.
Here's a minimal, verifiable example. I named the delete function del because delete is a reserved word. Run the code below and click the ❌ to delete some items.
function App({ items = [] }) {
const [deleted, del, wasDeleted] = useSet([])
React.useEffect(_ => {
console.log("an item was deleted", deleted)
}, [deleted])
return <div>
{items.map((item, key) =>
<div className="item" key={key} data-deleted={wasDeleted(item)}>
{item} <button onClick={_ => del(item)} children="❌" />
</div>
)}
</div>
}
function useSet(iterable = []) {
const [state, setState] = React.useState(new Set(...iterable))
return [
Array.from(state), // members
newItem => setState(s => (new Set(s)).add(newItem)), // addMember
item => state.has(item) // isMember
]
}
ReactDOM.render(
<App items={["apple", "orange", "pear", "banana"]}/>,
document.querySelector("#app")
)
div.item { display: inline-block; border: 1px solid dodgerblue; padding: 0.25rem; margin: 0.25rem; }
[data-deleted="true"] { opacity: 0.3; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Since you are updating deletedRecordIds inside a React.useEffect, this variable will have the correct value only after the render complete. wasDeleted is a closure that capture the value of deletedRecordIds when the component renders, thus it always have a stale value. As yourself are suggesting, the correct way to do that is to use .filter() and remove the second state.
Talking about the example you provided in both cases you are defining 5 hooks: one hook for each DisplayRecord component and one for the App. Each hook define is own states, thus there are 5 deletedRecords arrays on the page. Clicking on Del, only the array inside that specific component will be updated. All other component won't be notified by the update, because the state change is internal to that specific row. The hook state in App will never change because no one is calling its own deleteRecord function.
You could solve that problem in 2 way:
Pulling up the state: The hook is called just once in the App component and the deleteRecord method is passed as parameter to every DisplayRecord component. I updated your CodeSandbox example.
Use a context: Context allows many component to share the same state.

Refactoring class component to functional component with hooks, getting Uncaught TypeError: func.apply is not a function

This is my first attempt to refactor code from a class component to a functional component using React hooks. The reason we're refactoring is that the component currently uses the soon-to-be-defunct componentWillReceiveProps lifecylcle method, and we haven't been able to make the other lifecycle methods work the way we want. For background, the original component had the aforementioned cWRP lifecycle method, a handleChange function, was using connect and mapStateToProps, and is linking to a repository of tableau dashboards via the tableau API. I am also breaking the component, which had four distinct features, into their own components. The code I'm having issues with is this:
const Parameter = (props) => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter)
const dispatch = useDispatch();
let parameterSelections = parameterCurrent;
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [props.parameterCurrent])
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
console.log(parameterCurrent[key]);
return (
prevState => ({
...prevState,
parameterSelections: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
Swal.fire({
position: "center",
icon: "success",
title:
JSON.stringify(key) + " set to " + JSON.stringify(valKey),
font: "1em",
showConfirmButton: false,
timer: 2500,
heightAuto: false,
height: "20px"
});
})
.otherwise(function (err) {
alert(
Swal.fire({
position: "top-end",
icon: "error",
title: err,
showConfirmButton: false,
timer: 1500,
width: "16rem",
height: "5rem"
})
);
});
}
);
};
const classes = useStyles();
return (
<div>
{Object.keys(parameterSelect).map((key, index) => {
return (
<div>
<FormControl component="fieldset">
<FormLabel className={classes.label} component="legend">
{key}
</FormLabel>
{parameterSelect[key].map((valKey, valIndex) => {
console.log(parameterSelections[key])
return (
<RadioGroup
aria-label="parameter"
name="parameter"
value={parameterSelections[key]}
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
<FormControlLabel
className={classes.formControlparams}
value={valKey}
control={
<Radio
icon={
<RadioButtonUncheckedIcon fontSize="small" />
}
className={clsx(
classes.icon,
classes.checkedIcon
)}
/>
}
label={valKey}
/>
</RadioGroup>
);
})}
</FormControl>
<Divider className={classes.divider} />
</div>
);
})
}
</div >
)};
export default Parameter;
The classes const is defined separately, and all imports of reducers, etc. have been completed. parameterSelect in the code points to all available parameters, while parameterCurrent points to the default parameters chosen in the dashboard (i.e. what the viz initially loads with).
Two things are happening: 1. Everything loads fine on initial vizualization, and when I click on the Radio Button to change the parameter, I can see it update on the dashboard - however, it's not actually showing the radio button as being selected (it still shows whichever parameter the viz initialized with as being selected). 2. When I click outside of the Filterbar (where this component is imported to), I get Uncaught TypeError: func.apply is not a function. I refactored another component and didn't have this issue, and I can't seem to determine if I coded incorrectly in the useEffect hook, the handleParameterChange function, or somewhere in the return statement. Any help is greatly appreciated by this newbie!!!
This is a lot of code to take in without seeing the original class or having a code sandbox to load up. My initial thought is it might be your useEffect
In your refactored code, you tell your useEffect to only re-run when the props.parameterCurrent changes. However inside the useEffect you don't make use of props.parameterCurrent, you instead make use of parameterCurrent from the local lexical scope. General rule of thumb, any values used in the calculations inside a useEffect should be in the list of re-run dependencies.
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [parameterCurrent])
However, this useEffect doesn't seem to do anything, so while its dependency list is incorrect, I don't think it'll solve the problem you are describing.
I would look at your dispatch and selector. Double check that the redux store is being updated as expected, and that the new value is making it from the change callback, to the store, and back down without being lost due to improper nesting, bad key names, etc...
I'd recommend posting a CodeSandbox.io link or the original class for further help debugging.

Pass ref to function with looped id?

I'm trying to create a very simple CMS that allows the user to update certain areas on the page.
I have a h3 tag where I want to be able to pass a ref to my onChange function so that I can grab it's innerHTML text (that gets changed by contentEditable) and pass on the new data that gets changed to my back-end server. However, I'm having trouble being able to grab the innerHTML (of the new data) of the correct looped h3 that wants to get changed.
I read documentation online that ref would help me with this but it only gives me an example of where it does it in the render method instead of how to pass it to a function within the ref.
In short, I want to be able to modify my h3 tag (within the cms) with new data and send it to my back-end server to upload to my db.
Also, I tried playing around with not putting it inside of a function and I manage to get access to the myRef.current however in the console it shows as null I want to be able to get access to the specified ref's blogTopic Id so I know which mapped id I'm sending to my back-end server.
I have a lot of code so I'm only going to show the part where I'm stuck on:
class Blogtopics extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.state = {
blogData: [],
blogTopic: "",
};
}
Selectblogtopics = async () => {
const blogTopics = await blogtopicsService.selectblogTopics();
this.setState({
blogData: blogTopics
});
};
editorData = (event, content) => {
let data = content.getData();
//this.setState({ blogContent: data });
};
onChange = (event, content) => {
const node = this.myRef;
//where im stuck
console.log(node);
};
render() {
const node = this.myRef;
console.log(node);
return (
{this.state.blogData.map((rows, index) => (
<div className="blogWrapper" key={uuid()}>
<div className="col-md-6">
<h3
suppressContentEditableWarning
contentEditable={this.state.isEditing}
style={
this.state.isEditing === true
? { border: "1px solid #000", padding: "5px" }
: null
}
onInput={e => this.onChange(e)}
ref={e => this.onChange(e, this.myRef)}
//onBlur={e => this.onChange(e)}
>
{rows.blog_category}
</div>
))}
);
}
}
export default Blogtopics;
onChange = (event) => {
const nodeContent = this.myRef.current.innerHTML;
console.log(nodeContent);
};
<h3 ... ref={this.myRef} onInput={this.onChange} ... >
will work. But since onInput passes target element you don't even need to use ref:
onChange = ({ target }) => {
console.log(target.innerHTML);
}
<h3 onInput={this.onChange} >

Showing data by React

I'm going to to click in div and show a text in another div in multiple items.
I have got series of data that contains some objects in one array(json file) and it will be shown by react.The code will be done up tohandelrule = ((e,element,i) =>{....}) . There is an onClick function ({e => this.handelrule(e,element,i)}) for each item.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
OtherRooms: {},
divVisibles: {},
loadingVisible: {},
resultRule: {},
};
}
render() {
const { data } = this.state;
const renderHotel = data.map((item, i) => {
return <div class="item">
<div class="moreInfo" onClick={(e) => this.showDiv(e, item, i)}><span>show more data</span></div>
<div key={i} className={`${!this.state.loadingVisible[i] ? "unvisible" : "visible"}`}>
<div id="ballsWaveG">
</div>
</div>
<div id="box-info" key={i} className={` ${!this.state.divVisibles[i] ? "unvisible" : "visible"}`}>
<div class="table">
{this.state.OtherRooms[i]}
</div>
</div>
</div>
});
return (
<div>
{renderHotel}
</div>
);
}
showDiv = (e, element, i) => {
this.showLoading(e, element, i);
setTimeout(() => {
fetch('/json.bc', {
method: 'POST'
})
.then(response => response.text())
.then(text => {
var Maindata = JSON.parse(text.replace(/\'/g, '"'))
this.setState(prevState => ({
Details: {
...prevState.Details,
[i]: this.renderDetails(Maindata, i),
},
divVisibles: { ...prevState.divVisibles, [i]: !prevState.divVisibles[i] },
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
}).catch(error => console.error(error))
}, 1000);
}
renderDetails(element, i) {
var indents = [];
indents.push(<div>
<span>{this.renderRule(element, i)}</span>
<div key={i} className={`${!this.state.loadingVisible[i] ? "unvisible" : "visible"}`}>
<div id="ballsWaveG">
</div>
</div>
<div key={i}>{this.state.resultRule[i]}</div>
</div>
)
return (
indents
)
}
showLoading = (e, elem, val) => {
this.setState(prevState => ({
loadingVisible: { ...prevState.loadingVisible, [val]: !prevState.loadingVisible[val] }
}))
};
renderRule(element, i) {
return <span class="txtRul" onClick={e => this.handelruleRoom(e, element, i)}>Show Rule</span>
}
handelruleRoom = (e, element, i) => {
var mainprovider = element.id.provider
if (mainprovider == undefined) {
return ''
} else {
this.showLoading(e, element, i);
/////the loading whould not be shown //////
setTimeout(() => {
var return_rule = function () { ////This part will be done but the result will not be shown in class="resultRule" ///////
var tmp = null;
$.ajax({
'async': false,
'type': "POST",
'global': false,
'dataType': 'html',
'url': "rule.bc",
'data': { 'mainprovider': JSON.stringify(mainprovider), },
'success': (response) => {
tmp = response;
}
});
return tmp;
}();
return this.setState(prevState => ({
resultRule: { ...prevState.resultRule, [i]: return_rule }, ///In this part return_rule does not set in resultRule ////
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
}, 1000);
}
}
}
ReactDOM.render(<App />, document.getElementById('Result'));
Actually there is a problem with this part this.setState( prevState => ({....})
There are quite a few issues with your code. I'm going to do my best to point them out and show alternatives, not merely to be critical of you, but to help you learn!
You're using quite a few state values. Think about the problem you're trying to solve, and determine the minimum possible states you need to accomplish that. For example, some of the conditional rendering your currently handling in state, can be offloaded to the render method itself using a ternary operator to render a loading component if a state value is undefined
render() {
<div>
{
!this.state.value
? <Loading />
: this.state.value
}
</div>
}
You're using class="name" in some elements. Because in Javascript, class is a keyword, when setting class names on JSX elements, use the className="name" property instead.
You're nesting your fetch calls inside of setTimeouts. This is not a very reliable method of ensuring the fetch returns a value, as network calls should be treated as taking an arbitrary amount of time. If it takes more than 1 second to fetch those files (for whatever reason) then your whole app will break. You have a few alternatives: You can add a series of callbacks, chain together .then()s, or use the new async/await ES6 feature. I'm going to give you an example of how you could utilize async/await for much cleaner and more reliable code:
showDiv = async (event, element, index) => {
this.showLoading(event, element, index);
let response = await fetch("/json.bc", { method: "POST" });
await response.text();
let data = JSON.parse(text.replace(/\'/g, '\"'));
this.setState(prevState => ({
Details: {
...prevState.Details,
[index]: this.renderDetails(data, index)
},
divVisible: {
...prevState.divVisibles,
[index]: !prevState.divVisibles[index]
},
loadingVisible: {
...prevState.loadingVisible,
[index]: ""
}
}));
};
Since this showDiv method itself is asynchronous, when you call it in your top-level code, you will need to chain a .then() and add additional code after it to ensure the new state has taken effect going forward
render() {
// Note that you cannot use await in this top-level code
showDiv.then(() => {
// The rest of your code that relies on the state set in showDiv
}).catch(err => console.error(err));
}
You may save yourself headache of dealing with asyncronous operations by simply importing the files containing your data directly into your app. The only reason you would need to use fetch() as you are, is if you were grabbing your data from a separate backend (like if you had a separate server for your database and REST API). Otherwise, you can pull those files into the client-side bundle with everything else:
// Use whatever the relative path is to these files
import data from "./json.bc";
import rules from "./rules.bc";
The power of React lies in its component based model. I see that your using a lot of helper functions within the same class to render different aspects of your interface. It may work for now, but it looks messy, and is confusing to debug and maintain. Instead, try extracting some of that functionality into new component classes, and then importing them into your App component to render. There will be a learning curve to figure out passing props, and changing state of a parent from a child, but you will gain the benefits of using React as it was intended. Most notably, you can avoid having to store arrays of data for every single div you're rendering. By encapsulating the functionality into a component, each component can manage it's own state and properties.
In your render() method, you placed a semicolon after a div near the end of your renderHotel function. Any JavaScript you want to place within a JSX block, must be put inside curly braces { }, and semicolons are not required to terminate JSX elements. It may have been a typo, but just in case wanted to add this in.
Using var may be more familiar to you, but it's spilling the variable scope all over the place, keeping variables that are no longer needed in memory because you're allowing them a class-wide scope. Instead, use const or let, the keep the variable context contained within their lexical scope. If you need access to one of those locally scoped variables, it can be solved through composition (the way you've organized your functions).
As to the problem you originally posted about, all of the problems I listed may be contributing; typos in JSX, class instead of className, async timing problems. Although perhaps the primary issue is regarding number 3. You are returning the value of tmp and binding it to return_rule possibly before the AJAX call has time to resolve. I would recommend refactoring your handleRuleRoom function using the async/await example I provided as a guide.
Your handelruleRoom function has no index declared inside its scope
So you just need to figure out what is index inside your handelruleRoom function which is probably the last argument of it you called it - i
Try changing index to i
like this
this.showLoading(e, DetailsRoomJ, i);
// and also here
return this.setState(prevState => ({
resultRule: { ...prevState.resultRule, [i]: return_rule },
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
Also if you care there are many things wrong with this snippet
class should be className
are you sure you need a sync function (because you are blocking you browser until you get your response)

How to set a state array with values from TextField using onchange

I am new to react and am trying to add string values in an array. I am using Material-UI objects.
My state has
this.state: {
roles: []
}
A button pushes an undefined element in roles, incrementing its length.
clickAddRole = () => {
this.setState({roles: this.state.roles.concat([undefined]) });
};
So now we have some length to the roles array.
The Textfield is generated with
this.state.roles.map((item, i)=> {
return (
<TextField id={'roles['+i+']'} label={'role '+i} key={i} onChange={this.handleChange('roles['+i+']')} />
)
})
the onchange event is handled as below
handleChange = name => event => {
console.log(name);
this.setState({[name]: event.target.value});
console.log(this.state.roles);
}
The console.log statements generate output like
roles[0]
[undefined]
I expect
roles[0]
["somedata"]
what is going wrong here? The data does not get set in the roles array.
The whole code file is
const styles = theme => ({
error: {
verticalAlign: 'middle'
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 300
},
submit: {
margin: 'auto',
marginBottom: theme.spacing.unit * 2
}
})
class AddModule extends Component {
constructor() {
super();
this.state = {
roles:[],
open: false,
error: ''
}
}
clickSubmit = () => {
const module = {
roles: this.state.roles || undefined
}
create(module).then((data) => {
if (data.error) {
this.setState({error: data.error})
} else {
this.setState({error: '', 'open': true});
}
})
}
clickAddRole = () => {
this.setState({roles: this.state.roles.concat([undefined]) });
};
handleChange = name => event => {
console.log(name);
this.setState({[name]: event.target.value});
console.log(this.state.roles);
}
render() {
const {classes} = this.props;
return (
<div>
<Button onClick={this.clickAddRole} >Add Role</Button>
{
this.state.roles.map((item, i)=> {
return (
<TextField className={classes.textField} id={'roles['+i+']'} label={'role '+i} key={i} onChange={this.handleChange('roles['+i+']')} />
)
})
}
</div>
)
}
}
I think you're making the whole code a bit overcomplicated creating names for each input field. What I would do is change the handleRolesChange or handleChange (not really sure if you changed its name) method so that it takes the index instead of a name.
handleRolesChange = index => event => {
const { roles } = this.state;
const newRoles = roles.slice(0); // Create a shallow copy of the roles
newRoles[index] = event.target.value; // Set the new value
this.setState({ roles: newRoles });
}
Then change the render method to something like this:
this.state.roles.map((item, index) => (
<TextField
id={`roles[${index}]`}
label={`role ${index}`}
key={index}
onChange={this.handleRolesChange(index)}
/>
))
Guy I have the issue (maybe temporarily).
I an array-element is a child of the array. so changing the data in the array-element does not need setState.
So this is what I did....
handleRolesChange = name => event => {
const i = [name];
this.state.roles[i]=event.target.value;
}
I also change the Textfield onchange parameter to
onChange={this.handleRolesChange(i)}
where i is the index starting from zero in the map function.
All this works perfectly as I needed.
However, if you think that I have mutated the roles array by skipping setState, I will keep the Question unanswered and wait for the correct & legitimate answer.
Thanks a lot for your support guys.
We must try and find the solution for such basic issues. :)
Are you positive it's not being set? From React's docs:
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
Usually logging state in the same block you set the code in will print the previous state, since state has not actually updated at the time the console.log fires.
I would recommend using React Dev Tools to check state, instead of relying on console.log.

Resources