Reactjs Component lifecycle - Fetching data from user input - javascript

I'm new to Reactjs. I try to build a filters system from a search result.
When user select a filter I would like get new data using an AJAX call according the chosen filter.
I set an eventHandler function on filter's checkbox which set state of my component. This make React re-render the component. But at this point, there is no newest data.
componentWillUpdate() seems perfect for this purpose but it will be deprecated on next release (17) unless using UNSAFE_componentWillUpdate().
How fetch newest data before re-rendering the component? Which lifecycle method is better choice?
First set of data is set on my index.html file
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Reactjs - Filtres component</title>
</head>
<body>
<div id="filtres" class="filtres" style="width:250px;"></div>
<script type="text/javascript">
var filtres = [{
"date": {
"libelle": "Year",
"buckets": {
"date2018": {
"name": "date[0]",
"value": "2018",
"title": 2018,
"libelle": 2018,
"nb": 1
},
"date2016": {
"name": "date[1]",
"value": "2016",
"title": 2016,
"libelle": 2016,
"nb": 54
},
"date2015": {
"name": "date[2]",
"value": "2015",
"title": 2015,
"libelle": 2015,
"nb": 70
}
}
}
},
{
// some filters
}
}];
</script>
<script src="dist/filtresComponent.js"></script>
</body>
</html>
FiltresComponent.js
import React from 'react'
import ReactDOM from 'react-dom'
import Filtres from './Filtres'
ReactDOM.render(<Filtres filtres={filtres} />, document.getElementById('filtres'));
Filtres.js
import React, { Component } from 'react'
import './Filtres.css'
import Crit from './Crit'
class Filtres extends Component {
constructor(props) {
super(props);
this.state = {
filtres : this.props.filtres,
appliedFiltres : {}
}
}
addFiltre = (filtreName, filtreValue) => {
console.log('addFiltre from FiltresComponent !!');
console.log('We add : ');
console.log('filtreName :' + filtreName + ' ,value : ' + filtreValue);
this.setState((state) => {
return {appliedFiltres: Object.assign(state.appliedFiltres, {[filtreName]: filtreValue})}
});
console.log(this.state);
}
// before re-rendering sounds good but will be deprecated on reactjs 17
componentWillUpdate = () => {
console.log('componentWillUpdate');
// Fetching before rendering ?
}
render() {
console.log('render Filtres.js');
return ([
this.state.filtres.map((crit, index) => {
let libelle = crit[Object.keys(crit)].libelle;
let critValues = Object.values(crit[Object.keys(crit)].buckets);
return <Crit key={index} libelle={libelle} critValues={critValues} addFiltre={this.addFiltre}/>
})
])
}
}
export default Filtres
Crit.js
import React, { Component } from 'react'
class Crit extends Component {
static defaultProps = {
open : true
}
constructor(props) {
super(props);
this.state = {
open : this.props.open
}
}
showHideCrit = (e) => {
if(this.state.open){
e.target.nextElementSibling.style.display = 'none';
this.setState({ open: false });
}else{
e.target.nextElementSibling.style.display = 'flex';
this.setState({ open: true });
}
}
addFiltre = (e) => {
console.log('addFiltre from CritComponent !!');
// User chosen filter
let filtreName = e.target.name;
let filtreValue = e.target.value;
// LiftUp
this.props.addFiltre(filtreName, filtreValue);
}
render() {
console.log('render Crit.js');
return ([
<div className="crit">
<a className={"js-head_crit " + (this.state.open ? 'open' : '' )+""} onClick={this.showHideCrit}>{this.props.libelle}</a>
<div className="crit-values">
{
this.props.critValues.map((critValue,index) => {
return (
<div key={index} className="crit-value" data-count={critValue.nb}>
<input type="checkbox" name={critValue.name} value={critValue.value} onChange={this.addFiltre}/>
<label className="crit-libelle">
{critValue.libelle}
<span className="crit-nb">{critValue.nb}</span>
</label>
</div>
)
})
}
</div>
</div>
])
}
}
export default Crit
Output
Here user want to filter by year : 2015
I add date[2] : 2015 to appliedFiltres using this.setState
Then I want to fetch data using the filter year = 2015
Finally re-render component with new values

