How to setState for array immediately? - arrays

I ran into one problem connected with updating state in react.
Well, I have some data and I want to build a table with this data. But, I would like to filter it first. The filtration is working well, but I only have problem with updating filtered data and throwing it to the next component... (I know that setState is not working immediately...)
updatedReports in ReportTable component still has not filtered data...
What is the best way to fix it and to work with updating state for arrays.
export default class ReportContent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentReports: this.props.reports
};
}
_filterBy(option) {
let updatedReports = [...this.props.reports].filter(report => {
if (report.organizations === option || report.reportType === option) {
return report;
}
});
console.log(updatedReports);
this.setState({currentReports: updatedReports});
}
render() {
return (
<div className="reports-table">
<ReportMenu organizations={this.props.organizations} reportTypes={this.props.reportTypes}
filterBy={this._filterBy.bind(this)}/>
<ReportTable updatedReports={this.state.currentReports}/>
</div>
);
}
}

You can maybe use the component lifecycle methods. Here is more info.
componentDidMount should do the trick. You can do something like this :
componentDidMount(){
let updatedReports = [...this.props.reports].filter(report => {
if (report.organizations === option || report.reportType === option) {
return report;
}
});
this.setState({currentReports: updatedReports});
}
Or just call your method _filterBy inside componentDidMount.
Hope this helps.

There's nothing wrong with the code you provided. setState should work if invoked correctly. My bet is the problem is within your other components or the data.
Here's a snippet that uses your code. I have no idea how you implement your other components so I just made some assumptions here.
class ReportMenu extends React.Component {
render() {
return <div className="well well-default">
<select className="form-control" onChange={(e) => {
this.props.filterBy(e.target.value)
}}>
<option> - </option>
{this.props.organizations.map(
(item, index) => <option key={index}>{item}</option>
)}
</select>
<br/>
<select className="form-control" onChange={(e) => {
this.props.filterBy(e.target.value)
}}>
<option> - </option>
{this.props.reportTypes.map(
(item, index) => <option key={index}>{item}</option>
)}
</select>
</div>
}
}
class ReportTable extends React.Component {
render() {
return <table className="table table-bordered">
<tbody>
{this.props.updatedReports.map(
(item, index) => <tr key={index}>
<td>{index}</td>
<td>{item.organizations}</td>
<td>{item.reportType}</td>
</tr>
)}
</tbody>
</table>
}
}
class ReportContent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentReports: this.props.reports
};
}
_filterBy(option) {
let updatedReports = this.props.reports.filter(report => {
if (report.organizations === option || report.reportType === option) {
return report;
}
});
console.log(updatedReports);
this.setState({currentReports: updatedReports});
}
render() {
return (
<div className="reports-table">
<ReportMenu
organizations={this.props.organizations}
reportTypes={this.props.reportTypes}
filterBy={this._filterBy.bind(this)}/>
<ReportTable updatedReports={this.state.currentReports}/>
</div>
);
}
}
class App extends React.Component {
render() {
const reports = [
{
organizations: 'orgA',
reportType: 'typeA'
}, {
organizations: 'orgA',
reportType: 'typeB'
}, {
organizations: 'orgB',
reportType: 'typeA'
}, {
organizations: 'orgB',
reportType: 'typeB'
}
];
return <div className="container">
<h1 className="page-header">Reports</h1>
<ReportContent
reports={reports}
organizations={['orgA', 'orgB']}
reportTypes={['typeA', 'typeB']}/>
</div>
}
}
ReactDOM.render(<App/>, document.getElementById('root'));

Related

React - Make an FAQ class toggle on click

