fetch Data when context changes in React - javascript

I need to make a new api request to fetch data for a given dataId.
this value lives in the Context.
import { MyContext } from './Context'
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
dataId: this.context.state.dataId // tried setting state first but didn´t work.
}
this.details = this.details.bind(this)
}
details() {
fetch('https://api.mydomain.com/' + this.context.state.dataId)
.then(response => response.json())
.then(data => this.setState({ data: data }));
}
componentDidMount() {
this.details()
}
render() {
return(
<MyContext.Consumer>
{(context) => (
<div>data: {JSON.stringify(data)} dataId: {context.state.dataId}</div>
)}
</MyContext.Consumer>
)
}
}
MyComponent.contextType = MyContext;
export default MyComponent
from others components I can set new values like
this.context.setDataId(1)
and this will show up correctly but the problem is that is not making a new fetch to get new data for the dataId that changed in the Context.
not sure what´s the correct lifecycle method I can use to detect changes in the context and make a new call to this.details()
I didn´t add the Context code here because it works fine. but if you need to see it please let me know.

In react, you must use life cycle hooks to inspect data such as props or context, to know if the state needs to update for your component. The most common life cycle hook for this purpose is componentDidUpdate(). it gives you the ability to decide whether or not your component needs to update state/rerender based on changes in props that caused the component to update. the following should work for your use case:
import { MyContext } from './Context'
class MyComponent extends Component {
state = {
data:[],
dataId:null
}
details = () => {
// we only want to update if dataId actually changed.
if(this.context.state.dataId !== this.state.dataId){
this.setState({dataId:this.context.state.dataId});
fetch('https://api.mydomain.com/' + this.context.state.dataId)
.then(response => response.json())
.then(data => this.setState({ data: data }));
}
}
componentDidMount() {
this.details()
}
componentDidUpdate() {
this.details();
}
render() {
return(
<MyContext.Consumer>
{(context) => (
<div>data: {JSON.stringify(this.state.data)} dataId: {context.state.dataId}</div>
)}
</MyContext.Consumer>
)
}
}
MyComponent.contextType = MyContext;
export default MyComponent;

Related

Re-render child after parent state change with get request

So I'm a beginner with react and I was wondering how to re-render the child after setting the state in the parent (from the child). Here's a code sample. I have a function that calls a GET request using Axios and when I press the button in the child component ideally it will update the state in the parent and also re-render the child but it only does the former.
Parent:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
fetchData = () => {
axios
.get(url)
.then(res => this.setState({data: res.data}))
}
Render() {
return (<Child data={this.state.data} fetchData={this.fecthData}/>)
}
// ...
Child:
class Child extends Component {
// ...
render() {
const { data, fetchData } = this.props
// render data
return <button onClick={fetchData}>Change data then fetch</button>
}
}
Also, are you supposed to make a local state in the Child and set it as a copy of the Parent's state or just passing it down as a prop is okay?
Your parent component holds the data and the child uses it. It seems to me you're doing it the right way. Here is a fully working example:
Codesandbox
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
this.updateData = this.updateData.bind(this);
}
async fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
return response.json();
}
updateData() {
this.setState({ data: [] }) // Creates a flicker, just so you see it does refresh the child
this.fetchData().then((res) => this.setState({ data: res }));
}
render() {
return <Child data={this.state.data} onAction={this.updateData} />;
}
}
Note I renamed your child prop fetchData into onAction (I don't know what's the name of the action that triggers a refresh, could be onRefresh). It's always best to see components props with separation between data attributes and event attributes.
Even standard components have it this way: <input value={user.firstname} onChange={doSomething} />. So, better to prefix events by on, then the parent decides what to do with it. It's not the child's concern.
class Child extends Component {
render() {
const { data, onAction } = this.props;
return (
<>
<button onClick={onAction}>Change data then fetch</button>
{data.map((item) => (
<div key={item.id}>
{item.id} - {item.title}
</div>
))}
</>
);
}
}

React Parent Component's this.state is called after Child Component''s constructor

