Updating react component from sideBar menu - reactjs

I have a simple react app that consists of 3 components:
1. SideBar that contains links
2. ItemList that contains data in a table
3. Wrapper that wraps both of them (I understood from some posts here that it is sometimes useful, as I want to update the ItemsList component after clicking on different links on the sideBar).
What I have working now:
In the main Wrapper component:
render() {
return (
<div>
<SideMenu handleClick={this.handleClick} />
<ItemsList url={this.state.currentUrl} />
</div>
);
}
as soon as the app starts, using componentDidMount() in ItemsList component, it fetches there the data, and display it. that works fine.
Problem is, when I click the links in the sideMenu component, I am changing the currentUrl in the state of the main wrapper, so then it will get re-rendered by the new url:
handleClick() {
this.setState({ currentUrl: 'here I put the new address to fetch from'});
}
but what gets fetched is data that is located in the previous url, not the one I had just changed it to.
Basically, right after I debug and check the state after I had changed it, the currentUrl stays the previous one, and then it re-renders the ItemList with the previous url.
my question is, how do I Change the content of the itemList, using this handleClick() method? Hopefully I will get some insights. thank you very much, I would appreciate your assistance.
the main wrapper Code:
class MainWrapper extends React.Component {
constructor() {
super();
this.state = {
currentUrl: 'current url to fetch from...',
data: []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ currentUrl: 'the new url ' });
}
render() {
return (
<div>
<SideMenu handleClick={this.handleClick} />
<ItemsList url={this.state.currentUrl} />
</div>
);
}
}
my Itemlist Component:
class ItemsList extends Component {
constructor(props) {
super(props);
this.state = { url: props.url, data: [] };
}
componentDidMount() {
return fetch(this.state.url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({ data: responseJson.data });
})
}
render() {
return (
displaying the table html tags..
}
</div>
)
}
}

You could the componentDidUpdate life cycle method in your itemList component. Each time the url changes, the listItem would presumably re-render based on what I understood from your question, so the componentDidUpdate method will fire. It is in this method where you can check for the new url and make the new request.
See more here.

Related

React component sometimes don't show up