A possible solution would be to trigger the fetch operation immediately after setting the new filters in the state, you can do this via a call back to this.setState method call:
addFiltre = (filtreName, filtreValue) => {
// ...other codes
this.setState(
{}, // state update value
this.fetchData() // The function to trigger the AJAX call
);
}
Note: The re-render should happen regardless of the triggered AJAX call, you shouldn't wait for the AJAX call to complete before doing the render because this would make you app un-usable(and miserable) for a while(until the AJAX call completes).
To ensure a good user experience and also avoid showing stale data, you can display maybe a loading component(which can be as simple as a text: loading...) while the fetch from sever is happening, for that you would need a state value to track the AJAX process.
In the code block below, I added a fetchingData boolean to the state, you can use that to track when the data fetching is happening and when it is done.
class Filtres extends Component {
constructor(props) {
// ...other codes
this.state = {
// ...other states
fetchingData: false,
};
}
// ...other methods
fetchData() {
// set fetchingData to true as soon as the AJAX process starts
this.setState({ fetchingData: true });
// make ajax call with set filters
// when ajax call is done, set it back to false
this.setState({ fetchingData: false });
}
render() {
if(this.state.fetchingData) {
// display loading component
} else {
// display the current data
}
// ...other methods
};

Related

ReactJs: componentWillReceiveProps doesn't get called for component whose redux action gets dispatched last

I have a component that displays four different charts:
class StudyingAnalytics extends Component {
render() {
return (
<div>
<MonthlyAnalytics></MonthlyAnalytics>
<WeeklyAnalytics></WeeklyAnalytics>
<DailyAnalytics></DailyAnalytics>
</div>
);
}
}
MonthlyAnalytics, WeeklyAnalytics, and DailyAnalytics are identical components. They just display different data. So I'll only give the details of MonthlyAnalytics:
MonthlyAnalytics.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { getMinutesSpentStudyingPerMonth } from "../../actions/analyticsActions";
import Spinner from "../common/Spinner";
import { Row } from "reactstrap";
import MonthlyChart from "./MonthlyChart";
class MonthlyAnalytics extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
const user_id = this.props.auth.user.id;
this.props.getMinutesSpentStudyingPerMonth(user_id);
}
render() {
let {
minutes_spent_studying_per_month,
minutes_spent_studying_per_month_loading,
minutes_spent_studying_per_month_error,
} = this.props.analytics;
let minutes_spent_studying_per_month_content;
if (minutes_spent_studying_per_month_loading) {
minutes_spent_studying_per_month_content = <Spinner />;
} else if (minutes_spent_studying_per_month_error) {
minutes_spent_studying_per_month_content = (
<p>{minutes_spent_studying_per_month_error.message}</p>
);
} else if (minutes_spent_studying_per_month) {
let last_year_in_months_data = Object.keys(
minutes_spent_studying_per_month
)[Object.keys(minutes_spent_studying_per_month).length - 1];
minutes_spent_studying_per_month_content = (
<Row>
<MonthlyChart
selected_analytics_year={last_year_in_months_data}
></MonthlyChart>
</Row>
);
}
return <div>{minutes_spent_studying_per_month_content}</div>;
}
}
MonthlyAnalytics.propTypes = {
auth: PropTypes.object.isRequired,
analytics: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
analytics: state.analytics,
});
export default connect(mapStateToProps, {
getMinutesSpentStudyingPerMonth,
})(MonthlyAnalytics);
The component that displays the chart is MonthlyChart.
MonthlyChart.js
import React, { Component } from "react";
import { Line } from "react-chartjs-2";
import {
Row,
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
} from "reactstrap";
import { connect } from "react-redux";
const options = {
scales: {
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
};
class MonthlyChart extends Component {
constructor(props) {
super(props);
this.state = {
minutes_spent_studying_per_month: {},
chartData: {},
selectedYear: 2020,
};
this.onYearSelected = this.onYearSelected.bind(this);
this.toggleYearDropDownMenu = this.toggleYearDropDownMenu.bind(this);
}
componentWillReceiveProps(props) {
console.group("Monthly.componentWillReceiveProps() is called.");
console.log("props: ", props);
console.groupEnd();
const { analytics, selected_analytics_year } = props;
const { minutes_spent_studying_per_month } = analytics;
if (minutes_spent_studying_per_month) {
this.setState({
minutes_spent_studying_per_month: minutes_spent_studying_per_month,
selected_analytics_year: selected_analytics_year,
});
}
}
toggleYearDropDownMenu() {
this.setState({
isYearDropDownMenuOpen: !this.state.isYearDropDownMenuOpen,
});
}
onYearSelected(e) {
const selectedYear = e.target.getAttribute("value");
this.setState({
selectedYear: selectedYear,
});
}
render() {
let labels =
this.state.minutes_spent_studying_per_month[this.state.selectedYear] ==
undefined
? []
: this.state.minutes_spent_studying_per_month[this.state.selectedYear]
.labels;
let data_values =
this.state.minutes_spent_studying_per_month[this.state.selectedYear] ==
undefined
? []
: this.state.minutes_spent_studying_per_month[this.state.selectedYear]
.data_values;
const data = {
labels: labels,
datasets: [
{
label: "Minutes Spent Studying per month",
data: data_values,
backgroundColor: "rgb(255, 99, 132)",
borderColor: "rgba(255, 99, 132, 0.2)",
},
],
};
return (
<>
<Line data={data} options={options} />
</>
);
}
}
const mapStateToProps = (state) => ({
analytics: state.analytics,
});
export default connect(mapStateToProps, {})(MonthlyChart);
In Redux debugging tool, I have noticed that the Chart whose API call gets executed last, is always the one that doesn't display the data and is the one whose componentWillReceiveProps doesn't get called.
This explains the bug but doesn't explain why componentWillReceiveProps doesn't get called for the component whose last redux action that fetches data gets dispatched last.
Example:
Notice how DailyChart.componentWillReceiveProps doesn't get called:
Notice how GET_MINUTES_SPENT_STUDYING_PER_DAY gets dispatched last:
And it does retrieve the daily chart data and stores it in the redux store as you see on the right.
But, DailyChart.componentWillReceiveProps doesn't get called, and so no data gets displayed as you see above.
I just can't link all my findings together in one coherent explanation for this bug.
NOTE 1: This doesn't always happen for DailyChart, it happens for the other charts. But, it almost always happens for the last chart.
NOTE 2: I noticed if I display only one chart, this bug always happens. In other words, no data gets displayed in that chart if it's the only one displayed.
Bottom line is that componentWillReceiveProps doesn't get called when it should always be called since the Redux code works perfectly.
So,it just doesn't make sense:
Why wouldn't get called for the chart whose data retrieving action gets dispatched?
Why doesn't it get called when I display one single chart?
I used componentDidMount instead of componentWillReceiveProps and my problem got solved.

When page first loaded or refreshed api post call don't wok well first time but after toggling button one or two times it works well

I have a toggle button in my page, when it is OFF it sends 0 value to my post API and when button is ON it sends 1 in API. But the problem is when project is loaded or refreshed by toggling the button state value doesn't change first time but by toggling one or two times it works well after. code is given below any solution would be great help. thanks.
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false,
value: 0
};
this.handleChange = this.handleChange.bind(this);
handleChange() {
if (this.state.checked === true) {
this.setState({ value:1, checked: false })
}
else if (this.state.checked == false) {
this.setState({ value:0, checked: true })
}
const article = {
"all_value":this.state.value
};
const headers = {
'api-key': 'key',
};
axios.post('url', article, { headers })
.then(function(response) {console.log(response);
})
};
This is only applicable where the component is functional. An example is below to change class component to functional component.
You're calling setState() and then using the value immediately. That will cause the component to rerender, with the correct state but by that point you've made the call with the previous state.
I would suggest the axios.post call is inside a useEffect hook, to detect changes
this.state = {
checked: false,
value: 0
};
useEffect(() => {
const article = {
"all_value": this.state.value
}
const headers = {
'api-key': 'key',
};
axios.post('url', article, { headers })
.then(function (response) {
console.log(response);
})
}, [this.state])
This will use setState to trigger a rerender with the new values, and then useEffect will trigger seeing the values have changed, and make a new call to the API.
EDIT: The this.state needed to have [ ]
Class based component to functional component
Taking the example from the question, it's trivial to convert a class component to a functional component
import React, { useState, useEffect } from 'react'
const Dashboard = (props) => {
const [dashboardState, setDashboardState] = useState({
checked: false,
value: 0
});
useEffect(() => {
const article = {
"all_value": dashboardState.value
}
const headers = {
'api-key': 'key',
};
axios.post('url', article, { headers })
.then(response => {
console.log(response);
})
}, [setDashboardState])
const handleChange = () => {
dashboardState.checked ?
setDashboardState({ value: 1, checked: false }) : setDashboardState({ value: 0, checked: true })
const article = {
"all_value": dashboardState.value
};
const headers = {
'api-key': 'key',
};
axios.post('url', article, { headers })
.then(response => {
console.log(response);
})
};
return (
<div>Do your rendering / use handleChange() down here</div>
)
}
export default Dashboard

