How store data from fetch - javascript

I'm pretty new in React and need some help.
I wanted display data from a movie database based on the search term. I'm using fetch inside my getMovies methode to get the data. The data is stored in data.Search but I don't know how to access it and store it in a variable.
class DataService
{
getMovies (searchTerm) {
//let dataStorage; //store somehow data.Search inside
fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then(function(data) {
return data.Search;
}).catch(function(error) {
console.log(error);// Error :(
});
}
//return dataStorage; //return data.Search
}

The below code is the correct react's way for your case, as simple as this:
import React from 'react';
export default class DataService extends React.Component {
constructor(props) {
super(props);
this.state = {
search_data: [], //for storing videos
};
this.getMovies = this.getMovies.bind(this); //bind this function to the current React Component
}
getMovies (searchTerm) {
fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then((data) => { //so that this callback function is bound to this React Component by itself
// Set state to bind search results into "search_data"
// or you can set "dataStorage = data.Search" here
// however, fetch is asynchronous, so using state is the correct way, it will update your view automatically if you render like I do below (in the render method)
this.setState({
search_data: data.Search,
});
});
}
componentDidMount() {
this.getMovies(); //start the fetch function here after elements appeared on page already
}
render() {
return (
{this.state.search_data.map((video, i) =>{
console.log(video);
// please use the correct key for your video below, I just assume it has a title
return(
<div>{video.title}</div>
)
})}
);
}
}
Feel free to post here any errors you may have, thanks

There are several ways of doing asynchronous tasks with React. The most basic one is to use setState and launch a Promise in the event handler. This might be viable for basic tasks but later on, you will encounter race conditions and other nasty stuff.
In order to do so, your service should return a Promise of results. On the React side when the query changes, the service is called to fetch new results. While doing so, there are few state transitions: setting loading flag in order to notify the user that the task is pending and when the promise resolves or rejects the data or an error is stored in the component. All you need is setState method.
More advanced techniques are based on Redux with redux-thunk or redux-saga middlewares. You may also consider RxJS - it is created especially for that kind of stuff providing debouncing, cancellation and other features out of the box.
Please see the following example of simple search view using yours DataService.
class DataService
{
//returns a Promise of search results, let the consumer handle errors
getMovies (searchTerm) {
return fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then(function(data) {
return data.Search;
})
}
}
const SearchResults = ({data, loading, error}) =>
<div>
{
data && data.map(({Title, Year, imdbID, Type, Poster}) =>
<div key={imdbID}>
{Title} - {Year} - {Type}
<img src={Poster} />
</div>
)
}
{loading && <div>Loading...</div>}
{error && <div>Error {error.message}</div>}
</div>
class SearchExample extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
data: [],
loading: false
};
}
handleChange(event) {
const service = new DataService();
//search query is target's value
const promise = service.getMovies(event.target.value);
//show that search is being performed
this.setState({
loading: true
})
//after the promise is resolved display the data or the error
promise.then(results => {
this.setState({
loading: false,
data: results
})
})
.catch(error => {
this.setState({
loading: false,
error: error
})
})
}
render() {
return (
<div>
<input placeholder="Search..." onChange={this.handleChange} type="search" />
<SearchResults data={this.state.data} loading={this.state.loading} error={this.state.error} />
</div>
)
}
}
ReactDOM.render(<SearchExample />, 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"></div>

Related

How to handle routes in React which exist byt may not have data if user enters it straight into URL

There is React router route in Main.js as the error indicates line:95:
"/article/:article_id"
const Main = props => {
const { authUser, errors, removeError, currentUser } = props;
return(
<div className="">
<Switch>
<Route exact path="/article/:article_id" render={props => {
return (
<ArticleDetails
currentUser={currentUser}
{...props}
/>
)
}} />
// lots of other routes
{/* Not found component */}
<Route component={PageNotFound} />
</Switch>
</div>
);
};
// and some redux state
function mapStateToProps(state) {
return {
currentUser: state.currentUser,
errors: state.errors
};
};
export default withRouter(connect(mapStateToProps, { authUser, removeError })(Main));
So, for example, there can be "/article/5" but there might not be "/article/6", article with an id of 6.
I have a default component that loads when the route does not exist, but in this case, the route does exist, only dynamic data may not.
If article request returns no article when user types "/article/6/" in URL, he does get redirected back to "/articles" with "this.props.history.push('/articles');".
ArticleDetails component fetches all data of an article, while parent component ArticlesList, has only few details with the link to ArticleDetails page, it does not pass article as prop.
class ArticleDetails extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
article: null,
lists: [],
showBookmark: false,
showPdf: false,
showDelete: false
};
// binded methods...
}
componentDidMount(){
this._isMounted = true;
if (this._isMounted) {
let id = this.props.match.params.article_id;
axios.get('/api/article/' + id)
.then(res => {
this.setState({
article: res.data.article
});
return res.data.article;
})
.then(article => {
if(!article)
{
this.props.history.push('/articles');
}
else if(this.props.currentUser.isAuthenticated)
{
return apiCall("post", `/api/article/${id}/checklike`)
.then(res => {
let newState = Object.assign({}, this.state);
newState.article.isLiked = res.isLiked;
this.setState(newState);
});
}
})
.then( res => {
let newState = Object.assign({}, this.state);
if(this.props.currentUser.isAuthenticated)
{
newState.article.comments.map(comment => {
let temp = comment.likes.find(like => like.user_id ===
this.props.currentUser.user.id)
if(temp) { comment.isLiked = true}
else { comment.isLiked = false}
})
this.setState(newState);
}
})
.catch(err => {
return <Redirect to='/articles' />
});
this.getUserArticleLists();
}
};
// and also a small state
const mapStateToProps = state => ({})
export default connect(mapStateToProps)(ArticleDetails);
but the user also gets this warning of a memory leak which sounds not cool and may signal that I'm doing something incorrectly. I added _isMounted property but I'm either doing it wrong or it does not help.
componentWillUnmount() {
this._isMounted = false;
}
"index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in ArticleDetails (created by ConnectFunction)
in ConnectFunction (at Main.js:95)"
Question is how to handle situations when component is loaded and only then it is known that the data does not exist or maybe the user is not allowed to access it and redirect the user properly? Am I missing some sort of middleware on the React that I should be using for such cases? or How should be URL access even handled in such cases?
P.S I'm new to React, I have a little redux store, but not getting very deep into it at the moment, trying to solve just by using react, as I'm currently doing the project which has almost no time left. But for future improvement, I accept all answers, feedback, I might not know basic practises so I appreciate every comment.

