Fetching query in method of a class component - javascript

Apollo client (2.6.3) with react.
Is it possible to fetch data in a method of a class component?
I'm building a global search component and I want to fetch data only when 3rd (and each subsequent) character is typed. Right now it is implemented with fetch api but I want to switch to apollo client and graphql api.
Until this moment I haven't gotten any problems with apollo client API cause I use
<Query/> component.
I've tried to use new hooks api but it turned out that useLazyQuery() can be used only in a function component (rules of hooks).
This is what I have done so far. I know that component is messy and I'm open to suggestions. This is my first react app.
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Select, Icon, Button } from "antd";
import { es } from "../../../utils/queries";
import { useLazyQuery } from "#apollo/react-hooks";
const { Option, OptGroup } = Select;
class GlobalSearch extends Component {
constructor(props) {
super(props);
this.searchInput = React.createRef();
}
state = {
data: [],
value: undefined,
display: false
};
componentDidMount() {
document.addEventListener("click", this.onClickOutside);
}
generateOptions(data) {
return data.map(d => {
if (d._index === "cases") {
d.label = (
<div>
<Icon style={{ fontSize: "18px" }} type="tool" />
<span style={{ color: "#183247" }}>
{d._source.number + d._source.serialNumber}
</span>
</div>
);
} else if (d._index === "items") {
d.label = (
<div>
<Icon style={{ fontSize: "18px" }} type="barcode-o" />
<span style={{ color: "#183247" }}>{d._source.name}</span>
</div>
);
} else if (d._index === "people") {
d.label = (
<div>
<Icon
type="user"
style={{ fontSize: "18px", float: "left", marginTop: "3px" }}
/>
<div style={{ marginLeft: "26px" }}>
<span style={{ color: "#183247" }}>
{" "}
<div>{d._source.name + " " + d._source.surname}</div>
</span>
<div>{d._source.email}</div>
</div>
</div>
);
} else if (d._index === "counterparties") {
d.label = (
<div>
<Icon style={{ fontSize: "18px" }} type="shop" />
<span style={{ color: "#183247" }}>{d._source.name}</span>
</div>
);
} else {
d.label = "undefined";
}
return d;
});
}
//group data according to type of the search result (index e.g. cases, items, contacts)
setData = es_data => {
const data = {};
const map = new Map();
for (const item of es_data) {
if (!map.has(item._index)) {
map.set(item._index);
data[item._index] = [];
}
data[item._index].push(item);
}
this.setState({ data: data });
};
handleSearch = value => {
if (value.length > 2) {
//tutaj wyszukujemy dane na serwerze a wyniki callbackiem przesyłamy do state.data[]
//response.json() calls mixin methods from body
const host = window.location.hostname
// const { loading, data } = useLazyQuery(es(value));
// if (loading) return undefined;
// if (data) {
// this.setData(this.generateOptions(data));
// }
fetch(`http://${host}:3000/api/es?searchString=${value}`)
.then(response => response.json())
.then(es_data => {
this.setData(this.generateOptions(es_data));
});
}
};
handleChange = value => {
//przy kazdym wpisanym znaku aktualizuj wartosc pola
this.setState({ value });
};
handleSelect = (key, option) => {
console.log(key);
console.log(option);
};
handleBlur = e => {
this.setState({ display: false, value: undefined, data: [] });
};
onClick = () => {
this.setState({ display: !this.state.display });
};
getSearchField(options) {
if (this.state.display) {
return (
<Select
id="global-search-field"
optionLabelProp="label"
showSearch
value={this.state.value}
placeholder={"search..."}
style={{ display: this.state.display, width: "350px" }}
defaultActiveFirstOption={true}
showArrow={false}
filterOption={false}
onSearch={this.handleSearch}
onChange={this.handleChange}
onSelect={this.handleSelect}
notFoundContent={"nothing here..."}
onBlur={this.handleBlur}
autoFocus={true}
showAction={["focus"]}
dropdownClassName="global-search-dropdown"
>
{options}
</Select>
);
}
}
render() {
//generate options for each group (index)
const options = Object.keys(this.state.data).map(d => (
<OptGroup label={d}>
{this.state.data[d].map(d => (
<Option
className={d._index}
type={d._index}
key={d._id}
label={d.label}
>
<div>{d.label}</div>
</Option>
))}
</OptGroup>
));
return (
<span>
{this.getSearchField(options)}
<Button
id="global-search"
shape="circle"
icon="search"
onClick={this.onClick}
/>
</span>
);
}
}
export default GlobalSearch;

Apollo Client offers a Promise-based API that provides more imperative control over when/where your queries/mutations are run. Here's an example from their docs:
client
.query({
query: gql`
query GetLaunch {
launch(id: 56) {
id
mission {
name
}
}
}
`,
variables: { ... }
})
.then(result => console.log(result));
You can import your client and use this pattern in any method or function like any other async operation using Promises.

Related

react setState() from external?