how to keep the table from looping when get data from API React JS

I use MUIDataTable to make my table on React JS. I should get data that I want to display on the table from API, but if data in the API is 3 then the table is looping 3 times. I want to only call data once with many lines of data as a response.
Can anyone help me?
here's my code.
App.js
import React from "react";
import ReactDOM from "react-dom";
import MUIDataTable from "mui-datatables";
import axios from "axios";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
errors: null
};
}
getData = async () => {
const option = {
url: "API/URL",
method: 'POST',
data: { data: .... }
};
axios(option)
.then(response => {
this.setState({
data: response.data.data,
isLoading: false,
});
console.log(response.data);
})
// If we catch any errors connecting, let's update accordingly
.catch(error => {
console.log(error.response);
this.setState({ error, isLoading: false })
}
);
}
componentDidMount() {
this.getData();
}
render() {
const { isLoading, data } = this.state;
const columns = ["ID", "Name", "Identity"];
return (
data.map(post => {
const { id, name, identity } = post;
const data = [
[
[id],
[name],
[identity]
]
];
const options = {
filterType: "dropdown",
responsive: "scroll"
};
return (
<MUIDataTable
title={"View Data"}
data={data}
columns={columns}
options={options}
/>
);
})
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
export default App
Where's the code I must change to for data loops only and not a table?
ComponentDidMount() would run the call every time it mounts, meaning it could re-call the API on subsequent mounts. A quick fix would be to check for state on both the data values and perhaps a waitingResponse=true flag before making the call.

How to pass data between two react sibling components?

I have two components: which takes value from an input field. Second component is which I fetch api data. The problem is that I want to get the value from GetSearch as the value i search the API in Pexels.
I have tried to change my code multiple times. I just cant understand how it is supposed to be done, and how should I actually communicate together with my components.
import React from "react";
class GetSearch extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
var PassValue = React.CreateClass({
render: function() {
return (
<p>{this.state.value}</p>
);
},
});
return (
<form className="search-form">
<input
type="text"
placeholder="Search for images"
value={this.state.value}
onChange={this.handleChange}/>
<input type="submit"/>
</form>
);
}
}
export default GetSearch
import React, { Component } from 'react'
export default class Pexels extends Component {
componentDidMount(){
let query = "water"
const url = `https://api.pexels.com/v1/search?query=${query}e+query&per_page=15&page=1`
const api_key = "xxxxxxxxxxxx"
fetch(url, {
method: 'GET',
headers: new Headers({
'Authorization': api_key
})
})
.then(response => response.json())
.then(data => {
console.log(data)
})
.catch(error => console.error(error))
}
render() {
return (
<h1>Hello</h1>)
}
}
So as you can see now: Pexels sends a get request with the value of water: let query = "water", which works fine. But I need the value from
this.state.value in the GetSearch component
First, you need to create a parent class. Then You need to pass callback functions to the children as props. Here GetSearch component can be your child class. After you click search button your main class method will notify that change. Then create your logic as you want.
Follow this example code. thanks
Parent Component
var ParentComponent = React.createClass({
update: function() {
console.log("updated!");
},
render: function() {
<ChildComponent callBack={this.update} />
}
})
Child Component
var ChildComponent = React.createClass({
preupdate: function() {
console.log("pre update done!");
},
render: function() {
<button onClick={this.props.callback}>click to update parent</button>
}
})
You may need a store(just a function) to fetch url data rather than in a UI component Pexels.
In GetSearch invoke the store function with input as parameter and return a promise, and get data in callback.

