React setState on another element - javascript

I am new to react and to get to grips with it, I'm converting existing project from jQuery to React.
I have six select boxes that update sequentially depending on the selection of the previous select box e.g. select option FOO from select box A and select box B must update with items corresponding to FOO.
I'll list some of references at the bottom
What I have so far:
I've got onchange events using fetch to call my api and get the data I want to use to populate the next select box and this is where I'm hitting a wall.
I've written two components MapControls and SelectBox. MapControls has an array of objects in its state that are used to generate a collection of SelectBox instances
Here's the MapControls component:
class MapControls extends React.Component {
state = {
selectBoxes: [
{
id: 'WorkSource',
name: 'WorkSource',
title:'Work Source',
containerId: 'WorkSourceContainer',
className: 'WorkSource',
childControllerMethod: 'GetBusinessTypeDropdown',
items: [{value:0, text:'Select'}, { value: '1', text: 'Routed' }],
child: 'BusinessType'
},
{
id: 'BusinessType',
name: 'BusinessType',
title: 'Business Type',
containerId: 'BusinessTypeContainer',
className: 'BusinessType',
childControllerMethod: 'GetWorkTypeDropdown',
items: [{ value: 0, text: 'Select' }],
child: 'WorkType'
},
//... more items ...
]
}
render() {
return this.state.selectBoxes.map(selectBox =>
<div key={selectBox.id} className='col-xs-2'>
<div id={selectBox.containerId}>
<SelectBox id={selectBox.id} name={selectBox.name} selectBox={selectBox} onChange={this.handleChange} />
</div>
</div>
);
}
};
and here's the SelectBox component. It's in the handleChange event where I want to be able to update the items in another SelectBox instance based on the ref. See the inline comments that describe my stumbling blocks
class SelectBox extends React.Component{
constructor(props) {
super(props);
this.state = { items: this.props.selectBox.items };
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const selectedValue = event.target.value;
const url = "/Home/" + event.target.dataset.childControllerMethod;
const data = JSON.stringify({ selectedValue: selectedValue });
fetch(url, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: data
}).then(response => {
if (response.status >= 400) {
console.log("Bad response from server");
}
return response.json();
}).then(data => {
// This updates the selectbox that was changed, which is not what I want
// this.setState({ items: data})
// This is what I was hoping would work, but I've discovered that BusinessType is a DOM element here, so setState is not valid
// this.refs.BusinessType.setState({ items: data });
// I hardcorded the 'BusinessType' as a ref just for testing because I know it's valid, but I want this to be dynamic
// findDOMNode seems to be somewhat of an anti-pattern, so I'd rather not do this. Not that the below code works because sibling is not a React object
// let sibling = ReactDOM.findDOMNode(this.refs.BusinessType);
// sibling.setState({ items: data });
});
}
render()
{
const optionItems = this.state.items.map((item, index) =>
<option key={index} value={item.value} >{item.text}</option>
);
return <div>
<label htmlFor={this.props.selectBox.id} >{this.props.selectBox.title}</label>
<select onChange={this.handleChange} id={this.props.selectBox.id} ref={this.props.selectBox.child} /*data-child={this.props.selectBox.child}*/ data-child-controller-method={this.props.selectBox.childControllerMethod}>
{optionItems}
</select>
</div>
}
};
ReactDOM.render(<MapControls />,
document.getElementById('mapControls')
);
Places I've looked:
http://jamesknelson.com/react-js-by-example-interacting-with-the-dom/
https://reactjs.org/docs/react-dom.html#finddomnode
https://reactjs.org/docs/refs-and-the-dom.html
http://www.mattmorgante.com/technology/dropdown-with-react
https://davidwalsh.name/get-react-component-element
https://www.carlrippon.com/react-drop-down-data-binding/