New to Reactjs, I have followed a tut or 2 to build a relatively simple app, that sends queries to Mongodb and renders the results. Although I am yet to render them. I can pass the find() through and get back results that I like, and log them to the console, but for the life of me I cannot figure out how to get the results into "state", or anywhere else in the app. It's likely a very simple mistake somewhere. But I don't have enough knowledge of react to figure it out.
Here is the (small) App.js file in it's entirety, I thought it easier than trying to pick through it and make a valid sample.
// /client/App.js
import React, { Component } from 'react';
import axios from 'axios';
import * as PropTypes from "prop-types";
import {useEffect, useState} from "react";
function View(props) {
return null;
}
View.propTypes = {children: PropTypes.node};
function Text(props) {
return null;
}
function MyForm() {
const [user_search, setName] = useState("");
const handleSubmit = async (event) => {
event.preventDefault();
console.log(`The Search you entered was: ${user_search}`);
let felcher = user_search.split(/[ ,]+/);
let search_obj = {}
for (let i in felcher) {
search_obj[i] = felcher[i]
}
axios.post('http://localhost:3001/api/searchData', {
search_obj
}
).then(resp => {
console.log("RESPONSE FROM POST", resp['data'])
});
}
return (
<form onSubmit={handleSubmit}>
<label>Enter Search Terms:
<input
type="text"
value={user_search}
onChange={(e) => setName(e.target.value)}
/>
</label>
<input type="submit" />
</form>
)
}
let formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',})
Text.propTypes = {children: PropTypes.node};
class App extends Component {
// initialize our state
state = {
data: [],
_id: 0,
ticker: '',
primary_share: [],
title: null,
document_date: null,
release_date: null,
search_text: null,
url: null,
result_state: null,
};
componentDidMount() {
this.getDataFromDb();
if (!this.state.intervalIsSet) {
let interval = setInterval(this.getDataFromDb, 1000);
this.setState({ intervalIsSet: interval });
}
}
componentWillUnmount() {
if (this.state.intervalIsSet) {
clearInterval(this.state.intervalIsSet);
this.setState({ intervalIsSet: null });
}
}
getDataFromDb = () => {
fetch('http://localhost:3001/api/getData')
.then((data) => data.json())
.then((res) => this.setState({ data: res.data }));
};
render() {
const { data } = this.state;
return (
<div>
<MyForm />
<div class={"row"}>
<div class={"col-4"}>
{/*<ul>*/}
{/* {data.length <= 0*/}
{/* ? 'Getting Results......'*/}
{/* : data.map((dat) => (*/}
{/* <li class="border" style={{ padding: '10px' }} key={dat._id}>*/}
{/* <span style={{ color: 'gray' }}> Ticker: </span> {dat.ticker} <br />*/}
{/* <span style={{ color: 'gray' }}> Release Date: </span> {dat.release_date} <br />*/}
{/* <span style={{ color: 'gray' }}> Document Title: </span>{dat.title} <br />*/}
{/* <span style={{ color: 'gray' }}> Document URL: </span>{dat.url} <br />*/}
{/* </li>*/}
{/* ))}*/}
{/*</ul>*/}
</div>
</div>
</div>
);
}
}
export default App;
The area I am struggling with is where print the results to the console here ...
console.log("RESPONSE FROM POST", resp['data'])
In the "MyForm()" function. I feel if I could setState() there, but it appears to not work.
But I can't do anything else that gets them over to render. HELP!!!!
SetState is a hook that returns two items: the state and setter (or the function to set the state). In your case you will have a setState at the top of your function:
const [data, setData] = useState([]) // what ever you put as an argument will be the default data until it is set
const [err, setErr] = useState(null) // this will be our error state
In your axios request you will use:
axios
.post('http://localhost:3001/api/searchData', { search_obj })
.then(resp => {
setData(resp['data']) // see here we call the state function
})
.catch(err => {
setErr(err) // and here for our error
})
Then in our return we can use the data any way we like:
return (
<>
<div>{data}</data>
<div>{err ? err : 'no errors'}</div>
</>
)
Does that make sense? (Code not tested)

Antd Design EditableRow not changing buttons from "edit" to "save" and "cancel"