I am creating an FAQ with React and I have the questions in strong tags and the answers in p tags. On click of the strong tags I would like to add a class of active to the clicked tag. I am close but there is some sort of scope issue on my toggle function and I'm not sure how to move past it:
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Here's what I have so far
You need to bind toogleClass method to the Questions instance:
Opition one: Using bind
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.toggleClass.bind(this)
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Option 2: Using class property initializer
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.toggleClass.bind(this)
}
toggleClass = () => {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
To selected only the active question you can do this:
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
activeQuestion: null
};
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
data-question={item.question}
className={
item.question === this.state.activeQuestion ? "active" : null
}
onClick={() => {
console.log(item.question);
this.setState({ activeQuestion: item.question });
}}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Your function toggleClass needs to be an arrow function -> toggleClass = () => {...your code here...}. When it's a regular function the outer scope (where this.state is), is not being passed to your function. Without an arrow function, when you refer to this you are referring only to the scope of the toggleClass function, where state does not exist and so is undefined.
Working Code
Also due to setState being async, it's best practice to use current state by referencing it within the setState function like below:
toggleClass = () => {
this.setState(prevState => ({
active: !prevState.active
})
}
When you reference state outside of the setState function then pass it in, it's possible that the state would be different by the time you use it to set it.
i.e. say the currentState you got was true, and then your setState uses current state to set the opposite (False) by the time setState tries to set it, something else might have changed your existing state to False already and you are just setting it to False again (instead of modifying what the current state is at that time which would be False and you want want True). Unlikely in your case, but it is good practice to follow this because you might run into this issue elsewhere

Cannot change the state of parent component and re-render

im new to React, trying to make some simple 'Chat' app, stuck a bit in some feature.
im trying to make user list, that onClick (on one of the user) it will change the class (to active), and when hitting another user it will set the active class to the new user.
tried a lot of things, managed to make it active, but when hitting another user, the old one & the one receive the 'active' class.
here is my Parent componenet
class Conversations extends React.Component {
constructor(props) {
super(props);
this.loadConversations = this.loadConversations.bind(this);
this.selectChat = this.selectChat.bind(this);
this.state = { count: 0, selected: false, users: [] }
}
selectChat = (token) => {
this.setState({ selected: token });
}
loadConversations = (e) => {
$.get('/inbox/get_conversations', (data) => {
let r = j_response(data);
if (r) {
this.setState({ count: r['count'], users: r['data']});
}
});
}
componentDidMount = () => {
this.loadConversations();
}
render() {
return (
<div>
{this.state.users.map((user) => {
return(<User selectChat={this.selectChat} selected={this.state.selected} key={user.id} {...user} />)
})}
</div>
)
}
here is my Child componenet
class User extends React.Component {
constructor(props) {
super(props);
this.handleSelect = this.handleSelect.bind(this);
this.state = {
token: this.props.token,
selected: this.props.selected,
username: this.props.username
}
}
handleSelect = (e) => {
//this.setState({selected: e.target.dataset.token});
this.props.selectChat(e.target.dataset.token);
}
render() {
return (
<div data-selected={this.props.selected} className={'item p-2 d-flex open-chat ' + (this.props.selected == this.props.token ? 'active' : '')} data-token={this.props.token} onClick={(e) => this.handleSelect(e)}>
<div className="status">
<div className="online" data-toggle="tooltip" data-placement="right" title="Online"></div>
</div>
<div className="username ml-3">
{this.props.username}
</div>
<div className="menu ml-auto">
<i className="mdi mdi-dots-horizontal"></i>
</div>
</div>
)
}
Any help will be great...hope you can explain me why my method didnt work properly.
Thank you.
You can make use of index from map function to make element active.
Initially set selected to 0;
this.state = { count: 0, selected: 0, users: [] }
Then pass index to child component,also make sure you render your User component when you are ready with data by adding a condition.
{this.state.users.length > 0 && this.state.users.map((user,index) => {
return(<User selectChat={this.selectChat} selected={this.state.selected} key={user.id} {...user} index={index} />)
})}
In child component,
<div data-selected={this.props.selected} className={`item p-2 d-flex open-chat ${(this.props.selected === this.props.index ? 'active' : '')}`} data-token={this.props.token} onClick={() => this.handleSelect(this.props.index)}>
...
</div>
handleSelect = (ind) =>{
this.props.selectChat(ind);
}
Simplified Demo using List.

react state is one state behind button clicks

I am writing a simple react page that renders 2 different html tables based off of which button is clicked on the screen. The issue I am having is that the table that is rendered for each button click is associated with the previous button click. (E.G. if I click button 1 one time then click button 2 the table associated with button 1 will be displayed.)
I am new to react so in order to get the tables to update I refactored my code to hold as much of the state as possible in the App.js class, I created the toggleState callback to associate the button clicks with state change of the parent, and I then pass that to DataProvider via the endpoint property. I realize this is probably where the state / UI disconnect is occurring, but I'm uncertain of the cause since I'm adhering to react principles to the best of my capability.
my class structure is as follows:
App
/ \
/ \
/ \
DataProvider ButtonToggle
|
Table
If it is relevant the table class is building the table based off of an API call, I will add the code for this, but it is not causing me problems so I do not believe it to be the source of the issue.
App.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import DataProvider from "./DataProvider";
import Table from "./Table";
import ButtonToggle from "./ButtonToggle";
class App extends Component {
constructor(props){
super(props);
this.state = {
input : 'employees',
endpoint : "api/employees/"
};
console.log("constructor app: " + this.state.input + "\n" + this.state.endpoint);
}
toggleState(input) {
if(input == "employees") {
this.setState({input : input, endpoint: "api/employees/"});
}
else {
this.setState({input : input, endpoint: "api/categories/"});
}
console.log("toggleState " + this.state.input + "\n" + this.state.endpoint);
}
render() {
return (
<div className="col-lg-12 grid-margin">
<div className="card">
<div className="card-title">
<div className="row align-items-center justify-content-center">
<div className="col-3"></div>
<div className="col-6">
<h1> Striped Table</h1>
</div>
<div className="col-3"></div>
</div>
<ButtonToggle toggleInput={ (input) => this.toggleState(input)}/>
</div>
<div className="card">
<div className="card-title"></div>
<div className="card-body">
<DataProvider endpoint={this.state.endpoint}
render={data => <Table data={data} />} />
</div>
</div>
</div>
</div>
);
}
}
export default App;
DataProvider.js
class DataProvider extends Component {
static propTypes = {
endpoint: PropTypes.string.isRequired,
render: PropTypes.func.isRequired
};
constructor(props) {
super(props);
this.state = {
data: [],
loaded: false,
placeholder: "Loading..."
};
}
componentWillReceiveProps(props) {
console.log("dataprov: " + this.props.endpoint);
this.componentDidMount();
}
componentDidMount() {
fetch(this.props.endpoint)
.then(response => {
if (response.status !== 200) {
return this.setState({ placeholder: "Something went wrong" });
}
return response.json();
})
.then(data => this.setState({ data: data, loaded: true }));
}
render() {
const { data, loaded, placeholder } = this.state;
return loaded ? this.props.render(data) : <p>{placeholder}</p>;
}
}
export default DataProvider;
ButtonToggle.js
class ButtonToggle extends Component {
constructor (props) {
super(props);
}
render() {
return (
<div className="row align-items-center justify-content-center">
<div className="col-3 center-in-div">
<button type="button" className="btn btn-info btn-fw" onClick={this.props.toggleInput.bind(this, 'categories')}> Categories </button>
</div>
<div className="col-3 center-in-div">
<button type="button" className="btn btn-info btn-fw" onClick={this.props.toggleInput.bind(this, 'employees')}>
Employees
</button>
</div>
<div className="col-6"></div>
</div>
);
}
}
export default ButtonToggle;
Table.js : I don't think this is a problem, but I may stand corrected.
import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";
const Table = ({ data }) =>
!data.length ? (
<p>Nothing to show. Records: {data.length} </p>
) : (
<div className="table-responsive">
<h2 className="subtitle">
Showing <strong>{data.length} items</strong>
</h2>
<table className="table table-hover">
<thead>
<tr>
{Object.entries(data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</table>
</div>
);
Table.propTypes = {
data: PropTypes.array.isRequired
};
export default Table;
Below is the minimum working code I could come up with. Your Button and Table components can be dumb components which will get data from parent component and will present it.
Your Parent or container component will have logic to set the properties for Button and Table component.
As Table and Button components are dumb you can go with functional components.
I have added the code for calling api (I have tried to mimic the api call) and getting data in same parent component, you can separate it out.
You can work on style and validations as per your needs.
Let me know if you need any further help.
class ParentComponent extends Component {
constructor() {
super();
this.state = {
name: "Category"
}
this.onBtnClick = this.onBtnClick.bind(this);
}
componentDidMount() {
this.getData(this.state.name)
}
getData(name) {
if (name === "Category") {
this.apiCall("/Category").then((data) => {
this.setState({ data: data })
})
} else {
this.apiCall("/Employee").then((data) => {
this.setState({ data: data })
})
}
}
apiCall(url) {
return new Promise((res, rej) => {
setTimeout(() => {
if (url === "/Employee") {
res([{ "Emp Name": "AAA", "Emp Age": "20" }, { "Emp Name": "BBB", "Emp Age": "40" }])
} else {
res([{ "Cat Id": "XXX", "Cat Name": "YYY" }, { "Cat Id": "MMM", "Cat Name": "NNN" }])
}
}, 1000)
});
}
onBtnClick(name) {
let newName = "Category"
if (name === newName) {
newName = "Employee"
}
this.setState({ name: newName, data: [] }, () => {
this.getData(newName);
})
}
render() {
return (<>
<ButtonComponent name={this.state.name} onBtnClick={this.onBtnClick}></ButtonComponent>
<TableComponent data={this.state.data} />
</>)
}
}
const ButtonComponent = ({ name, onBtnClick }) => {
return <Button onClick={() => { onBtnClick(name) }}>{name}</Button>
}
const TableComponent = ({ data }) => {
function getTable(data) {
return < table >
<thead>
<tr>
{getHeading(data)}
</tr>
</thead>
<tbody>
{getRows(data)}
</tbody>
</table >
}
function getHeading(data) {
return Object.entries(data[0]).map((key) => {
return <th key={key}>{key[0]}</th>
});
}
function getRows(data) {
return data.map((row, index) => {
return <tr key={"tr" + index}>
{Object.entries(data[0]).map((key, index) => {
console.log(row[key[0]]);
return <td key={"td" + index}>{row[key[0]]}</td>
})}
</tr>
})
}
return (
data && data.length > 0 ?
getTable(data)
: <div>Loading....</div>
)
}

How to remove an element in reactjs by an attribute?

I want to get unknown key attribute using known ID so that i may delete corresponding div.
I tried using document.getElementById("a").getAttribute('key'); , but it isn't working. May be my concept is wrong.
class PostAdded extends Component {
constructor(props) {
super();
this.deletepost = this.deletepost.bind(this);
}
deletepost() {
let ab =document.getElementById("a").getAttribute('key');
console.log(ab)
}
render() {
return (
<div>
{ this.props.posts.map((post, i) =>
<div id="a" key={`i-${post.title}`}>
<span> <h3>{post.title}</h3><p>{post.post}</p></span>
<input type="button" value="Delete" onClick={this.deletepost}/>
</div>
) }
</div>
)
}
}
export default PostAdded;
If you were able to delete the div, that probably wouldn't end up working for you anyway because any state change would cause a re-render and it would appear again. Instead, you could keep track of your posts in state and then remove one of the posts from state in your deletepost method.
class PostAdded extends Component {
constructor(props) {
super();
this.state = {
posts: props.posts
}
this.deletepost = this.deletepost.bind(this);
}
deletepost(index) {
const newPosts = this.state.posts.splice(index)
this.setState({posts: newPosts})
}
render() {
return (
<div>
{ this.state.posts.map((post, i) =>
<div id="a" key={`i-${post.title}`}>
<span> <h3>{post.title}</h3><p>{post.post}</p></span>
<input type="button" value="Delete" onClick={() => this.deletepost(i)}/>
</div>
) }
</div>
)
}
}
export default PostAdded;

How to add conditional statement to React data.map function

I'm a React newbie and have a component which is outputting a checkbox list by mapping an array. This is the code that presently works.
export default class First extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data,
};
}
render() {
return (
<div>
{this.state.data.map(d =>
<label key={ d.name } className="control control--checkbox">{d.name}
<input type="checkbox"/>
<div className="control__indicator"></div>
</label>)}
</div>
);
}
}
What I need to do is add an if/else telling the return map function only to render the checkbox list if another variable in the array (called domain) = "HMO". I'm really struggling with the syntax. I think it needs to be inserted below the {this.state.data.map(d => line but am really stuck. Any help would be greatly appreciated.
I believe you're looking for something like this.
export default class First extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data,
};
}
render() {
return (
<div>
{this.state.data.map(d => {
if(d.domain === "HMO"){
return (
<label key={ d.name } className="control control--checkbox">{d.name}
<input type="checkbox"/>
<div className="control__indicator" />
</label>
)}
return null
})}
</div>
)
}
}
The .map function will iterate through each element in your data array, it will then check if that element's domain variable is equal to the string "HMO". If it is, it will return the html that displays the checkbox, otherwise it will return null.
Use Array.filter:
export default class First extends React.Component {
constructor(...args) {
super(...args);
this.state = {
data: data
};
}
getItems() {
return this.state.data
.filter(d.domain === "HMO")
.map(d => (
<label key={ d.name } className="control control--checkbox">{d.name}
<input type="checkbox"/>
<div className="control__indicator"></div>
</label>
));
}
render() {
return (
<div>
{this.getItems()}
</div>
);
}
}
The downside is that you loop twice compared to #MEnf's solution so it can be slower if you have a really long array. The upside is that it looks neater (I know that is quite subjective).

Resources