How to update state of component in componentDidUpdate() without being stuck in an infinite re render?

I have a component with a componentDidMount() method that calls a method called getData() which gets the initial data and sets the initial state of the component.
class LogsSettings extends React.Component {
constructor(props) {
super(props);
this.settingsUrls = [
"/ui/settings/logging"
];
this.state = {
configSettings: {},
formSchema: formSchema
};
this.configSettings = {};
this.selected = "general";
}
getData = (url, selectedSetting) => {
fetch(url)
.then((response) => {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
response.json().then((response) => {
//pass formschema here
console.log(selectedSetting);
let newFormSchema = this.setNonDefaultValues(response.data, formSchema.subsections);
Object.assign(this.configSettings, response.data);
this.setState({
configSettings : this.configSettings,
formSchema: newFormSchema
});
});
}
)
.catch((err) => {
console.log('Fetch Error :-S', err);
});
};
componentDidMount() {
this.settingsUrls.map((settingUrl) => {
this.getData(settingUrl, this.selected)
})
}
componentDidUpdate() {
this.settingsUrls.map((settingUrl) => {
this.getData(settingUrl, this.props.selectedSetting)
})
}
render() {
return (
<div className="card-wrapper">
<h2>{formSchema.label.toUpperCase()}</h2>
{
formSchema.subsections.map((subSection) => {
return (
<>
<h3>{subSection['description']}</h3>
<div style={{marginBottom: '10px'}}></div>
{
subSection['input_fields'].map((inputField) => {
return buildForm(inputField, this.handleChange)
})
}
<hr></hr>
</>
)
})
}
<button className="button button-primary">Save Changes</button>
</div>
)
}
}
The selectedSetting parameter that gets passed to the getData() method in this component will change however and when this changes, I need to change the state of the component and get new data specific to the changed selectedSetting parameter.
The new selectedSetting is passed into the component as a prop. The problem is that I can't pass the new selectedSetting parameter to my getData method to update the state of the component as it gets caught in an infinite loop.
How do I go about passing the new selectedSetting to the getData() method without getting caught in an infinite loop? Is this even possible? If not, what is the best approach I should take?
note the selectedSetting parameter isn't used in the getData() function yet but will be and it will be used to get data from an API call and a new form schema which will then lead to the ConfigSettings and formSchema states being changed
If you look closely on the lifecycle of your component, after mount, you'll fetch then update the component. This will trigger the componentDidUpdate lifecycle method which will do the same thing, causing the infinite loop. You need to have a flag that checks whether this.props.selected changed. If it didn't, don't fetch the data else fetch as normal. In the update method, you have access to the previous props. (You may also do this in componentShouldUpdate method, but it'll be just outright risky)
componentDidUpdate(prevProps) {
if( prevProps.selectedSetting !== this.props.selectedSetting ){
this.settingsUrls.map((settingUrl) => {
this.getData(settingUrl, this.props.selectedSetting)
})
}
}
also just a heads up, I noticed that your didMount method, uses a default of "general" as the selected setting, since you want to be using this.props.selectedSetting might be better if it was the one being used instead and just set default props to "general".

