I am working on a react native app where i'm using native base for design purpose. I am using date picker from native base. Also i'm showing different results of different dates which i'm fetching from api. Now, some dates have data and some don't. When I pick a date that doesn't have data it shows blank as expected. But after showing blank I can't get data from other dates which have data. it kind of gets stuck. But if I select the dates that have data,it works fine. Here's the code I have now:
async setDate(newDate) {
let day = newDate.getDate()
console.log('date', day)
let month = newDate.getMonth() + 1
let year = newDate.getFullYear()
day = String(day).length > 1 ? day : '0' + day
month = String(month).length > 1 ? month : '0' + month
let fullDate = 'https://myapi'+year+month+day
await fetch(fullDate, {
method: 'GET',
})
.then((response) => response.json())
.then((response) => {
console.log('new', response)
if(response === null){
this.setState({
tableData1: [],
tableData2: [],
tableData3: [],
})
} else {
const first = response.First
this.setState({ tableData1: first })
const second = response.Special
this.setState({ tableData2: second })
const third = response.Consolidation
this.setState({ tableData3: third })
}
})
}
What can I do to get data as usual after selecting a date which doesn't have any value?
I'm quite surprised you're not receiving any errors here in console or on screen - are you testing on a device or simulator?
if(response === null) is your problem, undefined === null evaluates to false, so you're pushing into your else statement and trying to access response.First etc which is undefined and will cause an error to throw - this is why things get 'stuck'.
You can simplify your check and, as mentioned in my comment, the if else could be cleaned up quite a bit as you don't need to assign data from the response object to variables because you're not re-using them - and your multiple calls to setState will cause three re-renders of the component.
Some examples of how concise this block could be. I'll explain what I'm doing as I'm unaware of your experience and other readers may be less experienced than yourself:
if(!response)
this.setState({tableData1: [], tableData2: [], tableData3: []})
else
this.setState({
tableData1: response.First,
tableData2: response.Special,
tableData3: response.Consolidation
})
A simple if(!response) check to fix the error, setState being merged into one call and values being pulled directly from the response object. You'll notice i've omitted the curly braces from both the if and else blocks as they are both single statement blocks.
Or you could take it one step further:
.then((response) => {
this.setState({
tableData1: response.First ? response.First : [],
tableData2: response.Special ? response.Special : [],
tableData3: response.Consolidation ? response.Consolidation : []
})
}
Here we combine into a single setState call, using a ternary operator to assign each piece of state to data from the response object, or to an empty array. You'll notice each condition is based on response.key instead of just response - this is safer and less likely to cause errors (unless you're absolutely certain there can be no situation in which response contains some, but not all, of those keys)
Related
I'm executing this mutation in my NewBook component:
const [addBook] = useMutation(ADD_BOOK, {
update: (cache, response) => {
cache.updateQuery({ query: ALL_BOOKS }, ({ allBooks }) => {
return { allBooks: allBooks.concat(response.data.addBook) };
});
},
refetchQueries: [{ query: ALL_AUTHORS }, { query: ALL_GENRES }],
options: {
awaitRefetchQueries: true,}});
Instead of having to refetch those two queries, I'd like to update them like ALL_BOOKS - but could not find any example in the docs. Does anyone know a way to accomplish that?
Thank you.
What you need to do is make multiple cache updates based on response data.
Once you add your new book to the query, the next step is to fetch all authors.
cache.updateQuery({ query: ALL_BOOKS }, ({ allBooks }) => {
return { allBooks: allBooks.concat(response.data.addBook) };
});
//Get all authors
const existingAuthors = cache.readQuery({
query: ALL_AUTHORS,
//variables: {}
});
//If we never called authors, do nothing, as the next fetch will fetch updated authors. This might be a problem in the future is some cases, depending on how you fetch data. If it is a problem, just rework this to add newAuthor to the array, like allAuthors: [newAuthor]
if(!existingAuthors.?length) {
return null
}
The next thing is that we need to compare the new book's author with existing authors to see if a new author was added.
//continued
const hasAuthor = existingAuthors.find(author => author.id === response.data.createBook.id)
//Double check response.data.createBook.id. I don't know what is returned from response
//If author already exists, do nothing
if(hasAuthor) {
return null
}
//Get a new author. Make sure that this is the same as the one you fetch with ALL_AUTHORS.
const newAuthor = {
...response.data.createBook.author //Check this
}
cache.writeQuery({
query: ALL_AUTHORS,
//variables: {}
data: {
allAuthors: [newAuthor, ...existingAuthors.allAuthors]
},
});
Then continue the same with ALL_GENRES
Note:
If you called ALL_GENERES or ALL_BOOKS with variables, you MUST put the SAME variables in the write query and read query. Otherwise Apollo wont know what to update
Double check if you are comparing numbers or strings for authors and genres
Double check all of the variables I added, they might be named different at your end.
Use console.log to check incoming variables
You can probably make this in less lines. There are multiple ways to update cache
If it doesn't work, console.log cache after the update and see what exactly did apollo do with the update (It could be missing data, or wrong variables.)
Add more checks to ensure some cases like: response.data returned null, authors already fetched but there are none, etc...
I am doing an axios.get call on an API, in React. When I run it the first time, I get the results that I expect. When I run it the next time and search for the same thing, I get more results than I had originally. Then if I search again, for the same thing, I get even more results that I had the second time. It keeps adding new results to the old ones without starting a brand new search. If I search for a different keyword, then I get results with both the new keyword results and the old ones that I did before.
There is probably an easy fix to this, but how do I get it to discard the old results and create new ones? I have tried creating CancelTokens, but haven't been successful.
I am currently putting the results into a new array and I set that array equal to nothing when I first render the component, so I don't understand how this problem is happening in the first place.
Thanks for any help you can offer!
async componentDidMount() {
//searches the api for the hashtag that the user entered
newArray.length=0;
newArray=[];
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
//tweetSpots is set to null in the initial component state set up
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
Okay, I figured out this problem is actually on the server side in Express where the data isn't being reset. So, I'll close this one and open a question about Express.
Yesterday I had a behaviour that I don't understand by using Immer.js and setState together. I was using a setState (in a bad way, by the way) when fetching my data and this fetch was called at each endReached of my SectionList.
This setState looked like this:
this.setState((prev) => {
let sections = prev.sections
/* Extract alive topics from "topics"(array retrieved from fetch)*/
let topicsSection1 = topics.filter((card) => !card.states.is_killed)
/* Extract killed topics from "topics"(array retrieved from fetch)*/
let topicsSection2 = topics.filter((card) => card.states.is_killed)
if (sections[0] && sections[0].data)
sections[0].data = positionExtracted > 1 ? sections[0].data.concat(...topicsSection1) : topicsSection1
if (sections[1] && sections[0].data)
sections[1].data = positionExtracted > 1 ? sections[1].data.concat(...topicsSection2) : topicsSection2
return {
sections: sections,
position: response.position,
lastPage: response.laftPage
}
})
and everything worked just fine.
However, I have a function that is called when you press on the topic, and it changes the "opened" value of the topic in the data array to indicate to the list that "this topic" is open.
This function calls the "produce" function of Immer.js
And this function looks like this:
_onPressTopic = (id_card) => {
this.setState(produce((draft) => {
if (draft.sections[0] && draft.sections[0].data)
draft.sections[0].data = draft.sections[0].data.map((item) => {
if (item.id === id_card)
item.opened = !item.opened
return item
})
if (draft.sections[1] && draft.sections[1].data)
draft.sections[1].data = draft.sections[1].data.map((item) => {
if (item.id === id_card)
item.opened = !item.opened
return item
})
}))
}
MY PROBLEM IS:
If I open a topic and then my list data goes through this function, then when an endReached is called again, either I get an error "This object is not expensive", or my list data is not modified at all. And if instead of my first setState, I use a produce from Immer, everything works again.
What I don't understand is: Why does everything work perfectly if I only use Immer.js or just SetState, but as soon as I try to use both together, they don't seem to get along?
Thank you for your answers,
I hope I made it clear !
Viktor
I am working on a core piece of my form functionality that finds the distance between two ZIP codes. It does find the distance accurately, but unfortunately, this Google Distance Matrix API is also searching for existing area codes, which can result in random and unnecessary mileage returns.
Currently, I can find the mileage between 64154 and 816 (19.1), but I would like to completely omit the return of any AREA CODE data.
This is the function that returns said data:
handleChange = (e) => {
const name = e.target.name
const value = e.target.value
this.setState({
[name]: value
//call back function to make sure we get the last character from the user input
}, () => {
this.getMiles(this.state.origin, this.state.destination)
})
}
getMiles = (origin, destination) => {
const {google} = this.props
let service = new google.maps.DistanceMatrixService()
console.log(origin)
console.log(destination)
service.getDistanceMatrix({
origins: [origin],
destinations: [destination],
travelMode: 'DRIVING',
unitSystem: google.maps.UnitSystem.IMPERIAL,
avoidHighways: false,
avoidTolls: false
}, (response, status) => {
if (status !== 'OK') {
alert('Error was: ' + status);
} else {
let originList = response.originAddresses;
let destinationList = response.destinationAddresses;
if (response.rows[0].elements[0].status === 'NOT_FOUND'){
return false
} else{
this.setState({mileage: response.rows[0].elements[0].distance.text.replace("mi", " ")})
}
}
});
}
I can't find anything in the documentation that would allow me to filter out certain address types. I have even tried to put different form limitations regarding character length, which works for the most part, but the user can still put three digits in and "space" all the way to the end of the input. This will, unfortunately, bypass my conditional and return the data for the AREA CODE because the character length that I am looking for will be fulfilled "816 ".length === 5
Well, I just ended up using a regex pattern to check to see if the string contains 5 digits that are numbers.
What this does is, it doesn't prevent the API from returning AREA CODE searches but it does prevent my mileage from calculating by constantly setting it to zero:
let fiveNumberMatch = /^[0-9]{5}$/;
<h1>{!this.state.origin.match(fiveNumberMatch) || !this.state.destination.match(fiveNumberMatch) ? this.state.mileage = 0 : this.state.mileage}</h1>
Works for now!
I have the following object, from which I want to remove one comment.
msgComments = {
comments: [
{ comment:"2",
id:"0b363677-a291-4e5c-8269-b7d760394939",
postId:"e93863eb-aa62-452d-bf38-5514d72aff39" },
{ comment:"1",
id:"e88f009e-713d-4748-b8e8-69d79698f072",
postId:"e93863eb-aa62-452d-bf38-5514d72aff39" }
],
email:"test#email.com",
id:"e93863eb-aa62-452d-bf38-5514d72aff39",
post:"test",
title:"test"
}
The action creator hits the api delete function with the commentId:
// DELETE COMMENT FROM POST
export function deleteComment(commentId) {
return function(dispatch) {
axios.post(`${API_URL}/datacommentdelete`, {
commentId
},{
headers: { authorization: localStorage.getItem('token') }
})
.then(result => {
dispatch({
type: DELETE_COMMENT,
payload: commentId
});
})
}
}
My api deletes the comment and I send the comment id to my Reducer, this is working fine to this point, api works and comment is deleted. The problem is updating the state in the reducer. After much trial and error at the moment I am trying this.
case DELETE_COMMENT:
console.log('State In', state.msgComments);
const msgCommentsOne = state.msgComments;
const msgCommentsTwo = state.msgComments;
const deleteIndexComment = state.msgComments.data.comments
.findIndex(elem => elem.id === action.payload );
const newComments = [
...msgCommentsTwo.data.comments.slice(0, deleteIndexComment),
...msgCommentsTwo.data.comments.slice(deleteIndexComment + 1)
];
msgCommentsOne.data.comments = newComments;
console.log('State Out', msgCommentsOne);
return {...state, msgComments: msgCommentsOne};
Both state in AND state out return the same object, which has the appropriate comment deleted which I find puzzling.
Also the component is not updating (when I refresh the comment is gone as a new api call is made to return the updated post.
Everything else seems to work fine, the problem seems to be in the reducer.
I have read the other posts on immutability that were relevant and I am still unable to work out a solution. I have also researched and found the immutability.js library but before I learn how to use that I wanted to find a solution (perhaps the hard way, but I want to understand how this works!).
First working solution
case DELETE_COMMENT:
const deleteIndexComment = state.msgComments.data.comments
.findIndex(elem => elem.id === action.payload);
return {
...state, msgComments: {
data: {
email: state.msgComments.data.email,
post: state.msgComments.data.post,
title: state.msgComments.data.title,
id: state.msgComments.data.id,
comments: [
...state.msgComments.data.comments.slice(0, deleteIndexComment),
...state.msgComments.data.comments.slice(deleteIndexComment + 1)
]
}
}
};
Edit:
Second working solution
I have found a second far more terse solution, comments welcome:
case DELETE_COMMENT:
const deleteIndexComment = state.msgComments.data.comments
.findIndex(elem => elem.id === action.payload);
return {
...state, msgComments: {
data: {
...state.msgComments.data,
comments: [
...state.msgComments.data.comments.slice(0, deleteIndexComment),
...state.msgComments.data.comments.slice(deleteIndexComment + 1)
]
}
}
};
That code appears to be directly mutating the state object. You've created a new array that has the deleted item filtered out, but you're then directly assigning the new array to msgCommentsOne.data.comments. The data field is the same one that was already in the state, so you've directly modified it. To correctly update data immutably, you need to create a new comments array, a new data object containing the comments, a new msgComments object containing the data, and a new state object containing msgComments. All the way up the chain :)
The Redux FAQ does give a bit more information on this topic, at http://redux.js.org/docs/FAQ.html#react-not-rerendering.
I have a number of links to articles talking about managing plain Javascript data immutably, over at https://github.com/markerikson/react-redux-links/blob/master/immutable-data.md . Also, there's a variety of utility libraries that can help abstract the process of doing these nested updates immutably, which I have listed at https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md.