This is a question about error propagation from react/flux actions into the store and then back to the component, the error is not validation, rather its an api call failure when trying to update the state of the store.
Here is a pretty simple example:
Component:
CityList.jsx
list of cities is held in a field thats bound to store data field, - This loops over these and writes list of City.jsx:
City.jsx
Populates the city data to the user, including a toggle button to say whether they have visited or not, e.g:
name: {this.state.city.name}
country: {this.state.city.country}
visited: {this.state.city.visited}
Toggling the 'visited' button fires a 'toggleVisited' action with that city object:
Actions:
CityActions.js
toggleVisited(city)
Takes a city object, makes a request to the api to update the 'visited' value and pushes result to store
Store:
CityStore.js
onToggleVisited(city)
Receives city object from the action and updates the relevant item in its cities array with new value
Now what happens when the api called in the CityActions returns an error? How do I map that to the correct component to show the appropriate error messages (red border, error text etc) to indicate that the update failed?
My initial thought was to set an error field on the city object after the api call in the action, then set it back in the store looking something like this:
{
name: '',
country: '',
visited: '',
error: true
}
(error could be an object) Then the component would re-render the city and
so then the component could read the error field and show the relevant error messages when its rendered.
So:
Is there a better way to handle these errors?
If I wanted to show a global error at the top of the page, theres no
way to know without looping through the data to check for at least
one instance of {error: true} and then render the error - this is not
ideal.
Is there any way to map errors to components?
To render a global error on top of the page, you could store a global error message in the CityStore.
When the response from the API contains error, you update the global error object in CityStore.
When the view then gets the state from CityStore, you don't need to loop over all cities to check for an error.
You could still keep the error: true in the city object itself, e.g. if you also want to highlight some city in the view that has the actual error.
Related
I'm using Normalizing State Shape for my Redux Store and it looks like this:
entities: {
users: {
list: [], // <-- list of all my users
loading: false,
lastFetch: null,
},
}
I got stuck on what should I do if someone opens up a website directly on the user's detail page. For example: {WEBSITE_URL}/users/1. The Redux Store is empty and I need to request only one entity. Should I:
fetch the whole list, put it in the Store and select one requested entity?
fetch only user #1, put it in the Store user list (entities.users.list), set lastFetch to null (this is because if someone will redirect to list next, he will fetch the new list again. Clearly the pervious list didn't have all users), and display user #1 from the list.
fetch only user #1, put it in the Store in separate place. For example in selected field of users:
entities: {
users: {
list: [],
loading: false,
lastFetch: null,
selected: null // <--- HERE
},
}
What solution do you think is the best? Do I need selected field at all? All tutorials and courses don't mention this scenario, only scenario how to fetch the list.
I'm having the same dilemma.
My approach is always — 3. I'm creating selected/single state to load data and one additional action (e.g. clearSelectedUser, clearSelectedPost) to clear data from the store on component unmount.
I'm also using Redux Saga to fetch data (do async operations) and this works good as combo. I really like the idea of having neat components without async calls in it.
However, I also found it acceptable to use component state (with useState hook) and do data fetching from a component directly (without Redux Saga or the store) in this particular case (entity single page/screen).
Option 1. will not work if you get paginated data from your API. You'll just complicate things.
Option 2. I agree with you on that one.
help... I'm making a project that detects food ingredients using Clarifai API Food model. When the API scans the image, it returns the response via console.log. How do you get THAT output from the API (console.log) and print it in the webpage. sorry, newbie aspiring web dev here.Image of website,console.log, and JS code
Instead of console logging the response, you should define a key value pair in the state of the component:
// the initial value of each key in state should match what data you are storing
this.state = {
input: '',
foodUrl: '',
prediction: {},
};
After the file has received the API response, instead of console logging the response you would write:
this.setState({ prediction: response.outputs[0].data.concepts[0] });
Next you would pass it in to the component you are using to display the response within the render portion of App.js:
<ComponentToRenderAPIResponse prediction={this.state.prediction} />
Within that child component, you would then write something like this within the render portion:
render() {
return (
<div>`Detected Food: ${prediction.name}`</div>
<div>`Prediction Value: ${prediction.value}`</div>
)
}
Instead of storing the entire object displayed in your console, you could just access the name and value key value pairs and store them as separate fields within your state object. Then pass both the name and value as different props to the child component. Also you might want to consider different html tags than the divs I used above as well as adding CSS styling to both tags.
I’m working on a solo project using React and I’ve been stuck on something for the past 2 days…I'm starting and I'm very beginner so it's maybe something very basic, but I'm struggling...
To try to be concise and clear:
I have a searchBar component, that searches through a local database, and returns objects associated with the search keyword. Nothing complicated so far.
Each rendered object has a button that triggers a function onClick. The said function is defined in my App component and is as follow:
changeState(term){
let idToRender=[];
this.state.dealersDb.map(dealer=>{
if(term===dealer.id){
idToRender=[dealer];
}});
let recoToFind=idToRender[0].reco;
recoToFind.map(item=>{
Discogs.search(item).then(response=>{idToRender[0].recoInfo.push(response)})
})
this.setState({
objectToRender: idToRender
});
to explain the above code, what it does is that first, it identifies which object’s button has been clicked on, and send said object to a variable called idToRender. Then, it takes the reco state of that object, and store it to another variable called recoToFind. Then it calls the map() method on recoToFind, make an API request (the discogs() method) for each element of the recoToFind array and push() the results into the recoInfo state of idToRender. So by the end of the function, idToRender is supposed to look like this:
[{
…
…
recoInfo: [{1stAPI call result},{2ndAPI call result}…]
}],
The array contains 1 object having all the states of the object that was originally clicked on, plus a state recoInfo equal to an array made of the results of the several API calls.
Finally, it updates the component’s state objectToRender to idToRender.
And here my problem is, onClick, I do get all the states values of the clicked on object that get rendered on screen (as expected with how I coded the nested components), BUT, the values of the recoInfo are not displayed as expected (The component who’s supposed to render those values is nested in the component rendering the clicked on object other states values). However, they get displayed properly after a SECOND click on the button. So it seems my problem boils down to an state update timing trouble, but I’m puzzled, because this function is calling setState once and I know for a fact that the state is updated because when I click on the button, the clicked on Object details get displayed, but somehow only the recoInfo state seems to not be available yet, but only becomes available on a second click…
Would anyone have a way to solve this issue? :(
It somehow feels like my salvation lies in async/await, but I’m not sure I understand them correctly…
thanks very much in advance for any help!
Is this someting you want to do?
changeState(term) {
let idToRender=[];
this.state.dealersDb.map(dealer=>{
if(term===dealer.id){
idToRender=[dealer];
}});
let recoToFind=idToRender[0].reco;
recoToFind.map(item=>{
Discogs.search(item).then(response=>{
idToRender[0].recoInfo.push(response)
this.setState({
objectToRender: idToRender
});
})
})
}
you can call setState once async call is done and result received.
I think this is more of a conceptual/architecture question but I will include code samples to help explain the question. I have a normalized redux state where the entities state slice looks like this:
entities: {
projects: {
[id]: {...},
[id]: {...},
...
},
assignments: {
[id]: {...},
[id]: {...},
...
}
}
And an individual assignment looks like this:
{
id: 1,
name: 'assignment 1',
status: 'active',
deadline: '01-01-2020'
}
I fetch this data from a backend DB. I am trying to figure out the proper way to handle the process of updating this data, keeping the UI responsive, and keeping my redux state in sync with the backend.
A specific example is a react component for displaying an individual assignment that has a picker/radio buttons to change the status between:
const statusOptions = {
'active',
'pending',
'complete'
}
The options I can see are:
1) Set the value of the picker as the props.assignment.status value, and in the onChange of the picker/selector dispatch an updateAssignment() action where a saga/thunk sends the POST request and immediately triggers a fetchAssignment() action which will send a GET request and update the redux state and in turn the component will re render.
The problem with this is the redux update takes too long so the UI appears laggy and the controlled input will revert to the old selection until the new props are passed in.
2) Set the local component state based on the redux state like this:
state = { status: this.props.assignment.status }
And then set the value of the picker based on the local state, which would provide near instant UI updates on a value change.
The problem I see here is I am pretty sure this is a react anti-pattern, and I would have to use getDerivedStateFromProps() or something similar to make sure the local state stays in sync with the redux state. Plus I really like the 'single source of truth' idea and I feel like this option would invalidate that.
3) set the value of the picker based on props.assignment.status and in the onChange handler of the picker clone the assignment object, update the status attribute, and then immediately send an updateAssignment() action that merges the locally created assignment object into the state.
After that send the POST request to the server and if it fails somehow revert the redux state to the prior state, basically removing the locally added assignment object. This seems kind of hacky though maybe?
Is there any agreed upon best practices for updating redux data while maintaining a single source of truth, snappy UI, and clean code?
The first part of (2) seems to me the right way.
In ComponentDidMount (or, even better, in App.js, when the app is starting) you fetch the data from the database to the redux state, and set the local state from it.
Then you maintain the data locally, and dispatch the proper action that will update the redux state and the database.
In shouldComponentUpdate you need to prevent updates that happen following this redux update: you will check if the values of the props have changed.
In componentDidUpdate you will update the state if the props change.
The last thing to take care of is getting data updates following database changes that happen by other instances of the app running on other smartphones, or by other sources of data, if this may happen. In firebase, for example, you do that by listening to relevant app changes. I don't know if this is relevant here.
I've been facing a weird issue lately with my React App. I'm trying to parse a JSON object that contains arrays with data. The data is something like this:
{"Place":"San Francisco","Country":"USA", "Author":{"Name":"xyz", "Title":"View from the stars"}, "Year":"2018", "Places":[{"Price":"Free", "Address":"sfo"},{"Price":"$10","Address":"museum"}] }
The data contains multiple arrays like the Author example I've just shown. I have a function that fetches this data from a URL. I'm calling that function in componentDidMount. The function takes the data i.e responseJson and then stores it in an empty array that I've set called result using setState. In my state I have result as result:[]. My code for this would look something like this:
this.setState({result:responseJson})
Now, when I've been trying to access say Author Name from result I get an error. So something like this:
{this.state.result.Author.Name}
I'm doing this in a function that I'm using to display stuff. I'm calling this function in my return of my render function. I get an error stating :
TypeError:Cannot read property 'Name' of undefined. I get the same error if I try for anything that goes a level below inside. If I display {this.state.result.Place} or {this.state.result.Country} it's all good. But if I try,say {this.state.result.Author.Title} or {this.state.result.Places[0].Price} it gives me the same error.
Surprising thing is I've parsed this same object in a different component of mine and got no errors there. Could anyone please explain me why this is happening?
If I store the individual element while I setState in my fetch call function, I can display it. For example:
{result:responseJson,
AuthorName:responseJson.Author.Name
}
Then I'm able to go ahead and use it as {this.state.AuthorName}.
Please help me find a solution to this problem. Thanks in advance!
It could be that your state object is empty on the first render, and only updated with the data from the API after the request has completed (i.e. after the first render). The Name and Place properties don't throw an error, as they probably resolve to undefined.
Try putting an if block in your render method to check if the results have been loaded, and display a loading indicator if they haven't.
I'm guessing your initial state is something like this:
{ results: {} }
It's difficult to say without seeing more code.
[EDIT]: adding notes from chat
Data isn't available on first render. The sequence of events rendering this component looks something like this:
Instantiate component, the initial state is set to { results: [] }
Component is mounted, API call is triggered (note, this asynchronous, and doesn't return data yet)
Render method is called for the 1st time. This happens BEFORE the data is returned from the API request, so the state object is still {results: [] }. Any attempts to get authors at this point will throw an error as results.Authors is undefined
API request returns data, setState call updates state to { results: { name: 'test', author: [...] } }. This will trigger a re-render of the component
Render method is called for the 2nd time. Only at this point do you have data in the state object.
If this state evolves, means it is changed at componentDidMount, or after a fetch or whatever, chances are that your state is first empty, then it fills with your data.
So the reason you are getting this error, is simply that react tries to get this.state.result.Author.Name before this.state.result.Author even exists.
To get it, first test this.state.result.Author, and if indeed there's something there, then get Author.Name like this.
render(){
return(
<div>
{this.state.result.Author ? this.state.result.Author.Name : 'not ready yet'}
</div>
);
}
[EDIT] I'll answer the comment here:
It's just because they are at a higher level in the object.
this.state.result will always return something, even false if there is no result key in your state (no result key in your constructor for instance when the component mounts).
this.state.result.Country will show the same error if result is not a key of your state in your constructor. However, if result is defined in your constructor, then it will be false at first, then become the data when the new state populates.
this.state.result.Author.Name is again one level deeper...
So to avoid it, you would have to define your whole "schema" in the constructor (bad practice in my opinion). This below would throw no error when getting this.state.result.Author.Name if I'm not mistaken. It would first return false, then the value when available.
constructor(props){
super(props);
this.state={
result: {
Author: {}
}
}
}