Im using Antd library and i can't seem to find where i have the bug.
This is my EditableTableCell component
import React, {Component} from 'react';
import { Form } from '#ant-design/compatible';
import '#ant-design/compatible/assets/index.css';
import { Input, InputNumber, Select, DatePicker } from "antd";
import moment from "moment";
import {EditableContext} from "./EditableTableRow";
const FormItem = Form.Item;
const Option = Select.Option;
class EditableTableCell extends Component {
getInput = (record, dataIndex, title, getFieldDecorator) => {
switch (this.props.inputType) {
case "number":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(
<InputNumber formatter={value => value} parser={value => value} />
)}
</FormItem>
);
case "date":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
initialValue: moment(record[dataIndex], this.dateFormat)
})(<DatePicker format={this.dateFormat} />)}
</FormItem>
);
case "select":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
initialValue: record[dataIndex]
})(
<Select style={{ width: 150 }}>
{[...Array(11).keys()]
.filter(x => x > 0)
.map(c => `Product ${c}`)
.map((p, index) => (
<Option value={p} key={index}>
{p}
</Option>
))}
</Select>
)}
</FormItem>
);
default:
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(<Input />)}
</FormItem>
);
}
}
render() {
const { editing, dataIndex, title, inputType, record, index,...restProps} = this.props;
return (
<EditableContext.Consumer>
{form => {
const { getFieldDecorator } = form;
return (
<td {...restProps}>
{editing ?
this.getInput(record, dataIndex, title, getFieldDecorator)
: restProps.children}
</td>
);
}}
</EditableContext.Consumer>
);
}
}
export default EditableTableCell;
This is my EditableTableCell component
import React, {Component} from 'react';
import { Form} from '#ant-design/compatible';
export const EditableContext = React.createContext();
class EditableTableRow extends Component {
render() {
return (
<EditableContext.Provider value={this.props.form}>
<tr {...this.props} />
</EditableContext.Provider>
);
}
}
export default EditableTableRow=Form.create()(EditableTableRow);
This is my ProductsPage component im having bug in
import React, {Component} from 'react';
import {Button, Layout, notification, Popconfirm, Space, Table,Typography} from "antd";
import {Link} from "react-router-dom";
import {Content} from "antd/es/layout/layout";
import EditableTableRow, {EditableContext} from "../components/EditableTableRow";
import EditableTableCell from "../components/EditableTableCell";
import API from "../server-apis/api";
import {employeesDataColumns} from "../tableColumnsData/employeesDataColumns";
import {CheckCircleFilled, InfoCircleFilled} from "#ant-design/icons";
class ProductsPage extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
error: null,
isLoaded: false,
editingKey: "",
errorMessage: "",
}
}
columns = [
...employeesDataColumns,
{
title: "Actions",
dataIndex: "actions",
width: "10%",
render: (text, record) => {
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{form => (<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>Save</a>)}
</EditableContext.Consumer>
<a onClick={this.cancel}>Cancel</a>
</span>
) : (
<Space size="middle">
<a onClick={() => this.edit(record.username)}>Edit</a>
<Popconfirm title="Are you sure you want to delete this product?"
onConfirm={() => this.remove(record.username)}>
<a style={{color:"red"}}>Delete</a>
</Popconfirm>
</Space>
);
},
}
];
isEditing = (record) => {
return record.username === this.state.editingKey;
};
edit(username) {
this.setState({editingKey:username});
}
cancel = () => {
this.setState({ editingKey: ""});
};
componentDidMount() {
this.setState({ loading: true });
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
API.get(`users/all`,{ headers: { Authorization: token}})
.then(res => {
// console.log(res.data._embedded.productList);
const employees = res.data._embedded.employeeInfoDtoList;
this.setState({loading: false,data:employees });
})
}
async remove(username) {
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
API.delete(`/users/${username}`,{ headers: { Authorization: token}})
.then(() => {
let updatedProducts = [...this.state.data].filter(i => i.username !== username);
this.setState({data: updatedProducts});
this.successfullyAdded("Employee is deleted. It wont have any access to the website anymore.")
}).catch(()=>this.errorHappend("Failed to delete"));
}
hasWhiteSpace(s) {
return /\s/g.test(s);
}
saveData(form,username) {
form.validateFields((error, row) => {
if (error) {
return;
}
const newData = [...this.state.data];
const index = newData.findIndex(item => username === item.username);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row
});
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
const response = API.put(`/users/${username}/update`, row,{ headers: { Authorization: token}})
.then((response) => {
this.setState({ data: newData, editingKey: ""});
this.successfullyAdded("Empolyee info is updated")
})
.catch(error => {
this.setState({ errorMessage: error.message });
this.errorHappend("Failed to save changes.")
console.error('There was an error!', error);
});
});
}
successfullyAdded = (message) => {
notification.info({
message: `Notification`,
description:message,
placement:"bottomRight",
icon: <CheckCircleFilled style={{ color: '#0AC035' }} />
});
};
errorHappend = (error) => {
notification.info({
message: `Notification`,
description:
`There was an error! ${error}`,
placement:"bottomRight",
icon: <InfoCircleFilled style={{ color: '#f53333' }} />
});
};
render() {
const components = {
body: {
row: EditableTableRow,
cell: EditableTableCell
}
};
const columns = this.columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => {
const checkInput = index => {
switch (index) {
case "price":
return "number";
default:
return "text";
}
};
return {
record,
// inputType: col.dataIndex === "age" ? "number" : "text",
inputType: checkInput(col.dataIndex),
dataIndex: col.dataIndex,
title: col.title,
editing: this.isEditing(record)
};
}
};
});
const { data, loading } = this.state;
return (
<Layout>
<div>
<Link to="/add-product">
<Button style={{float:"right", background: "#0AC035",marginBottom:"1em", marginTop:"1em" }}
type="primary">New emplyee</Button>
</Link>
</div>
<Content>
<Table components={components} bordered dataSource={data} columns={columns} loading={loading} rowKey={data.username} rowClassName="editable-row"/>
</Content>
</Layout>
);
}
}
export default ProductsPage;
This is the bug I'm having:
enter image description here
And i want to have this result like its shown in Antd docs:
enter image description here
Id really appreciate if you take a look and help me figure out where im wrong
Updated Solution:
I find the issue. In render where you map the columns, you just return the column if it's not an editable column. You can check the code below. I added a check if it's dataIndex === 'actions', then return the following code:
Please Follow the link:
https://react-ts-v3fbst.stackblitz.io
Changes:
1.In columns, i remove the render function from the action object:
{
title: 'Actions',
dataIndex: 'actions',
width: '10%',
},
2. In render function where you map the columns, add the following code before this condition if(!col.editable) {,:
if (col.dataIndex === 'actions') {
return {
...col,
render: (text, record) => {
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{(form) => (
<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>
Save
</a>
)}
</EditableContext.Consumer>
<a onClick={this.cancel}>Cancel</a>
</span>
) : (
<Space size='middle'>
<a onClick={() => this.edit(record.username)}>Edit</a>
<Popconfirm title='Are you sure you want to delete this product?' onConfirm={() => this.remove(record.username)}>
<a style={{ color: 'red' }}>Delete</a>
</Popconfirm>
</Space>
);
}
};
}
When you click on edit, you set the username as key for that particular row for editing, make sure you have username in each record. I tested this using the following data:
const data = [
{ id: 8, name: 'baun', model: '2022', color: 'black', price: 358, quantity: 3, username: 'brvim' },
{ id: 3, name: 'galileo', model: '20221', color: 'white', price: 427, quantity: 7, username: 'john' }
];
Most important, you should select that attribute as key that is unique in all records. As you are using username, i don't know what is your business logic or data looks like, but technically each record can have same username. So you must select something that would always be unique in your complete data.

