Issue with update timing for a state in React Component (2 clicks needed instead of 1) - javascript

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.

Related

React-Redux Slider undefined value when passing onwards the values

This is my first post so I'm sorry if I'm doing it wrong. So, I've started creating a webpage for my portfolio with React and Redux but I hit a point where I can't find the solution or the flaw in my logic. The page is that the user can select a skill and then the pointer to the value he knows. I can save the skills to localStorage but I can't manage to pass the skill value from the slider to save it. If anyone can help with any tip or info of where I m doing wrong please help.
This are some of the components:
slider component
skillsComp whee the slider is passed
listSkills Comp where skillsComp and Slider is passed
redux actions
redux reducer
and this is the console output: console
Problem: Not Dispatching Action
The problems that I'm seeing are in image #1 of the SkillSliderBar component. There might be other issue too, I haven't looked further then this.
First of all, your destructured props are missing the addSkillValue function from mapDispatchToProps.
You defined a function handleChange to call the addSkillValue function, but you never call handleChange. Instead you are calling changeValue which updates the local component state.
You probably don't need a local state at all. You can use the skillValue from mapStateToProps as the value for your inputs. To set the value, call addSkillValue from mapDispatchToProps.
Make sure that the onChange function for your inputs is being called with the value first and not the event. If it is being called with the event then your handleChange would look like:
const handleChange = e => {
addSkillValue(e.target.value);
}
Suggestions: State Shape
It looks like you only have a skill value in your state for the current skill. Probably what you want is to store an array of skill objects with properties name and value. Or possibly a key-value object where the keys are the skill names and the values are the skill values. I'm not sure if you need the currentSkillName. You can update the value for any skill by dispatching an action that contains both the new value and the name of the skill that you are updating.

Mobx and React async function call is freezing UI while onClick call is not

So we have been trying to solve this for hours and cant figure out whats the issue. I am afraid I cant include any code due to the companies policy, but I can include a sample code with an explanation.
Basically we have a function where we select all the table rows from the controller (simplified). Example:
selectAllRows = action(() => {
this.data.forEach(r=> {
this.selectTableRow(r, tableInstance)
})
})
and selectTableRow is just a simple function which sets the value to an observable object:
selectTableRow = action((row, tableInstance) => {
tableInstance.multiSelectedRows[row.id] = row.id
})
So when we have lets say 100 rows and we select by calling a button and calling selectAllRows function via onClick method or if we call it directly in componentDidMount life cycle, the table rows are selected instantly, no problems - all good.
As soon as this function is being called from anywhere else lets say if we call it from the esc key listener, or we wrap it with the setTimeout funcion. As soon as that function gets executed, it hangs the UI for a second or 2 and then selects the rows as normal - render issue, problem unknown.
So the problem is that we cant understand why the same function is working fine when its being called directly and not when its wrapped within the async function.
Any tips or if someone recognises the problem and can help us out would be appreciated!
EDITED: Also to note, we removed all the action wrapping from the functions and did the same test, and it was the same problem. onClick call executed fast and the other one slow.
We managed to solve by updating all the libraries (we do that anyway every 2 weeks), but it seems like it was the mobx issue as after the mobx got updated it fixed this problem.

Vue component changing its own property in store

okay, it's a little hard to explain, but i'll try.
i have some components rendered in a loop from an array of cards in my vuex store
<card-base
v-for="(card, i) in cards"
:class="`dashboard-card ${card.hidden ? 'hidden' : ''}`">
note the class depending on card.hidden, which is false onload
now, every <card-base> has a button (hide), which is supposed to.. well, hide it.
the way i try to do that is:
a v-btn in the card-base component gets a #clickproperty, which calls a method.
The Problem / Question
now i want to set the hidden property of the clicked card to true.
of course
minimizeDashboardCard() {
console.log(this.hidden)
this.hidden = true
}
doesn't work, because this is not the actual object from vuex, which provides the reactive properties, but just the "element".
if i set this.hidden = true, nothing changes (except the console.log correctly showing "true" after the first click)
but how can i access the actual vuex object from this? how do i get the index which i want to edit in my array? is there a way to have the card component "say" something like:
"dispatch an action that changes ME in the actual vuex array"?
like dispatch('hideCard', this) and have it actually working?
TLDR
how can i find out the index of the clicked card in the array, or directly target it in any other way? is there a connection between a rendered element and the array in store which defines it?
i hope you understood my problem :D
thanks!

Parsing JSON objects array and displaying it in ReactJS

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: {}
}
}
}

How to ensure I am reading the most recent version of state?

I may be missing something. I know setState is asynchronous in React, but I still seem to have this question.
Imagine following is a handler when user clicks any of the buttons on my app
1. ButtonHandler()
2. {
3. if(!this.state.flag)
4. {
5. alert("flag is false");
6. }
7. this.setState({flag:true});
8.
9. }
Now imagine user very quickly clicks first one button then second.
Imagine the first time the handler got called this.setState({flag:true}) was executed, but when second time the handler got called, the change to the state from the previous call has not been reflected yet -- and this.state.flag returned false.
Can such situation occur (even theoretically)? What are the ways to ensure I am reading most up to date state?
I know setState(function(prevState, props){..}) gives you access to previous state but what if I want to only read state like on line 3 and not set it?
As you rightly noted, for setting state based on previous state you want to use the function overload.
I know setState(function(prevState, props){..}) gives you access to previous state
So your example would look like this:
handleClick() {
this.setState(prevState => {
return {
flag: !prevState.flag
};
});
}
what if I want to only read state like on line 3 and not set it?
Let's get back to thinking why you want to do this.
If you want to perform a side effect (e.g. log to console or start an AJAX request) then the right place to do it is the componentDidUpdate lifecycle method. And it also gives you access to the previous state:
componentDidUpdate(prevState) {
if (!prevState.flag && this.state.flag) {
alert('flag has changed from false to true!');
}
if (prevState.flag && !this.state.flag) {
alert('flag has changed from true to false!');
}
}
This is the intended way to use React state. You let React manage the state and don't worry about when it gets set. If you want to set state based on previous state, pass a function to setState. If you want to perform side effects based on state changes, compare previous and current state in componentDidUpdate.
Of course, as a last resort, you can keep an instance variable independent of the state.
React's philosophy
The state and props should indicate things the components need for rendering. React's render being called whenever the state and props change.
Side Effects
In your case, you're causing a side effect based on user interaction which requires specific timing. In my opinion, once you step out of rendering - you probably want to reconsider state and props and stick to a regular instance property which is synchronous anyway.
Solving the real issue - Outside of React
Just change this.state.flag to this.flag everywhere, and update it with assignment rather than with setState. That way you
If you still have to use .state
You can get around this, uglily. I wrote code for this, but I'd rather not publish it here so people don't use it :)
First promisify.
Then use a utility for only caring about the last promise resolving in a function call. Here is an example library but the actual code is ~10LoC and simple anyway.
Now, a promisified setState with last called on it gives you the guarantee you're looking for.
Here is how using such code would look like:
explicitlyNotShown({x: 5}).then(() => {
// we are guaranteed that this call and any other setState calls are done here.
});
(Note: with MobX this isn't an issue since state updates are sync).

Categories

Resources