React Redux re-fetch data using state and state change - reactjs

I would like to pass data from my state to a fetch call (e.g. search term). When the state changes, it should re-fetch my data.
The lifecycle methods seem to be the place to do this, but when I use them it either does nothing (render is called before state change), or it goes on an endless loop.
Here is my simplified code:
#connect((store) => {
return {
collections : store.collections.collections
}
}, {
fetchCollections
})
export default class CollectionPane extends Component {
constructor(props){
super(props);
this.state = {
term : null
}
}
componentWillMount(){
this.handleFetchCollections(this.state.term); // This loads initial ok
}
componentDidUpdate(){
// this.handleFetchCollections(this.state.term); // This causes loop
}
handleFetchCollections(props, sort){
log('Fetching...', 'green');
this.props.fetchCollections(props, sort);
}
onSearch(){
const term = "test example";
this.setState({
term
})
}
render(){
const { collections } = this.props;
return (
<div>
<a style={{ display: "block", padding: "1em" }} onClick={this.onSearch.bind(this)}>Search</a>
<CollectionList collections={collections} />
</div>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>

You should compare your new state with the previous state before performing fetch:
componentDidUpdate(prevProps, prevState) {
if (prevState.term !== this.state.term) {
this.handleFetchCollections(this.state.term);
}
}

With react-redux you should change your state through actions and reducers. Then you can use async actions as described here: http://redux.js.org/docs/advanced/AsyncActions.html

Related

How to render updated state in react Js?

I am working on React Js in class component I am declaring some states and then getting data from API and changing that state to new value but React is not rendering that new value of state. but if I console.log() that state it gives me new value on console.
My code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState({ unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}
This is printing 2 on console but rendering 0 on screen. How can I get updated state(2) to render on screen.
and if I visit another page and then return to this page then it is rendering new value of state (2).
Please call getUnread() function in componentDidMount, something like this
componentDidMount() {
this.getUnread()
}
This is because in React class components, while calling setState you it is safer to not directly pass a value to set the state (and hence, re-render the component). This is because what happens that the state is set as commanded, but when the component is rerendered, once again the state is set back to initial value and that is what gets rendered
You can read this issue and its solution as given in react docs
You pass a function that sets the value.
So, code for setState would be
this.setState((state) => { unread: data.count });
Hence, your updated code would be :
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState((state) => { unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}

React JS with Redux is not updating the state on action triggered

I am developing a simple Web application using React JS and the Redux. What I am doing now just updating the state through the actions. But it is not working.
I have the component like this
#connect((store) => {
return {
tweets: store.tweets.tweets
}
})
class TweetListComponent extends React.Component{
constructor(props)
{
super(props);
this.props.dispatch(fetchTweets());
this.state = { tweets : this.props.tweets };
}
render()
{
var todos = this.state.tweets.map((item, index)=>{
return <li>{item}</li>;
})
return (
<div>
<h3>Tweet List</h3>
<ul>
{todos}
</ul>
</div>
);
}
}
module.exports = TweetListComponent;
As you can see in the constructor, I am calling fetchTweets() function which is the Redux action imported. Then getting the tweets property of the state of the TweetsReducer.
This is the tweet reducer definition.
export default function reducer(state={
tweets : [ "this is default" ]
}, action){
switch(action.type)
{
case 'FETCH_TWEETS': {
return { tweets: ["How", "are", "you", "doing"] };
}
}
return state;
}
According to my code, it should display this array, ["How", "are", "you", "doing"] because I called fetchTweets action again in the constructor to update the value. But it is still rendering this array, [ "this is default" ]. How can I solve the issue?
The problem is that you are setting the state based on props in constructor which is only called once and although you dispatch the action in constructor to get tweets the new updated tweets are not immediately available. You should directly use tweets from props instead of copying it to state since setting a state directly derivable from props is an antipattern
class TweetListComponent extends React.Component{
constructor(props)
{
super(props);
this.props.dispatch(fetchTweets());
}
render()
{
var todos = this.props.tweets.map((item, index)=>{
return <li>{item}</li>;
})
return (
<div>
<h3>Tweet List</h3>
<ul>
{todos}
</ul>
</div>
);
}
}
The usage should be like
#connect((store) => {
return {
tweets: store.tweets.tweets
}
}, {
fetchTweets
})
And in component use it as
componentDidMount() {
this.props.fetchTweets();
}
Also directly use tweets from props, don't store it in the state. You are not updating your state when props are changed

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.

Call a function on application startup in react

I'm trying to call a function from application startup. The function reads data from JSON via dataVar (set elsewhere) and tries to load it into {items} for further consumption:
const dataVar = JSONStuff;
class Global extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
items: []
}
this.init();
}
// componentDidMount() {
// This doesn't work either!
// this.init();
// }
init() {
let { items } = dataVar;
this.setState({items});
}
render() {
return (
<div className="Global">
<Gallery items={this.state.items}/>
</div>
)
}
}
Then in Gallery.js:
import React, { Component } from 'react';
class Gallery extends Component {
render() {
return (
<div>
<h3>gallery:</h3>
{
this.props.items.map((item, index) => {
let {title} = item.name;
return (
<div key={index}>{title}</div>
)
})
}
</div>
)
}
}
export default Gallery;
Not sure why Global can't call a function inside of itself. I've tried with and without "this." I either get error to where the app won't complile or I get:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
First of all, it's a warning, you probably better not call setState in componentDidMount.
My suggestion 1: assign value to state in constructor
constructor(props) {
super(props);
this.state = {
query: '',
items: dataVar.items,
};
}
Suggestion 2:
Do inside the componentWillReceiveProps
componentWillReceiveProps(nextProps) {
const { dataVar: items } = nextProps; // pass dataVar as props
this.setState({
items,
});
}
Plus try to debug your props and pay attention on your console for errors.

ReactJS: How can I change dynamically inserted Component's data

I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>

Resources