Update chart on dropdown list change - pass props to children

I'm using react-vis to make a line chart. I have a parent component called RoomUtilisation and children called ChartGridLine. The parent fetch data (currently using mock data), then pass it into children via props. However, somehow the chart only render when I change the dropdown list several time. The children component also uses a wrong set of data sometimes e.g. selected class A but uses data for class B.
I tried debugging and it seems that nextProps and prevState is the same in some instances. Please help!
var dataArrayA = [];
var dataArrayB = [];
for (var item in sampleDataWeek) {
dataArrayA.push([
sampleDataWeek[item].week,
sampleDataWeek[item].occupancy,
sampleDataWeek[item].capacity
]);
}
for (var item in sampleDataWeekB) {
dataArrayB.push([
sampleDataWeekB[item].week,
sampleDataWeekB[item].occupancy,
sampleDataWeekB[item].capacity
]);
}
class RoomUltilisation extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
loading: true,
selectedClass: "LectureA",
selectedCourse: "COMP9517",
counter: 0 //dummy prop
};
this.classOptions = [
{ value: 0, label: "LectureA" },
{ value: 1, label: "LectureB" }
];
this.courseOptions = [
{ value: 0, label: "COMP9517" },
{ value: 1, label: "ARTS2663" }
];
this.handleChange = this.handleChange.bind(this);
this.addData = this.addData.bind(this);
}
componentDidMount() {
if (this.state.selectedClass === "LectureA") {
this.setState({
data: dataArrayA
});
}
this.setState({
loading: false
});
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
if (this.state.selectedClass === "LectureA") {
this.setState({
data: dataArrayA
});
} else if (this.state.selectedClass === "LectureB") {
this.setState({
data: dataArrayB
});
}
};
render() {
const { loading } = this.state;
if (loading) {
return (
<React.Fragment>
<NavBar />
<SideBar />
<div id="page-wrap">
<h1 style={{ padding: 20 }}>Class Attendance</h1>
<div>Loading Chart ....</div>
</div>
</React.Fragment>
);
}
return (
<React.Fragment>
<NavBar />
<SideBar />
<div id="page-wrap">
<h1 style={{ padding: 20 }}>Class Attendance</h1>
<label htmlFor="course" style={this.textAreaStyle}>
Select course
</label>
<select
id="selectedCourse"
value={this.state.selectedCourse ? this.state.selectedCourse : ""}
onChange={this.handleChange}
>
{this.courseOptions.map((e, key) => {
return (
<option key={key} value={e.label}>
{e.label}
</option>
);
})}
</select>
<label htmlFor="class" style={this.textAreaStyle}>
Select class
</label>
<select
id="selectedClass"
value={this.state.selectedClass ? this.state.selectedClass : ""}
onChange={this.handleChange}
>
{this.classOptions.map((e, key) => {
return (
<option key={key} value={e.label}>
{e.label}
</option>
);
})}
</select>
<div id="chart-wrap">
<ChartGridline
data={this.state.data}
// key={++this.state.counter}
/>
</div>
</div>
</React.Fragment>
);
}
}
export default RoomUltilisation;
class ChartGridline extends Component {
constructor(props) {
super(props);
this.state = {
highlightSeries: null,
highlightTip: null,
receivedData: null,
loading: true,
data: null,
value: null //value of mark?
};
this.translatedArray = [];
this.debouncedSetState = debounce(newState => this.setState(newState), 40);
this._rememberValue = this._rememberValue.bind(this);
this._forgetValue = this._forgetValue.bind(this);
}
async componentDidMount() {
await this.translateSeries(this.props.data);
this.setState({ loading: false });
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.data !== prevState.data) {
return {
data: nextProps.data
};
}
// Return null if the state hasn't changed
return null;
}
async componentDidUpdate(prevProps, prevState, snapshot) {
if (JSON.stringify(this.props) !== JSON.stringify(prevProps.data)) {
console.log("state has changed!");
await this.translateSeries(this.props.data);
}
// if (snapshot.loading) {
// console.log("computing....!");
// }
}
async translateSeries(data) {
this.translatedArray = [1].map(i =>
data.map(input => ({
x: new Date(input[0]),
y: input[1],
capacity: input[2]
}))
);
console.log("translated function: " + JSON.stringify(this.translatedArray));
}
_rememberValue(value) {
this.setState({ value });
}
_forgetValue() {
this.setState({ value: null });
}
axisProps = {
tickSizeInner: 0,
style: { line: { stroke: "#939393", strokeWidth: "1px" } }
};
hintStyle = {
fontSize: 14,
color: "black",
background: "#faffe6",
borderRadius: "5px",
border: "3px solid #fff"
};
render() {
const { highlightSeries, loading, value } = this.state;
console.log("render method is called");
if (loading) {
return (
<React.Fragment>
<div>loading...</div>
</React.Fragment>
);
}
return (
<div>
<DiscreteColorLegend
// items={["Attendance", "Enrolment"]}
items={["Attendance"]}
orientation="horizontal"
// style={{ position: "absolute", textAlign: "left", right: "25%" }}
strokeWidth="3px"
/>
<XYPlot
xDomain={[0, 20]}
key="1"
width={600}
height={600}
onMouseLeave={() => this.setState({ highlightTip: null })}
>
<XAxis
title="semester week"
{...this.axisProps}
tickFormat={String}
/>
<YAxis
title="occupancy"
{...this.axisProps}
// tickFormat={d => d + "%"}
tickFormat={d => d}
/>
{this.translatedArray.map((d, i) => (
<LineMarkSeries
key={i}
size={3}
data={d}
onValueMouseOver={this._rememberValue}
onValueMouseOut={this._forgetValue}
onSeriesMouseOver={() =>
this.debouncedSetState({ highlightSeries: d })
}
onSeriesMouseOut={() =>
this.debouncedSetState({
highlightSeries: null
})
}
stroke={d === highlightSeries ? "black" : bgColors.Blue}
/>
))}
{console.log("this.translatedArray: " + this.translatedArray)}
{value ? (
<Hint value={value} style={this.hintStyle}>
<HintContent value={value} />
</Hint>
) : null}
</XYPlot>
</div>
);
}
}
export default ChartGridline;
Hi based on given code i can understand that you state value is not changing properly please change your handleChange method as follows:
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
},() => {});
if (this.state.selectedClass === "LectureA") {
this.setState({
data: dataArrayA
},() => {});
} else if (this.state.selectedClass === "LectureB") {
this.setState({
data: dataArrayB
},() => {});
}
};