I'm encountering some counterintuitive behavior in react (at least to me). I have an API call that returns some JSON to be displayed. I wanted the result of this call to be in the state of a Parent Component('Problem Container'), and passed down as props to its child component ('Problem').
I was getting an undefined result in the child component so I did some console logging in 4 different places to see what was going on: once in each constructor, and once in each components "this.state" using my "getProblemsFromAPI()" function.
The Parent Component:
class ProblemContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
problem_set: getProblemsFromAPI()
};
{console.log(this.state.problem_set, 'parent constructor')}
}
render() {
return(
<div>
<Problem problem_set={this.problem_set} />
</div>
)
}
}
function getProblemsFromAPI(problem_set = {}) {
fetch("http://localhost:5000/api/problem_set/5")
.then(response => response.json())
.then((data) => {console.log(data, 'set state of parent'); return data});
}
The Child Component:
class Problem extends React.Component {
constructor(props){
super(props);
this.state = {
current_problem: '',
problem_set: getProblemsFromAPI()
}
console.log(this.props, 'child constructor')
}
render() {
return (
<div>
</div>
);
}
}
function getProblemsFromAPI(problem_set = {}) {
fetch("http://localhost:5000/api/problem_set/5")
.then(response => response.json())
.then((data) => {console.log(data, 'set state of child'); return data});
}
What I'm seeing in the console is:
"Parent Constructor"
"Child Constructor"
"Set State of Parent"
"Set State of Child"
This is weird to me because the "this.state" constructor of the Parent Component should be the first thing sequentially called. I can put the API call in the componentDidMount of the child component to move forward, that's not an issue. I just don't understand this behavior.
Is this happening because it's a function call and not a value? I'm new to both javascript and react. Any advice will help!
Thanks
Any side-effects like fetching data from an external API or any state variable update should be done after the component has mounted in componentDidMount life cycle method if using class components or in useEffect hook if using functional components. Since you said you want the ProblemContainer to pass the data to it's children. The ProblemContainer should be responsible for fetching the data.
For example,
class ProblemContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
problem_set: null,
error: undefined
};
}
componentDidMount() {
fetch("http://localhost:5000/api/problem_set/5")
.then(response => response.json())
.then((data) => {this.setState({problem_set: data})});
// never leave a promise with out catching the error
.catch(error => {
// you can console.log the error to see what is going wrong
this.setState({problem_set: null, error: error})
})
}
render() {
return(
<div>
<Problem problem_set={this.state.problem_set} /> // you need to pass the prop like this
</div>
)
}
}
In your problem component,
class Problem extends React.Component {
// you have access to the problem_set via this.props.problem_set
render() {
console.log(this.props.problem_set) // should show the expected result
return (
<div>
// component logic
</div>
);
}
}

Using ComponentDidMount to load data before page renders - React