I'm in process of learning React (currently high order component) and I have one problem. In my exercise, component sometimes display data and sometimes don't. My code consist of two Component
DisplayList - component that show data passed by props
GetAndDisplayData - high order component that receive DisplayList component, url of API, and desired parameter (to take from API)
Code:
DisplayList.js
class DisplayList extends React.Component{
render(){
return(
<ul>
{this.props.data.map((input, i) => <li key={i}>{input}</li>)}
</ul>
);
}
}
GetAndDisplayData.js
const GetAndDisplayData = (DisplayList, urlOfData, parameterToGet) =>
class GetAndDisplayDataClass extends React.Component{
constructor(props){
super(props);
this.state = {
loading: true,
urlOfData: urlOfData,
parameterToGet: parameterToGet,
data: []
}
}
componentDidMount(){
this.getData(urlOfData, parameterToGet)
}
getData(urlOfData,parameterToGet){
fetch(urlOfData)
.then(data => data.json())
.then(jsonData => {
jsonData.map(input =>
this.state.data.push(eval("input."+parameterToGet))
);
})
this.setState({loading: false})
console.log(this.state.data)
}
render(){
if(this.state.loading){
return(<p>Data is loading</p>)
}else{
return(
<div>
<p>Data loaded</p>
<DisplayList data={this.state.data} />
</div>
);
}
}
}
And call of HOC
render(){
const GetData = GetAndDisplayData(DisplayList, "https://restcountries.eu/rest/v1/all", "name" );
return(
<div>
<GetData/>
</div>
);
I suppose that problem is something about asynchronous, beacuse if I use some short list of data everthing is working great, but if I use this API and list of 250 object in list, sometimes data don't show up (and sometimes does). What am I doing wrong?
As you already said, data loading is asynchronous, so you must update loading state variable inside the callback function :
componentDidMount(){
this.loadData(urlOfData, parameterToGet)
}
loadData(urlOfData, parameterToGet){
fetch(urlOfData)
.then(data => data.json())
.then(jsonData => {
// I didn't understand how you want to map the data
const data = jsonData.map(...);
console.log(data);
// you must update the state here
this.setState({loading: false, data: data});
});
}

Unmounting child component throws can't call setstate on an unmounted component in reactjs

when i unmount child component i get can't setstate on unmounted component warning.
What i am trying to do?
I have a parent component ViewItems and child component ItemsList.
In the ViewItems component i retrieve the items list from the server using load_items method which in turn uses client.get_file method. and store those items list in state named "items". I call this load_items method in componenDidMount method. However this will not show the new details for another item.
To give a clear picture of the problem. Consider i render items in one page. When i click on one item it takes me to other page (in this case items component in mounted) and when i click a button click to view item details it lists the details related to that item. When i click the button to get back to list of items (page where we were before) and click another item. It should display details related to the new item clicked.
However, in this case when i click the new item it shows previous item details unless page refresh.
To overcome this, on componentDidUpdate i call this load_items method. This works. But, when i dont close the child component meaning the layout where details of item is shown...i get the can't call setstate on unmounted component warning. This error is shown after child component is unmounted.
Below is the code,
class ViewItems extends React.PureComponent {
constructor(props) {
super(props);
this.default_root_item = {
name: 'Items',
index: 0,
children: [],
};
this.state = {
root_items: this.default_root_item,
items: [],
};
}
componentDidMount() {
this.load_items();
}
componentDidUpdate() {
this.load_items();
}
componentWillUnmount() {
this.unlisten_path_change();
}
load_items = () => {
const file_name = 'file_name.json';
client.get_file(this.props.item_id, file_name, 'json')
.then((request) => {
const items = request.response;
this.setState({items: [this.default_root_item]});}
this.handle_path_change(this.props.location.pathname);})};
return (
<ChildComponent
on_close={this.handle_item_close}
root_item={this.state.root_item}/>)}
export default class ChildComponent extends React.PureComponent {
<Items
items={root_item}/>
function Items(props) {
return (
<ul className="Items_list">
<div className="items">
{props.items.map((item, index) => {
return (
<Item
key={index}
item={item}
/>
);
})}
</div>
</ul>
);}
}
First of all, do not make an API call in componentDidUpdate without making a check whether the props changed or not.
Second: While setting state from API request response, check if the component is still mounted or not
class ViewItems extends React.PureComponent {
constructor(props) {
super(props);
this.default_root_item = {
name: 'Items',
index: 0,
children: [],
};
this.state = {
root_items: this.default_root_item,
items: [],
};
this._isMounted = true;
}
componentDidMount() {
this.load_items();
}
componentDidUpdate(prevProps) {
if (prevProps.item_id !== this.props.item_id) {
this.load_items();
}
}
componentWillUnmount() {
this._isMounted = false;
this.unlisten_path_change();
}
load_items = () => {
const file_name = 'file_name.json';
client.get_file(this.props.item_id, file_name, 'json')
.then((request) => {
const items = request.response;
if (this._isMounted) {
this.setState({items: [this.default_root_item]});
this.handle_path_change(this.props.location.pathname);
}
})
};

What is best approach to set data to component from API in React JS

We have product detail page which contains multiple component in single page.
Product Component looks like:
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
Here we have 3 APIs for
- Details
- Similar Product
- Other Products
Now from Detail API we need to set data to these components
<Gallery/>
<Video/>
<Details/>
<Contact/>
In which component we need to make a call to API and how to set data to other components. Lets say we need to assign a,b,c,d value to each component
componentWillMount(props) {
fetch('/deatail.json').then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ...');
}
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
OR
Do we need to create separate api for each components?
Since it's three different components you need to make the call in the component where all the components meet. And pass down the state from the parent component to child components. If your app is dynamic then you should use "Redux" or "MobX" for state management. I personally advise you to use Redux
class ParentComponent extends React.PureComponent {
constructor (props) {
super(props);
this.state = {
gallery: '',
similarPdts: '',
otherPdts: ''
}
}
componentWillMount () {
//make api call and set data
}
render () {
//render your all components
}
}
The Product component is the best place to place your API call because it's the common ancestor for all the components that need that data.
I'd recommend that you move the actual call out of the component, and into a common place with all API calls.
Anyways, something like this is what you're looking for:
import React from "react";
import { render } from "react-dom";
import {
SearchBar,
Gallery,
Video,
Details,
Contact,
SimilarProd,
OtherProd
} from "./components/components";
class Product extends React.Component {
constructor(props) {
super(props);
// Set default values for state
this.state = {
data: {
a: 1,
b: 2,
c: 3,
d: 4
},
error: null,
isLoading: true
};
}
componentWillMount() {
this.loadData();
}
loadData() {
fetch('/detail.json')
.then(response => {
// if (response.ok) {
// return response.json();
// } else {
// throw new Error('Something went wrong ...');
// }
return Promise.resolve({
a: 5,
b: 6,
c: 7,
d: 8
});
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
if (this.state.error) return <h1>Error</h1>;
if (this.state.isLoading) return <h1>Loading</h1>;
const data = this.state.data;
return (
<div>
<SearchBar/>
<Gallery a={data.a} b={data.b} c={data.c} d={data.d} />
<Video a={data.a} b={data.b} c={data.c} d={data.d} />
<Details a={data.a} b={data.b} c={data.c} d={data.d} />
<Contact a={data.a} b={data.b} c={data.c} d={data.d} />
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
render(<Product />, document.getElementById("root"));
Working example here:
https://codesandbox.io/s/ymj07k6jrv
You API calls will be in the product component. Catering your need to best practices, I want to make sure that you are using an implementation of FLUX architecture for data flow. If not do visit phrontend
You should send you API calls in componentWillMount() having your state a loading indicator that will render a loader till the data is not fetched.
Each of your Components should be watching the state for their respective data. Let say you have a state like {loading:true, galleryData:{}, details:{}, simProducts:{}, otherProducts:{}}. In render the similar products component should render if it finds the respective data in state. What you have to do is to just update the state whenever you receive the data.
Here is the working code snippet:
ProductComponent:
import React from 'react';
import SampleStore from '/storepath/SampleStore';
export default class ParentComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
loading:true,
}
}
componentWillMount () {
//Bind Store or network callback function
this.handleResponse = this.handleResponse
//API call here.
}
handleResponse(response){
// check Response Validity and update state
// if you have multiple APIs so you can have a API request identifier that will tell you which data to expect.
if(response.err){
//retry or show error message
}else{
this.state.loading = false;
//set data here in state either for similar products or other products and just call setState(this.state)
this.state.similarProducts = response.data.simProds;
this.setState(this.state);
}
}
render () {
return(
<div>
{this.state.loading} ? <LoaderComponent/> :
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
{this.state.similarProducts && <SimilarProd data={this.state.similarProducts}/>}
{this.state.otherProducts && <OtherProd data={this.state.otherProducts}/>}
</div>
</div>
);
}
}
Just keep on setting the data in the state as soon as you are receiving it and render you components should be state aware.
In which component we need to make a call to API and how to set data
to other components.
The API call should be made in the Product component as explained in the other answers.Now for setting up data considering you need to make 3 API calls(Details, Similar Product, Other Products) what you can do is execute the below logic in componentDidMount() :
var apiRequest1 = fetch('/detail.json').then((response) => {
this.setState({detailData: response.json()})
return response.json();
});
var apiRequest2 = fetch('/similarProduct.json').then((response) => { //The endpoint I am just faking it
this.setState({similarProductData: response.json()})
return response.json();
});
var apiRequest3 = fetch('/otherProduct.json').then((response) => { //Same here
this.setState({otherProductData: response.json()})
return response.json();
});
Promise.all([apiRequest1,apiRequest2, apiRequest3]).then((data) => {
console.log(data) //It will be an array of response
//You can set the state here too.
});
Another shorter way will be:
const urls = ['details.json', 'similarProducts.json', 'otherProducts.json'];
// separate function to make code more clear
const grabContent = url => fetch(url).then(res => res.json())
Promise.all(urls.map(grabContent)).then((response) => {
this.setState({detailData: response[0]})
this.setState({similarProductData: response[1]})
this.setState({otherProductData: response[2]})
});
And then in your Product render() funtion you can pass the API data as
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details details={this.state.detailData}/>
<Contact/>
<SimilarProd similar={this.state.similarProductData}/>
<OtherProd other={this.state.otherProductData}/>
</div>
);
}
}
And in the respective component you can access the data as :
this.props.details //Considering in details component.