What you seem to be wanting is similar to Angular's two way binding using #input #output.
What you can do is the following:
class MapControls extends React.Component{
constructor(props){
super(props); // needed
this.state = {...} // Your declared state above
this.handleChange = this.handleChange.bind(this);
}
handleChange(data){
// Here you should receive data change emitted from child components
}
render(){
...
<SelectBox id={selectBox.id} name={selectBox.name} selectBox={selectBox} onChange={this.handleChange}
}
}
Handle change listener should happen on the parent component, consider moving the fetch command to the parent instead. What you need to emit to parent is the event.target of the child
class SelectBox extends React.Component{
constructor(props) {
super(props);
this.state = { items: this.props.selectBox.items };
this.emitChange = this.emitChange.bind(this);
// Changed funciton's name to emitChange to avoid confusion
}
emitChange(event) {
const selectedValue = event.target.value;
const url = "/Home/" + event.target.dataset.childControllerMethod;
const data = JSON.stringify({ selectedValue: selectedValue });
fetch(url, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: data
}).then(response => {
if (response.status >= 400) {
console.log("Bad response from server");
}
return response.json();
}).then(data => {
// While you could keep this here, it can be sent to parent, it's your decision
if(!!this.props.onChange){
// Here you'll emit data to parent via a props function
this.props.onChange(data);
}
});
}
render() {
const optionItems = this.state.items.map((item, index) =>
<option key={index} value={item.value} >{item.text}</option>
);
return <div>
<label htmlFor={this.props.selectBox.id} >{this.props.selectBox.title}</label>
<select onChange={this.emitChange} id={this.props.selectBox.id} ref={this.props.selectBox.child} /*data-child={this.props.selectBox.child}*/ data-child-controller-method={this.props.selectBox.childControllerMethod}>
{optionItems}
</select>
</div>
}
};
ReactDOM.render(<MapControls />,
document.getElementById('mapControls')
);
So, this is the general idea, you pass from parent a prop that's a function binded to it (parent), child will have a method that will execute the prop (if exists).
What I left out of this example:
You need to consider where to handle the fetch command accordingly (parent or child), remember that state defined in constructors is not updated if props change.
IF you want state to update on component's prop changes you'll have to use event cycles like "componentWillReceiveProps" (deprecated in recent version) or similar.
My general recommendation is child components should dwell on props, whereas parent component should handle state to be passed to child as props.
Passing function handles as props is a good way to intercommunicate your components, you could also use RXJS and pass Subscription types as props.