getDerivedStateFromProps, change of state under the influence of changing props

I click Item -> I get data from url:https: // app / api / v1 / asset / $ {id}. The data is saved in loadItemId. I am moving loadItemId from the component Items to the component Details, then to the component AnotherItem.
Each time I click Item the props loadItemId changes in the getDerivedStateFromProps method. Problem: I'll click Element D -> I see in console.log 'true', then I'll click Element E --> It display in console.log true andfalse simultaneously, and it should display only false.
Trying to create a ternary operator {this.state.itemX ['completed'] ? this.start () : ''}. If {this.state.itemX ['completed'] call the function this.start ()
Code here: stackblitz
Picture: https://imgur.com/a/OBxMKCd
Items
class Items extends Component {
constructor (props) {
super(props);
this.state = {
itemId: null,
loadItemId: ''
}
}
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://app/api/v1/asset/${id}`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
this.setState({
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render () {
return (
<div >
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
>
<Details
loadItemId={this.state.loadTime}
/>
</div>
)
}
Item
class Item extends Component {
render () {
return (
<div onClick={() => this.props.selectItem(item.id}>
</div>
)
}
}
Details
class Details extends Component {
constructor() {
super();
}
render () {
return (
<div>
<AnotherItem
loadItemId = {this.props.loadItemId}
/>
</div>
)
}
}
AnotherItem
class AnotherItem extends Component {
constructor() {
super();
this.state = {
itemX: ''
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.loadItemId !== prevState.loadItemId) {
return { itemX: nextProps.loadItemId }
}
render () {
console.log(this.state.itemX ? this.state.itemX['completed'] : '');
{/*if this.state.loadX['completed'] === true, call function this.start()*/ }
return (
<button /*{this.state.loadX['completed'] ? this.start() : ''}*/ onClick={this.start}>
Start
</button>
);
}
}
here:
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
you call setState(), then 'Item' and 'Details' and 'AnotherItem' call their render method. so you see log for previous 'loadItemId'.
when 'load' method work done. here:
this.setState({
loadItemId: response.data
});
you setState() again, then 'Item' and 'Details' and 'AnotherItem' call their render method again. in this time you see log for new 'loadItemId'.
solution
setState both state in one place. after load method done, instead of:
this.setState({
loadItemId: response.data
});
write:
this.setState({
itemId: id,
loadItemId: response.data
});
and remove:
this.setState({
itemId: id
})
from 'selectItem' method.
Need some clarification, but think I can still address this at high level. As suggested in comment above, with the information presented, it does not seem that your component AnotherItem actually needs to maintain state to determine the correct time at which to invoke start() method (although it may need to be stateful for other reasons, as noted below).
It appears the functionality you are trying to achieve (invoke start method at particular time) can be completed solely with a comparison of old/new props by the componentDidUpdate lifecycle method. As provided by the React docs, getDerivedStateFromProps is actually reserved for a few 'rare' cases, none of which I believe are present here. Rather, it seems that you want to call a certain method, perhaps perform some calculation, when new props are received and meet a certain condition (e.g., not equal to old props). That can be achieved by hooking into componentDidUpdate.
class AnotherItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
start = () => { do something, perform a calculation }
// Invoked when new props are passed
componentDidUpdate(prevProps) {
// Test condition to determine whether to call start() method based on new props,
// (can add other conditionals limit number of calls to start, e.g.,
// compare other properties of loadItemId from prevProps and this.props) .
if (this.props.loadItemId && this.props.loadItemId.completed === true) {
//Possibly store result from start() in state if needed
const result = this.start();
}
}
}
render () {
// Render UI, maybe based on updated state/result of start method if
// needed
);
}
}
You are encountering this behaviour because you are changing state of Items component on each click with
this.setState({
itemId: id
})
When changing its state, Items component rerenders causing AnotherItem to rerender (because that is child component) with it's previous state which has completed as true (since you've clicked element D before). Then async request completes and another rerender is caused with
this.setState({
loadItemId: response.data
});
which initiates another AnotherItem rerender and expected result which is false.
Try removing state change in selectItem and you'll get desired result.
I'd suggest you read this article and try to structure your code differently.
EDIT
You can easily fix this with adding loader to your component:
selectItem = (id) => {
this.setState({
itemId: id,
loading: true
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://jsonplaceholder.typicode.com/todos/${id}`,
method: "GET"
})
.then(response => {
this.setState({
loading: false,
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render() {
return (
<div >
<ul>
{this.state.items.map((item, index) =>
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
/>
)
}
</ul>
{this.state.loading ? <span>Loading...</span> : <Details
itemId={this.state.itemId}
loadItemId={this.state.loadItemId}
/>}
</div>
)
}
This way, you'll rerender your Details component only when you have data fetched and no unnecessary rerenders will occur.

Fetch one JSON object and use it reactjs

I'm very new to JS and ReactJS and I try to fetch an endpoint which gives me a JSON Object like this :
{"IDPRODUCT":4317892,"DESCRIPTION":"Some product of the store"}
I get this JSON Object by this endpoint :
http://localhost:3000/product/4317892
But I dont how to use it in my react application, I want to use those datas to display them on the page
My current code looks like this but it's not working and I'm sure not good too :
import React, {Component} from 'react';
class Products extends Component {
constructor(props){
super(props);
this.state = {
posts: {}
};
};
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
const { postItems } = this.state;
return (
<div>
{postItems}
</div>
);
}
}
export default Products;
In the console.log(this.state) there is the data, but I'm so confused right now, dont know what to do
Since I'm here, I have one more question, I want to have an input in my App.js where the user will be able to type the product's id and get one, how can I manage to do that ? Passing the data from App.js to Products.js which is going to get the data and display them
Thank you all in advance
Your state doesn't have a postItems property which is considered undefined and react therefore would not render. In your situation there is no need to define a new const and use the state directly.
Also, when you setState(), you need to tell it which state property it should set the value to.
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
...this.state, // Not required but just a heads up on using mutation
posts: res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
return (
<div>
<p><strong>Id: {this.state.posts.IDPRODUCT}</strong></p>
<p>Description: {this.state.posts.DESCRIPTION}</p>
</div>
);
}
I have got 3 names for the same thing in your js: posts, postItems and res.
React can not determine for you that posts = postItems = res.
So make changes like this:
-
this.state = {
postItems: {}
};
-
this.setState({
postItems: res
});
-
return (
<div>
{JSON.stringify(postItems)}
<div>
<span>{postItems.IDPRODUCT}</span>
<span>{postItems.DESCRIPTION}</span>
</div>
</div>
);
{postItems["IDPRODUCT"]}
Will display the first value. You can do the same for the other value. Alternatively, you can put
{JSON.stringify(postItems)}
With respect to taking input in the App to use in this component, you can pass that input down through the props and access it in this component via this.props.myInput. In your app it'll look like this:
<Products myInput={someInput} />

How do I create an array after Axios response and Display it in React js

I am having trouble creating an array of titles from an Axios response. The method getTitles(props) receives data from the Axios response. How do I create an array of titles dynamically?
The functions I have tried in Javascript are for loops and EC6 mapping, nothing seems to work. Being new to react I could be missing something but I am not sure what it is.
React code
export default class Featured extends React.Component {
constructor() {
super();
this.state = {
data: null,
}
}
/**
* Received request from server
*/
componentDidMount(){
ApiCalls.articleData()
.then(function(data){
this.setState(function(){
return {
data: data
}
})
}.bind(this));
}
getTitles(props){
//-- What code do I place here?
console.log(props.data)
return ['test title', 'test title 2'];
}
/**
* Render request
*/
render() {
let dataResponse = JSON.stringify(this.state.data, null, 2);
const Articles = this.getTitles(this.state).map((title, i) => <Article key={i} title={title}/> );
return (
<div class="row">{Articles}
<pre>{dataResponse}</pre>
</div>
);
}
}
Axios Code
var ApiCalls = {
articleData: function(id){
return axios.all([getArticles(id)])
.then(function(arr){
return arr[0].data.data;
})
.catch(function (error) {
console.log(error);
})
},
React setState behaves asynchronously . Your articles get rendered before the ajax was called and was not re rendered due to asynchrony in setState.
This is what doc(https://reactjs.org/docs/react-component.html#setstate) says
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.
You can render the article after successful ajax call like below
componentDidMount(){
ApiCalls.articleData()
.then(function(data){
render(<Article data={data}/>, document.getElementById('...'));
}.bind(this));
}
Because of the post above, I was able to fix the issue by the code example below To see a working example goto the git repo
ApiCalls.articleData()
.then(function(data){
const newData = data.map(c => {
return c.attributes.title;
})
const addElement = newData.map((title, i) => <ContentTiles key={i} title={title}/> );
const newState = Object.assign({}, this.state, {
newData: addElement
});
this.setState(newState);
}.bind(this));

Categories

Resources