I am using ComponentDidMount to call data from my database and render page when data is ready. However, i have noticed the speed of my application has reduced when navigating since i have to wait for the data.
This is happening when i have large data in the database i am retrieving. My question is, is there any way of optimizing this, or i just have to render page before data loads ?
Component.JS
componentDidMount()
{
this.fetchAllItems();
}
fetchAllItems(){
return this.fetchPost().then(([response,json]) => {
console.log('here now ',response);
console.log(localStorage.getItem('user_token'))
if(response.status === 200)
{
}
})
}
fetchPost(){
const URL = 'http://localhost:8000/api/';
return fetch(URL, {method:'GET',headers:new Headers ({
'Accept': 'application/json',
'Content-Type': 'application/json',
})})
.then(response => Promise.all([response, response.json()]));
}
Try to use axios to make call to API asynchronously, after it's done, just update your response data to state. No need to wait your page is finished loading or not, react will render by following changes of state value.
import React from 'react';
import axios from 'axios';
export default class MovieList extends React.Component {
state = {
movies: []
}
componentDidMount() {
axios.get(`http://localhost/movies`)
.then(res => {
const movies = res.data;
this.setState({ movies: movies });
})
}
render() {
const {
movies
} = this.state;
return (
<div>
<ul>
{ movies.map(movie => <li>{movie.name}</li>)}
</ul>
</div>
)
}
}
Have you tried the shouldComponentUpdate lifecycle method? This method accepts nextProps (new or upcoming props) and nextState (new or upcoming State) parameters. You can compare your next props and state (state preferably in your case) to determine if your component should re-render or not. Fewer re-renders equals to better speed and optimization. that means your pages will load faster. The shouldComponentUpdate method returns a boolean to determine if a page should re-render or not. Read more here. Also, Here's an example:
class App extends React.Component {
constructor() {
super();
this.state = {
value: true,
countOfClicks: 0
};
this.pickRandom = this.pickRandom.bind(this);
}
pickRandom() {
this.setState({
value: Math.random() > 0.5, // randomly picks true or false
countOfClicks: this.state.countOfClicks + 1
});
}
// comment out the below to re-render on every click
shouldComponentUpdate(nextProps, nextState) {
return this.state.value != nextState.value;
}
render() {
return (
<div>
shouldComponentUpdate demo
<p><b>{this.state.value.toString()}</b></p>
<p>Count of clicks: <b>{this.state.countOfClicks}</b></p>
<button onClick={this.pickRandom}>
Click to randomly select: true or false
</button>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
In your case all the optimization must be done in the backend.
But if there is something that can be done in React is using Should Component Update as previous comment mentioned.

Rendering previous state instead of current value selected in dropdown

I wrote the dropdown component that passes a selected value back to parent via callback function. From there I would like to simply render the selected value below the dropdown. Instead I have rendered previous state. I have no idea why that works like that, could someone explain me my app's behaviour and maybe give a hint how to fix it? I don't even know where to look for the answers.
index.js
import React, { Component } from 'react';
import './App.css';
import { Dropdown } from './components/dropdown'
class App extends Component {
state = {
response: "",
currA: ""
};
componentDidMount() {
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => console.log(err));
}
callApi = async () => {
const response = await fetch('/main');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
};
calculateRate = (currA) => {
this.setState({currA: currA});
}
render() {
return (
<div className="App">
<div>
<Dropdown callbackFromParent={this.calculateRate}/>
</div>
<p>
{this.state.currA}
</p>
</div>
);
}
}
export default App;
dropdown.js
import React from 'react';
export class Dropdown extends React.Component {
constructor(props){
super(props);
this.state = {
list: [],
selected: ""
};
}
componentDidMount(){
fetch('https://api.fixer.io/latest')
.then(response => response.json())
.then(myJson => {
this.setState({ list: Object.keys(myJson.rates) });
});
}
change(event) {
this.setState({ selected: event.target.value });
this.props.callbackFromParent(this.state.selected);
}
render(){
var selectCurr = (curr) =>
<select
onChange={this.change.bind(this)}
value={this.state.currA}
>
{(this.state.list).map(x => <option>{x}</option>)}
</select>;
return (
<div>
{selectCurr()}
</div>
);
}
}
Since your setState() is not a synchronous call, it might be that your callback is firing before the state of your dropdown is actually modified. You could try using the callback on setState...
change(event) {
this.setState({
selected: event.target.value
}, () => {this.props.callbackFromParent(event.target.value)});
;
}
...Or if your parent component is the only thing that cares about the selected value (my guess from your snip), you don't need to update the dropdown state at all.
change(event) {
this.props.callbackFromParent(event.target.value;)
}
Good luck!
Documentation:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

fetch JSON (http get) not working

I want run it using http get, but it not show nothing, Where is the error?. Angular http.get easier to get JSON and doing ngFor and show, but on React is little special. So, in conclusion I don't like do a simple "import data from './data.json'", I need load json from the cloud.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class App extends Component {
// 1.JSON
constructor(props) {
super(props);
this.state = {
items: [],
};
}
// 2. JSON
componentJSON() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => this.setState({ items: data.items }))
}
render() {
// this.componentJSON = this.componentJSON.bind(this);
this.setState({ items: data})
// 3. JSON
// const { items } = this.state;
return (
<Router>
<div className="App">
<ul>
{items.map(item =>
<li key={item.title}>
{item.title}
</li>
)}
</ul>
</div>
</Router>
);
}
}
export default App;
Working now!,
Thanks anyway friends!
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
items : []
};
// You should bind this object to componentWillMount method, other setState was not working
this.componentWillMount = this.componentWillMount.bind(this);
}
// This method is call before component will mounted
componentWillMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then( data => this.setState({ items : data }) );
}
render() {
const { items } = this.state;
return (
<ul>
{items.map(item =>
<li key={item.title}>
{item.title}
</li>
)}
</ul>
);
}
}
export default App;
In your function you are assuming that the context is the class, but its not, its not how js works, so when you are trying to use this.setState it would not work because the context of the function doesnt have any function called setState.
A simple way of solving this is binding the function to the class, by simply adding the following line in the ctor:
this.componentJSON = this.componentJSON.bind(this);
You need to call your componentJSON function.
It is best to do this within componentDidMount()
componentDidMount(){
this.componentJSON()
}
This will get called when the component is rendered in the browser.
It is a common mistake to call your API within componentWillMount() but this will make your API call happen twice.
As mentioned in my other comment, be careful about calling your API in your render function as it will mean that you API is called every time there is a re-render. A re-render happens after setting state and since you are setting state in your API call, it will cause an infinite loop
Use componentDidMount
componentDidMount ()
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => this.setState({ hits: data.hits }))
}

Categories