So the solution I found is as follows. Thank you to Gabriel for pointing me in the right direction. The final solution could be used for any filter component that needs to react to users' selections
I followed Gabriel's recommendation to call the parent's onChange event and handle the setting of the state in there.
I created the triggerSibling method so that I could hook into the componentDidUpdate() event and cascade the changes down the hierarchy of select boxes. So the onChange and componentDidMount events trigger the same logic.
Then in the MapControls onChange, I followed Gabriel's suggestion to handle the data there.
In the call to the parent's onChange event, I pass the data from the api call, along with the name of the child to target
The children of the parent component are accessible through this.refs and I discovered that I can access the specific child by using its name as a key in the array of children, as follows
this.refs[data.child].setState({ items: data.items })
I used the componentDidMount() event to set the initial value of the first selectBox and trigger the cascade of updates on the initial load
MapControls component:
class MapControls extends React.Component {
constructor(props) {
super(props); // needed
this.state = {
selectBoxes: [
{
id: 'WorkSource',
name: 'WorkSource',
title: 'Work Source',
containerId: 'WorkSourceContainer',
className: 'WorkSource',
childControllerMethod: 'GetBusinessTypeDropdown',
items: [{ value: 0, text: 'Select' }, { value: 'ROUTED', text: 'Routed' }],
child: 'BusinessType'
},
{
id: 'BusinessType',
name: 'BusinessType',
title: 'Business Type',
containerId: 'BusinessTypeContainer',
className: 'BusinessType',
childControllerMethod: 'GetWorkTypeDropdown',
items: [{ value: 0, text: 'Select' }],
child: 'WorkType'
},
... more ...
]
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(data) {
this.refs[data.child].setState({ items: data.items });
}
render() {
return this.state.selectBoxes.map(selectBox =>
<div key={selectBox.id} className='col-xs-2'>
<div id={selectBox.containerId}>
<SelectBox id={selectBox.id} name={selectBox.name} ref={selectBox.name} selectBox={selectBox} onChange={this.handleChange} />
</div>
</div>
);
}
};
SelectBox component:
class SelectBox extends React.Component{
constructor(props) {
super(props);
this.state = { items: this.props.selectBox.items };
this.emitChange = this.emitChange.bind(this);
}
triggerSibling (idOfDropdownToUpdate, selectedValue, url) {
const data = JSON.stringify({ selectedValue: selectedValue });
fetch(url, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: data,
}).then(response => {
if (response.status >= 400) {
console.log("Bad response from server");
}
return response.json();
}).then(data => {
if (!!this.props.onChange) {
// add the target to be updated as `child` property in the data passed to the parent
this.props.onChange({ child: this.props.selectBox.child, items: data });
}
});
}
componentDidMount() {
// Set the value of the first selectBox AFTER it has mounted, so that its `onChange` event is triggered and the `onChange` events cascade through the rest of the selectBoxes
if (this.props.name == "WorkSource") {
this.setState({ items: [{ value: 'ROUTED', text: 'Routed' }] });
}
}
// triggered when the component has finished rendering
componentDidUpdate(prevProps, prevState) {
const url = "/Home/" + this.props.selectBox.childControllerMethod;
if (this.props.selectBox.child != "")
this.triggerSibling(this.props.selectBox.child, this.state.items[0].value, url)
}
emitChange(event) {
const idOfDropdownToUpdate = event.target.dataset.child;
const selectedValue = event.target.value;
const url = "/Home/" + event.target.dataset.childControllerMethod;
this.triggerSibling(idOfDropdownToUpdate, selectedValue, url)
}
render()
{
const optionItems = this.state.items.map((item, index) =>
<option key={index} value={item.value} >{item.text}</option>
);
return <div>
<label htmlFor={this.props.selectBox.id} >{this.props.selectBox.title}</label>
<select onChange={this.emitChange} id={this.props.selectBox.id} data-child={this.props.selectBox.child} data-child-controller-method={this.props.selectBox.childControllerMethod}>
{optionItems}
</select>
</div>
}
};

Related

Creating an element using .map function in Javascript

I'm struggling while creating an element that is passed by the .map function. Basically, I want my webpage to create a div element with some date in it when a button is clicked for that I'm using a .map function but it isn't working out.
const handleSubmit = (e) => {
e.preventDefault();
const data = {title:`${title}`, desc:`${desc}`, date:`${date}`};
data.map(userinfo =>{
return(<div>
<h1>{userinfo.title}</h1>
</div>)
})
console.log(data);
}
In reactJS, if we want to display our data in HTML webpage we usually do that in the render funciton.
We can use userInfo variable in the state object.
The userInfo data is hardcoded for demonstration purposes but you can also populate the userInfo variable either using API or in any other way you like.
Moreover, showUserInfo is another variable (initially false) that would render the data once it is set to true
this.state = {
userInfo: [
{
title: 'one',
desc: '',
date: new Date()
},
{
title: 'two',
desc: '',
date: new Date()
}
],
showUserInfo: false
}
On a click event we can set showUserInfo to true using setState function.
more on setState function via this link ->
https://medium.com/#baphemot/understanding-reactjs-setstate-a4640451865b
handleSubmit = async (event) => {
event.preventDefault();
this.setState(
{
...this.state,
showUserInfo: true
}
)
}
In the render function, if showUserInfo is false then userInfo.map is never going to render unless showUserInfo is set to true which we do using a click listener that is associated with our function handleSubmit.
render(){
return (
<div>
<button onClick={this.handleSubmit}>Click Me</button>
{ this.state.showUserInfo &&
this.state.userInfo.map(item =>(
<div>
<p> {item.date.toString()} </p>
</div>
) ) }
</div>
);
}
Overall the result looks a something like this.
export default class App extends React.Component {
constructor() {
super();
this.state = {
showUserInfo: false,
userInfo: [
{
title: 'one',
desc: '',
date: new Date()
},
{
title: 'two',
desc: '',
date: new Date()
}
],
}
}
handleSubmit = async (event) => {
event.preventDefault();
this.setState(
{
...this.state,
showUserInfo: true
}
)
}
render(){
return (
<div>
<button onClick={this.handleSubmit}>Click Me</button>
{ this.state.showUserInfo &&
this.state.userInfo.map(item =>(
<div>
<p> {item.date.toString()} </p>
</div>
) ) }
</div>
);
}
}

React component loads data twice on moving back-and-forth between tabs

For some reason my React component seems to remember its old state when going to another tab and back again, instead of reloading completely.
Basically when I click on the "Create" tab in my navbar and back to the "Board" tab data is populated twice instead of once, see image below. When going back the Board component this.state has two of each taskIds, as if it the component state still had the data from the initial page load when loading again. I have a React component looking like this:
const columnOrder = ['todo', 'in-progress', 'in-review', 'done']
const EMPTY_COLUMNS = {
'todo': {
id: 'todo',
title: 'TODO',
taskIds: []
},
'in-progress': {
id: 'in-progress',
title: 'In Progress',
taskIds: [],
},
'in-review': {
id: 'in-review',
title: 'In Review',
taskIds: []
},
'done': {
id: 'done',
title: 'Done',
taskIds: []
}
};
export class Board extends Component {
constructor(props) {
super(props);
this.onLoadEpic = this.onLoadEpic.bind(this);
this.state = {
columnOrder: columnOrder,
columns: {
'in-progress': {
id: 'in-progress',
title: 'In Progress',
taskIds: [],
},
// ...more columns similar to above
},
};
// Load state data on mount
componentDidMount() {
loadEpic(arg1, arg2);
}
// Async function loading items from DB and formatting into useful columns
async loadEpic(arg1, arg2) {
axios.get(...)
.then((response) => {
let data = response.data;
let newTasks = {};
let newColumns = EMPTY_COLUMNS;
data.taskItems.forEach(function(item) {
let id = item.id.toString();
newColumns[item.status]["taskIds"].push(id);
newTasks[id] = {
...item,
id: id
}
});
this.setState({
tasks: newTasks,
columns: newColumns
});
})
}
render() {
// Prints ["7"] on initial load and ["7", "7"] after going back and forth
console.log(this.state.columns["in-progress"].taskIds);
return (
// Simplified, but this is the main idea
<Container>
<DragDropContext onDragEnd={this.onDragEnd}>
{
this.state.columnOrder.map((columnId) => {
const column = this.state.columns[columnId]
const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]
return (
<Column key={column.id} column={column} tasks={tasks}/>
)
}
}
</DragDropContext>
</Container>
)
}
}
and an App.js with Routing looking like this:
export default class App extends Component {
static displayName = App.name;
render () {
return (
<Layout>
<Route exact path='/' component={Board} />
<Route exact path='/create' component={Create} />
</Layout>
);
}
}
Okay, so I figured it out: it's the EMPTY_COLUMNS constant that is bugging out. When the component is re-rendered, the same EMPTY_COLUMNS object is referenced - so the constant is being appended to. Instead, I should make a copy of the empty columns:
// Before - same object is being appended to, doesn't work
let newColumns = EMPTY_COLUMNS;
// After - create a deep copy of the constant, does work
let newColumns = JSON.parse(JSON.stringify(EMPTY_COLUMNS));

Make POST request and update DB for each and every user in Child Component, using React Life Cycle Method

Here Table shows the previous month user salary details. When click the "Update" button, system will retrieve the necessary data for this month and calculate the new salary and properties and will update the child component table values. Child component has other Child Component Buttons too.
When updating the table raws with new values "Need to make a post request for each and every user and update the database iterately". Here infinity looping happening(infinity POST request for update DB) when render child component and its children.
Could you please suggest a way to update each and every user details to the database. The way to call Redux action function(this.props.updateUserLog(newUserLog.handle, userDetails)) inside the child component "RowComponent". When re-rendering it's children, the POST request must not send looping.
~ Parent Component ~
import { getDriverCommissionAlcohol } from "../redux/actions/dataActions";
class DriverPerfomance extends Component {
constructor(props = {}) {
super(props);
this.state = {
press: false,
};
}
UpdatePerformance = (event) => {
this.setState({ press: true });
this.props.getDriverCommissionAlcohol(month, year);
};
render() {
const {
data: {
drivers: { user, month, year, createdAt },
performance: { driverCommission, alcoholStatus },
},
UI: { loadingOffScrean },
} = this.props;
let DriverCommissionResults = {};
if (this.state.press) {
let combinedUser = {};
let recent = [];
if (Object.keys(DriverCommissionResults).length > 0) {
combinedUser.forEach((filteredPerson) => {
recent.push(
<RowComponent
key={filteredPerson.userId}
handle={filteredPerson.username}
monthRetrive={this.state.month}
yearRetrive={this.state.year}
month={month}
year={year}
drunkenPesentage={filteredPerson.drunkenPesentage}
press={true}
newMonthCalculationDone={true}
/>
);
});
} else {
recent = (
<Fragment>
{user.map((filteredPerson) => (
<RowComponent
key={filteredPerson.userId}
handle={filteredPerson.username}
month={month}
year={year}
press={false}
newMonthCalculationDone={false}
/>
))}
</Fragment>
);
}
}
return (
<Fragment>
<Button disabled={loadingOffScrean} onClick={this.UpdatePerformance}>
Update
</Button>
<table>
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody>{recent}</tbody>
</table>
</Fragment>
);
}
}
~ Child Component ~
import { updateUserLog } from "../redux/actions/dataActions";
class RowComponent extends Component {
constructor(props) {
super(props);
this.state = {
handle: "",
createdAt: "",
ranking: 0,
year: "",
month: "",
};
}
componentWillReceiveProps() {
const newUserLog = {
handle: this.props.handle,
createdAt: new Date().toISOString(),
ranking: NewRankingCalculate,
year: this.props.yearRetrive ? this.props.yearRetrive : this.props.year,
month: this.props.monthRetrive ? this.props.monthRetrive : "",
};
this.mapUserDetailsToState(newUserLog);
}
mapUserDetailsToState = (newUserLog) => {
this.setState({
handle: newUserLog.handle ? newUserLog.handle : "",
createdAt: newUserLog.createdAt ? newUserLog.createdAt : "",
ranking: newUserLog.ranking ? newUserLog.ranking : "",
year: newUserLog.year ? newUserLog.year : "",
month: newUserLog.month ? newUserLog.month : "",
});
const userDetails = {
handle: newUserLog.handle,
createdAt: newUserLog.createdAt,
ranking: newUserLog.ranking,
year: newUserLog.year,
month: newUserLog.month,
};
this.props.updateUserLog(newUserLog.handle, userDetails);
};
render() {
const {
member: { username, year, month, salary },
} = this.props;
let action = (
<DrunkenLog
handle={username}
month={this.state.month !== "" ? this.state.month : month}
year={this.state.year !== "" ? this.state.year : year}
/>
);
<tr>
<td>{initialSalary}</td>
<td>{this.state.salary !== 0 ? this.state.salary : salary}</td>
<td>{action}</td>
</tr>;
}
}
Expectation:
Update DB table for each and every user, by calling POST requests function inside the child component life cycle methods. Stop the infinity looping POST requests. And make post request once changing the props.
i've noticed that if (Object.keys(DriverCommissionResults).length > 0) expression in ParentComponent will always be false, right? because DriverCommissionResults is just an empty object, initialised two rows before this check :)
try extend RowComponent from PureComponent, this will ensure that RowComponent will rerender only if some of props really changed (see docs: https://reactjs.org/docs/react-api.html#reactpurecomponent)
but i don't like the whole idea of what you are doing here.
You are basically change state of ParentComponent on button click, and make side effect (call redux in this case) when component is receiving props.
I would suggest:
in ParentComponent - make side effect (update DB) right in the middle of Button.onClick (keeping state changes, because you need some sort of wait indicator maybe).
in RowComponent - if you are doing some side effects - better place for them is componentDidMount or componentDidUpdate (but in second place you better always check for props to really differ from previous ones!)

Need to Populate Component with Nested Data - ReactJS/ES6

I haven't seen a post to answer this question yet, so forgive me if I overlooked something. I'm trying to populate a table with ONLY the nested array from my data on the page. this.state.data SHOULD contain a subCollection of "masterTicketItems" from the "master ticket" that is being into this React JSX script from the Razor View.
The trouble comes when I'm trying to optimistically update a given table, and ONLY focusing on that, rather than actively posting data. I've been trying look around to understand this ReactJS and ES6 "spread" mapping technique, and I'm not sure if that applies here or something different.
In essence, I'm trying to ONLY map a "LogList" component with a collection contained INSIDE "this.state.data" inside the parent "Ticket" component. I currently have if/else logic to detect if there is nothing in the collection yet, and if not, try to populate it with a SINGLE object from the child component that was "posted" from the LogForm component. However, I can't seem to get the page to render with the single child in the table! Here's the Ticket JSX file snippet for review:
class Ticket extends React.Component {
constructor(props) {
super(props);
this.state = { data: this.props.initialData };
this.handleLogSubmit = this.handleLogSubmit.bind(this);
}
loadLogsFromServer() {
const xhr = new xmlhttprequest();
xhr.open('get', this.props.geturl + this.props.id, true);
xhr.onload = () => {
const data = json.parse(xhr.responsetext);
this.setState({ data: data });
};
xhr.send();
}
handleLogSubmit(log) {
const logs = this.state.data.apptConfirmItems;
// Optimistically set an id on the new comment. It will be replaced by an
// id generated by the server. In a production application you would likely
// use a more robust system for ID generation.
if (!logs) {
log.ticketItemId = 1;
const newLogs = log;
let masterItemsAccess = Object.assign({}, this.state.data);
masterItemsAccess.masterTicketItems = log;
this.setState((data) => Object.assign({}, data, { masterTicketItems: [log] }));
}
else {
log.ticketItemId = logs.length + 1;
const newLogs = logs.concat([log]);
this.setState({ data: newLogs });
}
const data = new FormData();
data.append('ItemType', log.itemType);
data.append('ItemDescription', log.text);
const xhr = new XMLHttpRequest();
//xhr.open('post', this.props.submitUrl, true);
//xhr.onload = () => this.loadLogsFromServer();
//xhr.send(data);
}
componentDidMount() {
window.setInterval(() => this.loadLogsFromServer(), this.props.pollInterval);
}
render() {
if (!this.state.data.masterTicketItems || this.state.data.masterTicketItems.Count == 0) {
return (
<div className="queue">
<h1>Affirm Logs</h1>
<h2>{this.state.data.summary}</h2>
<h5><i>{this.state.data.description}</i></h5>
<div><h3>No logs at this time!</h3></div>
<LogForm onLogSubmit={this.handleLogSubmit} />
</div>
);
}
else {
return (
<div className="queue">
<h1>Affirm Logs</h1>
<h2>{this.state.data.summary}</h2>
<h5><i>{this.state.data.description}</i></h5>
<LogList data={this.state.data.masterTicketItems}/>
<LogForm onLogSubmit={this.handleLogSubmit} />
</div>
);
}
}
}
class LogList extends React.Component {
render() {
const logNodes = this.props.data.masterTicketItems.map(log => (
<Log key={log.ticketItemId}>
<td>{log.itemType}</td> < td > {log.itemDescription}</td>
</Log>
));
const logRaw = this.props.data.masterTicketItmes.map(log => (
<tr>
<td>{log.itemType}</td><td>{log.itemDescription}</td>
</tr>
));
return (
<div className="logList">
<table id="affirmTable" className="table table-bordered table-hover table-striped">
<tbody>
{logNodes}
</tbody>
</table>
</div>
);
}
}
class LogForm extends React.Component {
constructor(props) {
super(props);
this.state = { itemType: 'Test Log', itemDescription: '' };
this.handleItemTypeChange = this.handleItemTypeChange.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleItemTypeChange(e) {
this.setState({ itemType: e.target.value });
}
handleTextChange(e) {
this.setState({ itemDescription: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
const itemType = this.state.itemType.trim();
const itemDescription = this.state.itemDescription.trim();
if (!itemType || !itemDescription) {
return;
}
this.props.onLogSubmit({ ItemType: itemType, ItemDescription: itemDescription });
this.setState({ itemType: '', text: '' });
}
render() {
return (
<form className="logForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.itemType}
onChange={this.handleItemTypeChange}
/>
<input
type="text"
placeholder="Enter working log here..."
value={this.state.itemDescription}
onChange={this.handleTextChange}
/>
<input type="submit" value="Post" />
</form>
);
}
}
I purposely left out the child Log component, since I'm confident that when this is addressed that component WILL populate with the table data as expected.
Should I use nested mapping with ES6? Is there a simpler way? I'm trying to understand React state logic a little bit better.

How to fill dropdown with JSON data in React?

I try to fill in a dropdown with data from the JSON format but for now the dropdown is empty (no results found...)
I certainly have a mistake and I can not understand where I'm confusing.
I will attach a screen of my API.
I want to get Station and NameStation..
API for Stations
My code:
import React, { Component } from 'react';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
function parseStations(stations){
return stations.map((station) => {
return { label: station.NameStation, value: station.Station };
});
}
export default class Weather extends Component {
constructor(props) {
super(props);
this.state = {
options: [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' }
], stations: [
],
value: null
}
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.setState({ value: event.value });
console.log('Boolean Select value changed to', event.value);
}
componentDidMount() {
this.getStations();
}
getStations() {
fetch('http://localhost:56348/api/stations', {
data: 'Station',
data: 'NameStation',
method: "GET"
}).then(res => res.json())
.then(res => this.setState({ stations: parseStations(res.stations) }))
//.then(res => this.setState({ stations: res.stations }))
//.catch(e => )
}
render() {
return (
<div className="MasterSection">
<div className="wrapper">
<div className="section">Изберете № на станция</div>
<Select
onChange={this.onChange}
//options={this.state.options}
options={this.state.stations}
value={this.state.value}
clearable={false}
/>
</div>
<div class="section">
<input type="text" class="form-control" placeholder="Брой дни назад" aria-label="Username" aria-describedby="basic-addon1"></input>
</div>
<div class="section">
<button type="button" class="btn btn-outline-dark">Покажи</button>
</div>
</div>
);
}
}
Seems you made a typo naming the prop stations instead of options :
<Select
onChange={this.onChange}
options={this.state.stations} // here
value={this.state.value}
clearable={false}
/>
Edit : you'll need to parse your json first to pass a proper array of objects like this : [{ label: nameStation, value: Station }]
Edit 2 : Here's a parser for your data :
function parseStations(stations){
return stations.map((station) => {
return { label: station.NameStation, value: station.Station };
});
}
You can call this in your async request before setting the state :
.then(res => this.setState({ stations: parseStations(res.stations) }))
componentDidMount() is executed only after render() is completed. so there's no way getStations() gets executed at the time your UI gets rendered. it is not a good idea to setState inside componentDidMount() as it triggers re rendering. use componentWillMount() instead.
correct the typo that Dyo mentioned and use options={this.state.stations}

Categories