How to TEST async calls made in componentDidMount that set the state of React Component

What is the best way to test that an async call within componentDidMount sets the state for a React component? For context, the libraries I'm using for testing are Mocha, Chai, Enzyme, and Sinon.
Here's an example code:
/*
* assume a record looks like this:
* { id: number, name: string, utility: number }
*/
// asyncComponent.js
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
// assume that I'm using a library like `superagent` to make ajax calls that returns Promises
request.get('/some/url/that/returns/my/data').then((data) => {
this.setState({
records: data.records
});
});
}
render() {
return (
<div className="async_component">
{ this._renderList() }
</div>
);
}
_renderList() {
return this.state.records.map((record) => {
return (
<div className="record">
<p>{ record.name }</p>
<p>{ record.utility }</p>
</div>
);
});
}
}
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in componentDidMount executes", () => {
// I'm thinking of using a library like `nock` to mock the http request
nock("http://some.url.com")
.get("/some/url/that/returns/my/data")
.reply(200, {
data: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
});
const wrapper = mount(<AsyncComponent />);
// NOW WHAT? This is where I'm stuck.
});
});
So, what you are really trying to test is that based on some mock data it "should render correctly ...".
As some people pointed out, a good way to achieve that is by placing the data fetching logic into a separate container and have a "dumb" presentation component that only knows how to render props.
Here is how to do that:
(I had to modify it a bit for Typescript with Tslint, but you'll get the idea)
export interface Props {
// tslint:disable-next-line:no-any
records: Array<any>;
}
// "dumb" Component that converts props into presentation
class MyComponent extends React.Component<Props> {
// tslint:disable-next-line:no-any
constructor(props: Props) {
super(props);
}
render() {
return (
<div className="async_component">
{this._renderList()}
</div>
);
}
_renderList() {
// tslint:disable-next-line:no-any
return this.props.records.map((record: any) => {
return (
<div className="record" key={record.name}>
<p>{record.name}</p>
<p>{record.utility}</p>
</div>
);
});
}
}
// Container class with the async data loading
class MyAsyncContainer extends React.Component<{}, Props> {
constructor(props: Props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
fetch('/some/url/that/returns/my/data')
.then((response) => response.json())
.then((data) => {
this.setState({
records: data.records
});
});
}
// render the "dumb" component and set its props
render() {
return (<MyComponent records={this.state.records}/>);
}
}
Now you can test MyComponent rendering by giving your mock data as props.
Ignoring the, sane, advice to think again about the structure, one way to go about this could be:
Mock the request (fx with sinon), to make it return a promise for some records
use Enzyme's mount function
Assert that the state to not have your records yet
Have your rest function use done callback
Wait a bit (fx with setImmediate), this will make sure your promise is resolved
Assert on the mounted component again, this time checking that the state was set
Call your done callback to notify that the test has completed
So, in short:
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in componentDidMount executes", (done) => {
nock("http://some.url.com")
.get("/some/url/that/returns/my/data")
.reply(200, {
data: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
});
const wrapper = mount(<AsyncComponent />);
// make sure state isn't there yet
expect(wrapper.state).to.deep.equal({});
// wait one tick for the promise to resolve
setImmediate(() => {
expect(wrapper.state).do.deep.equal({ .. the expected state });
done();
});
});
});
Note:
I have no clue about nock, so here I assume your code is correct
IMO, this is actually a common issue which appears more complicated because of promises and componentDidMount:
You're trying to test a functions which are only defined within the scope of another function. i.e. You should split your functions out and test them individually.
Component
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
request.get('/some/url/that/returns/my/data')
.then(this._populateState);
}
render() {
return (
<div className="async_component">
{ this._renderList() }
</div>
);
}
_populateState(data) {
this.setState({
records: data.records
});
}
_renderList() {
return this.state.records.map((record) => {
return (
<div className="record">
<p>{ record.name }</p>
<p>{ record.utility }</p>
</div>
);
});
}
}
Unit Test
// asyncComponentTests.js
describe("Async Component Tests", () => {
describe("componentDidMount()", () => {
it("should GET the user data on componentDidMount", () => {
const data = {
records: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
};
const requestStub = sinon.stub(request, 'get').resolves(data);
sinon.spy(AsyncComponent.prototype, "_populateState");
mount(<AsyncComponent />);
assert(requestStub.calledOnce);
assert(AsyncComponent.prototype._populateState.calledWith(data));
});
});
describe("_populateState()", () => {
it("should populate the state with user data returned from the GET", () => {
const data = [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
];
const wrapper = shallow(<AsyncComponent />);
wrapper._populateState(data);
expect(wrapper.state).to.deep.equal(data);
});
});
});
Note: I've written the unit tests from documentation alone, so the use of shallow, mount, assert, and expect might not be best practices.

Categories