React-table Filter component lost state on re-render

I'm using react and react table. In my react table, I have a custom filter component which user can choose from the dropdown which filters he will use for the column. It's like less than, equal to, etc.
here I found some example and tried to make the same: https://codesandbox.io/s/03403m5xzp
everything works well when I'm using client side filtering but when using server-side data, there I get some problem.
problem is an example. I choose from dropdown some filter type like equal-to and open the input for typing. When the user types something I make a request with the onFetchData method for sending this filter values to backend API to getting new data and setting it to the table. When I got new data I change my state and of course, changing state of my filterComponent and user need to choose again the filter type and open the input for typing again.
what I can do for keeping the state of my filter component in the last state while table data change?
here is my react table and filterComponent code.
class ParentComponent extends Component {
state = {
betweens: false,
openTextBoxes: false
};
constructor() {
super();
this.state = {
pageSize: 15,
data: [],
pages: null,
loading: false
};
}
render() {
const { data, pages, loading } = this.state;
return (
<ReactTable
className="-striped -highlight"
data={this.state.data}
loading={this.state.loading}
manual
columns={columns}
pages={this.state.pages}
onFetchData={(state, instance) => {
this.setState({ loading: true });
let dataM = {
pagesize: state.pageSize,
page: state.page,
sorts: JSON.stringify(state.sorted),
filters: JSON.stringify(state.filtered)
};
axios
.post(baseUrl + "v1/report/data/list", dataM, {
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(response => {
this.setState({
data: response.data.result,
pages: Math.ceil(
response.data.record_count / this.state.pageSize
),
loading: false
});
console.log(this.state.data);
})
.catch(error => {
throw error;
});
}}
filterable={true}
resizable={true}
sortable={true}
defaultFilterMethod={filterCaseInsensitive}
defaultPageSize={15}
pageSizeOptions={[5, 10, 15, 20, 25, 50, 100]}
getTdProps={onRowClick}
minRows={undefined}
/>
);
}
}
class FilterInnerComponent extends React.Component {
state = {
betweens: false
};
constructor(props) {
super(props);
this.state = {
filterType: "All",
filterValue: "",
filterBetweenValue: "",
openTextBox: true
};
this.changeFilterValue = this.changeFilterValue.bind(this);
}
changeFilterType(event) {
console.log(event);
// console.log(this.props.saveLastStateState)
const filterType = event.target.value;
selectedFilterTypeGlobal = filterType;
this.setState({ filterType: filterType });
const newValue = "";
this.state.filterValue = newValue;
this.state.filterBetweenValue = newValue;
const newState = {
...this.state.filterType,
...this.state.filterValue,
...this.state.filterBetweenValue,
filterType
};
if (filterType === "is-between" || filterType === "is-not-between") {
this.state.betweens = true;
} else {
this.state.betweens = false;
}
if (filterType !== "All") {
// this.props.getFilterinnerComponent()
// this.state.openTextBox = true
this.setState({
openTextBox: true
});
} else {
this.setState({
openTextBox: true
});
}
this.setState(newState);
this.props.onChange(newState);
}
changeFilterValue(event) {
const filterValue = event.target.value;
selectedFilterValueGlobal = filterValue;
parseFloat(filterValue).toFixed(2);
const newState = {
...this.state,
filterValue
};
this.setState(newState);
this.props.onChange(newState);
}
changeBetweenFilterValue(event) {
if (event.keyCode === 13) {
}
const filterBetweenValue = event.target.value;
parseFloat(filterBetweenValue).toFixed(2);
const newState = {
...this.state,
filterBetweenValue
};
// Update local state
this.setState(newState);
// Fire the callback to alert React-Table of the new filter
this.props.onChange(newState);
}
render() {
return (
<div className="filter">
<SelectField
onChange={evt => this.changeFilterType(evt)}
style={{
height: "30px",
fontSize: "12px"
}}
value={this.state.filterType}
autoWidth
>
<MenuItem value="All">All</MenuItem>
<MenuItem value="is-equal-to">Is Equal To</MenuItem>
<MenuItem value="is-not-equal-to">Is Not Equal To</MenuItem>
<MenuItem value="greater-than">Greater Than</MenuItem>
<MenuItem value="greater-than-or-equal-to">
Greater Than Or Equal To
</MenuItem>
<MenuItem value="less-than">Less Than</MenuItem>
<MenuItem value="less-than-or-equal-to">
Less Than Or Equal To
</MenuItem>
<MenuItem value="is-between">Is Between</MenuItem>
<MenuItem value="is-not-between">Is Not Between</MenuItem>
</SelectField>
{this.state.openTextBox ? (
<TextField
onChange={this.changeFilterValue}
style={{
width: "100%",
height: "40px",
float: "left",
fontSize: "12px"
}}
type="text"
value={this.state.filterValue}
/>
) : null}
{this.state.betweens ? (
<TextField
onChange={evt => this.changeBetweenFilterValue(evt)}
style={{
width: "100%",
height: "40px",
float: "left",
fontSize: "12px"
}}
type="text"
value={this.state.filterBetweenValue}
/>
) : null}
</div>
);
}
}

React. Chatkit API. MessageList component bug - rendering messages from other rooms. Component Lifecycle and state

I'm experiencing some strange activity with my Chatkit app built using React. Essentially, I'm testing with two different users in different rooms. When I send a message from a user in one room. The other user is able to see that message, although they are not in the same room. Here's a screenshot of what's going on.
This only appears to happen when the users have been in the same room at least once.
Buggy Chat
I can tell the messages are being created correctly because I see them in the right place in the ChatKit API. Also, if I re-render the component, the messages end up in the right place. But The cross-room messaging bug still persists.
Corrected Chat
I'm under the impression that it definitely has something to do with the state of the MessageList component. I've made sure to update the component state every time we enter a new room, but I suppose the real question is whether or not other instances of the applications even care about the change in component state for a different instance.
So without further ado, here is my code:
ChatScreen (Main app)
import React from "react"
import Chatkit from "#pusher/chatkit"
import MessageList from "./MessageList"
import SendMessageForm from "./SendMessageForm"
import WhosOnlineList from "./WhosOnlineList"
import RoomList from "./RoomList"
import NewRoomForm from "./NewRoomForm"
import { getCurrentRoom } from "../../actions/chatkitActions"
import { connect } from "react-redux"
class ChatScreen extends React.Component{
constructor(props){
super(props)
this.state = {
messages: [],
currentRoom: {},
currentUser: {},
usersWhoAreTyping: [],
joinableRooms: [],
joinedRooms: [],
errors: {}
}
this.sendMessage = this.sendMessage.bind(this)
this.sendTypingEvent = this.sendTypingEvent.bind(this)
this.subscribeToRoom = this.subscribeToRoom.bind(this)
this.getRooms = this.getRooms.bind(this)
this.createRoom = this.createRoom.bind(this)
}
componentDidMount(){
//setup Chatkit
let tokenUrl
let instanceLocator = "somecode"
if(process.env.NODE_ENV === "production"){
tokenUrl = "somenedpoint"
} else {
tokenUrl = "http://localhost:3000/api/channels/authenticate"
}
const chatManager = new Chatkit.ChatManager({
instanceLocator: instanceLocator,
userId: this.props.chatUser.name,
connectionTimeout: 120000,
tokenProvider: new Chatkit.TokenProvider({
url: tokenUrl
})
})
//initiate Chatkit
chatManager.connect()
.then((currentUser) => {
this.setState({
currentUser: currentUser
})
//get all rooms
this.getRooms()
// if the user is returning to the chat, direct them to the room they last visited
if(this.props.chatkit.currentRoom.id > 0){
this.subscribeToRoom(this.props.chatkit.currentRoom.id)
}
})
}
sendMessage = (text) => {
this.state.currentUser.sendMessage({
roomId: this.state.currentRoom.id,
text: text
})
}
sendTypingEvent = () => {
this.state.currentUser
.isTypingIn({
roomId: this.state.currentRoom.id
})
.catch((errors) => {
this.setState({
errors: errors
})
})
}
getRooms = () => {
this.state.currentUser.getJoinableRooms()
.then((joinableRooms) => {
this.setState({
joinableRooms: joinableRooms,
joinedRooms: this.state.currentUser.rooms
})
})
.catch((errors) => {
this.setState({
errors: { error: "could not retrieve rooms"}
})
})
}
subscribeToRoom = (roomId) => {
this.setState({
messages: []
})
this.state.currentUser.subscribeToRoom({
roomId: roomId,
hooks: {
onNewMessage: (message) => {
this.setState({
messages: [...this.state.messages, message]
})
},
onUserStartedTyping: (currentUser) => {
this.setState({
usersWhoAreTyping: [...this.state.usersWhoAreTyping, currentUser.name]
})
},
onUserStoppedTyping: (currentUser) => {
this.setState({
usersWhoAreTyping: this.state.usersWhoAreTyping.filter((user) => {
return user !== currentUser.name
})
})
},
onUserCameOnline: () => this.forceUpdate(),
onUserWentOffline: () => this.forceUpdate(),
onUserJoined: () => this.forceUpdate()
}
})
.then((currentRoom) => {
this.setState({
currentRoom: currentRoom
})
this.getRooms()
//store currentRoom in redux state
this.props.getCurrentRoom(currentRoom)
})
.catch((errors) => {
this.setState({
errors: errors
})
})
}
createRoom = (roomName) => {
this.state.currentUser.createRoom({
name: roomName
})
.then((newRoom) => {
this.subscribeToRoom(newRoom.id)
})
.catch((errors) => {
this.setState({
errors: { error: "could not create room" }
})
})
}
render(){
const username = this.props.chatUser.name
return(
<div className="container" style={{ display: "flex", fontFamily: "Montserrat", height: "100vh"}}>
<div
className="col-md-3 bg-dark mr-2 p-0"
style={{display: "flex", flexDirection: "column", maxHeight: "80vh", padding: "24px 24px 0px"}}
>
<div style={{flex: "1"}} className="p-4">
<WhosOnlineList users={this.state.currentRoom.users}/>
<RoomList
roomId={this.state.currentRoom.id}
rooms={[...this.state.joinedRooms, ...this.state.joinableRooms]}
subscribeToRoom={this.subscribeToRoom}
/>
</div>
<NewRoomForm createRoom={this.createRoom} user={this.state.currentUser}/>
</div>
<div
className="col-md-9 border p-0"
style={{display: "flex", flexDirection: "column", maxHeight: "80vh"}}
>
<div className="mb-3">
{ this.state.currentRoom.name ? (
<h4
className="bg-black text-light m-0"
style={{padding: "1.0rem 1.2rem"}}
>
{this.state.currentRoom.name}
</h4>
) : (
this.props.chatkit.currentRoom.id > 0 ) ? (
<h3 className="text-dark p-4">Returning to room...</h3>
) : (
<h3 className="text-dark p-4">← Join a Room!</h3>
)}
</div>
<div style={{flex: "1"}}>
<MessageList messages={this.state.messages} room={this.state.currentRoom.id} usersWhoAreTyping={this.state.usersWhoAreTyping}/>
</div>
<SendMessageForm
sendMessage={this.sendMessage}
userTyping={this.sendTypingEvent}
currentRoom={this.state.currentRoom}
/>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return{
chatkit: state.chatkit
}
}
const mapDispatchToProps = (dispatch) => {
return{
getCurrentRoom: (currentRoom) => {
dispatch(getCurrentRoom(currentRoom))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatScreen)
MessageList (component)
import React from "react"
import ReactDOM from "react-dom"
import TypingIndicator from "./TypingIndicator"
class MessageList extends React.Component{
constructor(props){
super(props)
this.state = {
currentRoom: {}
}
}
componentWillReceiveProps(nextProps){
if(nextProps.room){
console.log(nextProps.room)
this.setState({
currentRoom: nextProps.room
})
}
}
componentWillUpdate(){
const node = ReactDOM.findDOMNode(this)
//scrollTop is the distance from the top. clientHeight is the visible height. scrollHeight is the height on the component
this.shouldScrollToBottom = node.scrollTop + node.clientHeight + 100 >= node.scrollHeight
}
componentDidUpdate(){
//scroll to the bottom if we are close to the bottom of the component
if(this.shouldScrollToBottom){
const node = ReactDOM.findDOMNode(this)
node.scrollTop = node.scrollHeight
}
}
render(){
const messages = this.props.messages
let updatedMessages = []
for(var i = 0; i < messages.length; i++){
let previous = {}
if(i > 0){
previous = messages[i - 1]
}
if(messages[i].senderId === previous.senderId){
updatedMessages.push({...messages[i], senderId: ""})
} else{
updatedMessages.push(messages[i])
}
}
return(
<div>
{this.props.room && (
<div style={{overflow: "scroll", overflowX: "hidden", maxHeight: "65vh"}}>
<ul style={{listStyle: "none"}} className="p-3">
{updatedMessages.map((message, index) => {
return (
<li className="mb-1" key={index}>
<div>
{message.senderId && (
<span
className="text-dark d-block font-weight-bold mt-3"
>
{message.senderId}
</span>
)}
<span
className="bg-info text-light rounded d-inline-block"
style={{padding:".25rem .5rem"}}
>
{message.text}
</span>
</div>
</li>
)
})}
</ul>
<TypingIndicator usersWhoAreTyping={this.props.usersWhoAreTyping}/>
</div>
)}
</div>
)
}
}
export default MessageList
RoomList (component)
import React from "react"
class RoomList extends React.Component{
render(){
const orderedRooms = [...this.props.rooms].sort((a, b) => {
return a.id - b.id
})
return(
<div>
{ this.props.rooms.length > 0 ? (
<div>
<div className="d-flex justify-content-between text-light mb-2">
<h6 className="font-weight-bold">Channels</h6><i className="fa fa-gamepad"></i>
</div>
<ul style={{listStyle: "none", overflow: "scroll", overflowX: "hidden", maxHeight: "27vh"}} className="p-2">
{orderedRooms.map((room, index) => {
return(
<li key={index} className="font-weight-bold mb-2">
<a
onClick={() => {
this.props.subscribeToRoom(room.id)
}}
href="#"
className={room.id === this.props.roomId ? "text-success": "text-info"}
style={{textDecoration: "none"}}
>
<span className="mr-2">#</span>{room.name}
</a>
</li>
)
})}
</ul>
</div>
) : (
<p className="text-muted p-2">Loading...</p>
)}
</div>
)
}
}
Here's the component (ChannelsContainer) that's rendering the ChatScreen as well
import React from "react"
import UsernameForm from "./UsernameForm"
import ChatScreen from "./ChatScreen"
import { connect } from "react-redux"
class ChannelsContainer extends React.Component{
constructor(props){
super(props)
this.state = {
chatScreen: false
}
}
componentWillMount(){
if(this.props.chatkit.chatInitialized){
this.setState({
chatScreen: true
})
}
}
componentWillReceiveProps(nextProps){
if(nextProps.chatkit.chatInitialized){
this.setState({
chatScreen: true
})
}
}
render(){
let chatStage
if(this.state.chatScreen){
chatStage = <ChatScreen chatUser={this.props.chatkit.chatUser}/>
} else{
chatStage = <UsernameForm/>
}
return(
<div style={{minHeight: "90vh"}}>
{chatStage}
</div>
)
}
}
const mapStateToProps = (state) => {
return{
chatkit: state.chatkit
}
}
export default connect(mapStateToProps)(ChannelsContainer)
Please let me know what you guys think.
Fixed. All I had to do was compare room id of the message against the current room id. If they're the same, then I'll update my component state messages field.
onNewMessage: (message) => {
if(message.room.id === this.state.currentRoom.id){
this.setState({
messages: [...this.state.messages, message]
})
}
}

Categories