react waypoint onEnter call supress after first time

In my OnEnter callback, I am making an api call to fetch more data. The data fetched is added to the array of elements I have. When I scroll back up, the api call is triggered again as the component is back in the viewport.
Is there a way to get around this?
react waypoint
code sample:
Okay, let me clean that up a little bit: #jmeas
fetchData(props){ function to call the api with server side pagination
if(props.previousPosition != 'above') { //an attempt to check if we passed that waypoint, do not call the api again
this.setState({page: this.state.page + 1}, function () { //increment the page number
this.getCatalogItems(this.state.categoryId, this.state.page) //make api call with incremented page number
.then((res) => {
console.log("fetched more data with scroll", res) //results
})
})
}else{
console.log("not calling fetch data")
}
}
This is how I am calling the waypoint:
class ProductContainer extends React.Component {
constructor(props) {
super(props);
console.log("catalog product container initialized", this.props);
}
render() {
const {catalogProducts, getCatalogItems, renderWaypoint} = this.props;
console.log("props in roduct container", this.props)
return (
<div className="products-container">
{
catalogProducts && catalogProducts['products'] ?
catalogProducts['products'].map((product) => {
return (
<span>
HIIIIIIIIIIIIIIIIIIIIIIIIIII
<CatalogProduct product={product}/>
</span>
)
})
:
false
}
{renderWaypoint()}
########################################################################### way point here ################################################
</div>
);
}
}
ProductContainer.propTypes = {
catalogProducts: React.PropTypes.any.isRequired,
getCatalogItems: React.PropTypes.func.isRequired,
renderWaypoint: React.PropTypes.func.isRequired
};
export default ProductContainer;
What I want to do:
I have an infinite scroll catalog page. I wish to make the api call when user has scrolled down to the waypoint which as in the component above is after we have rendered the products returned from first api call and would like to make another round trip to the server and render
Without reviewing any code of your code... It seems as though you could just create flag and set it to true once the data has been loaded, then just check that flag before you load the data. Perhaps not the most elegant way, but it'll work.
class SomeClass extends React.Component {
constructor(props) {
super(props);
this.data = [];
this.fetched = [];
this._loadPageContent = this._loadPageContent.bind(this);
}
render(){
return (
<Waypoint
key={cursor}
onEnter={this._loadPageContent(this.props.pageId)}
/>
);
}
_loadPageContent(i) {
if(this.fetched.indexOf(i) <= -1){
//go get the data
this.data.push(someApiCall());
this.fetched.push(i);
}
}
}
export default SomeClass;

Access Other component variable/states

I want to access state data or Portfolio in TotalTab/ProfitTab/LossesTab. I want them to get the data updated ( get data bind ? ) when async fetchData is finish also.
My code is as below
class Portfolio extends Component{
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource : ""
};
}
componentDidMount() { //This function get called once when react finished loading. Something like jQueryDom Ready seems to be https://facebook.github.io/react-native/docs/tutorial.html
this.fetchData();
}
fetchData() {
fetch("http://beta.setmonitor.com/api/trading/summary?portfolio_id=3")
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: responseData,
isLoading: false
});
})
.done();
}
render() {
return (
<ScrollableTabView>
<TotalTab tabLabel="Total" />
<ProfitTab tabLabel="Profit" />
<LossesTab tabLabel="Losses" />
</ScrollableTabView>
);
}
};
I tried creating an another class for just fetching data and storing and use it like new ClassName but the problem is that async data don't get update in the view.
Utilize props. You can pass the data down to the Tab components and React will automatically keep it in sync. If needed, you can also declare a function in the Portfolio component and pass that down to the children as needed.
React Docs - Data Flow

Resources