Atom Array Element Not Deleting Properly - reactjs

Within my page (index.tsx), I render a component which can have many children (grandparent.tsx). When I hit delete with no option selected for the input, it deletes fine from my global state (testAtom in atom.ts). However, if I select an input option, when hitting delete, it resets the input option i.e. it doesn't delete in the intended way. The intended behaviour is for it to remove the relevant index from the testAtom.
I believe it is something to do with the useEffect within the input.tsx component. I currently have this here because if the user changes the condition dropdown, I want the value to update accordingly. However, I've been stuck on this for a while and cannot figure it out, so any pointers would be helpful. I would recommend a look at the codesandbox link below, as it will give a clearer picture to the overall makeup of this problem.
CodeSandboxLink

Related

Performance issue with big React state object

I have a function updateRowValue, which updates a value in a row in a table based on user input. There are 11 columns whose values can be updated by the user, and the user can add/remove rows. I am currently testing on a table with 7 rows, and there is a severe lag between pressing a key on the keyboard and the change appearing on the screen.
Each time the user presses a key on their keyboard, the state is updated (I am using a controlled MUI TextField). I don't think the amount of inputs is the actual problem (though I might be wrong). I think the problem is that I have to traverse through the table_rows object every time a value should be updated.
My question is basically: Can I update the value each time the user presses a key on their keyboard and also make the app feel responsive to the key presses? If so, then how?
function updateRowValue(row_id, attribute, new_value){
const new_row = Object.assign(table_rows[row_id], {[attribute]: new_value});
const newState = Object.assign(table_rows, { [row_id]: {...table_rows[row_id], ...new_row} });
setTableRows({...table_rows, ...newState});
}
I found an alternative to the whole mess I had created. Instead of trying to optimize how I save the states (which might not even be possible) I realized that I don't actually need the states. I can access the form values directly.
By adding inputProps={{"data-row": row_id, "data-cell": cell_id}} to the TextField, and then using document.querySelectorAll("[data-row]"), it is easy and fast to access all cells and their data when it is actually relevant to use them (saving them to the database in my case).

Detect if the user got to the current route by using history.goBack()

I have an application that has a list of employees as a part of it. When the user clicks on an employee, the app navigates to the details page of the employee. In the details page there is a button that executes history.goBack(), which would take the user to the employee list, but that is not the only way to navigate to the employee list in the app.
I need to execute certain logic only when the user has gotten to the employee list by using history.goBack (the logic should NOT be executed, when the user gets to the employee list, f.e. using history.push).
How can I detect whether the user got there by using history.goBack() in the employee list component?
Presumably you also need to run this logic when the user clicks the built in browser "back" button, since this is functionally identical? The short answer is, you can't. The browser has no built in events for navigation direction by design for security reasons.
What you can do instead is create a HOC or Hook which calls history.listen and continuously updates some piece of state with the previous location. Then provide it as Context to the relevant components and compare it where needed with the current location.
This is probably a better solution anyway since it sounds like you only want to run logic in this limited routing scenario, and listening to a generalised "back" event would make it prone to errors should you add more ways to route back to the Employee List in the future.
You can use the history.action prop. After goBack is POP while after replace is PUSH or REPLACE.
https://reactrouter.com/web/api/history
Notice that the action is also POP on first load. If you want to ignore this case, combine it with history.length > 1.
To summarize:
const isBack = history.action === "POP" && history.length > 1;
https://codesandbox.io/s/react-router-dom-identify-goback-1po29?file=/src/pages/Project.js
(Click on "About" and "Back")

What is causing the input box text not to re-order in the ReactJS page example of Reconciliation?

In the ReactJS page of Reconciliation, there are two examples:
an example of the issues that can be caused by using indexes as keys
an updated version of the same example showing how not using indexes as keys will fix these reordering, sorting, and prepending issues
As of Feb 18, 2020, there is no telling of how to reproduce the issue on the page. I tried clicking on "Add New to Start" or "to End" a few times and reorder the list, and they seemed to work fine. Only later I found out you need to enter some text into the box, and then just "Add New to End", and do it three times, and reorder the list.
In the first example, the text in the input box were not re-ordered. In the second example, the text in the input box were re-ordered as expected.
The two programs differ by using
<ToDo key={index} {...todo} />
vs
<ToDo key={todo.id} {...todo} />
There are also some slightly re-ordering of code and using todoCounter vs toDoCounter (capital D) between the two versions, which I wonder why and the React team might fix it later. But you can modify the first version from key={index} to key={todo.id} and you can also see the problem solved.
But then when I looked into the code, the input box doesn't actually add the text data to the state property list (an array). Only id and createdAt are added per new entry to list.
So while we can say using key={todo.id} fixed the problem, what was causing the problem in the first place?
You can say that the id and createdAt are sorted correctly. What is not sorted correctly are the input boxes... but according to the rule of reconciliation to decide whether to refresh actual DOM elements on the page, the new Virtual DOM subtree is compared to the previous virtual DOM subtree.
Now the "property" of the input box value is not part of the virtual DOM, unless if React actually silently puts the value into the virtual DOM.
So when "diff'ing" recursively, React should think the input boxes are all the same. So is this how it worked: if we used the key={index}, now React diff'ed each column in the current virtual DOM subtree with the previous one, and see that the "ID" and "createAt" cells are different, therefore forcing a refresh to the actual DOM. React saw the input boxes all the same and didn't bother to force a refresh to the actual DOM.
However, if we use key={todo.id}, now React will think, the whole row is different, because the row "id" has changed. So React will force a refresh to the actual DOM for the whole row, including the input box.
So we can say, this bug occur only when some data is not in the virtual DOM subtree... which is rare, such as in this current case. In other cases, all the data would be given out by the render() of class component or the return of function component, and therefore, able to tell ReactJS that, "yes, force a refresh to the actual DOM".
Is this really how it worked?
Using index as keys is very dangerous and is highly advised against.
For example, if you delete an item from the middle of the list, another item will take its place, React needs a unique key to recognize a DOM element as an individual/independent element and not something that is temporary or useless.
Your list is being sorted as intended, you can see that the ID column is sorted accurately.
But in your case, React doesn't know which todo belongs to which ID, indexes are not todo list ID, they're numbers that were generated in order.
Imagine this
Todos:
id 1
todo: "take out the trash"
id 2
todo: "make dinner"
if you sort your todos by the shortest string
it will end up like this:
2
1
the normal order is:
1
2
No matter how you sort your list and change its order, if you tell React to use indexes, it will always render it like this:
1
2
3
4
...etc
If you use object IDs, React will keep track of the elements and will connect the keys to the IDs, then render your elements based on the order of your sorted IDs.

how to validate answers for quiz app, values which is fetched from json in reactjs?

I am developing a ReactJs quiz app in which I am having problem with validating the answers that is in the json.
I did this quiz app using react version 16.8 using state components and fetched the json data and stored in state using map function I have the completed the view part, now I started to validate the quiz and I am struggling in that part.
here is the full code:https://codesandbox.io/s/mystifying-firefly-2d2x5
and also ill add my json link: http://myjson.com/kpop9
I want the answer should be validated, and if user clicks the submit button before attempting all the questions it should show that you have unanswered questions and if user clicks submit after attempting all the quiz it should display the total marks that user got.
Replying to your comment from above, here is your sandbox code slightly edited. You can submit an answer at any time and these are the alerts you should see:
When you don't select an answer: Alert with message No nulls
When answer is wrong: Alert with message wrong
When answer is correct: Alert with message correct
Here is your edited sandbox
The solution provided is only example, and should not be treated as a perfect solution, it's only to give you an idea of how this may work (you may still want to implement the bit, when after submitting the last answer, scores are calculated - for that you may want to store scores)
Explanation:
In this example I decided to add selected_answer variable in the index.js that stores currently selected answer on the form.
Next, I created setAnswer function in index.js which accepts a selected answer as parameter and sets the selected_answer in state to whatever is passed in. You are welcome to implement as many checks for the value that is passed in as you want
setAnswer function is then passed to your Answer component, so when the value is changed, it can be saved inside index.js state
Result component receives the index.js state as a prop. This allows it to have access to current question, the array of all questions and currently selected value
Inside Result component there is a validateAnswer function that is triggered when submit button is clicked. Inside that function I use the props.current_question (which is the index of a question) to extract the whole question object from your JSON file. Next I filter over the array of answers from previously created question object, and I extract the one that has is_right set to 1. Finally, I check if the props.selected_answer is empty, and display a message if so. If it isn't, I check if it equals to the value of previously extracted correct answer object. And voila!
As mentioned before, this is not the best solution, but one that works on top of your code without changing much. Please let me know if you have any further questions, but hope that helps a bit.

What's the right way to reset / initialize InfiniteLoader

I'm trying to use InfiniteLoader from the react-virtualize library to show up a scrollable list that has a textSearch input field on top (used to filter list entries).
The code I use is very close to the InfiniteLoader Sample Code.
The list is working fine, but I'm not sure how to reset/initialize the InfiniteLoader when the searchText is changed and (completely) new data should be shown.
The flow is like this:
the list is opened for the first time and shows data from the redux store (works fine).
user changes textSearch and new data is fetched to the store
at this point, InfiniteLoader should be be initialized (I tried calling resetLoadMoreRowsCache on InfiniteLoader)
InfiniteLoader should call loadMoreRows like for the first time and rerender with the new data
I've seen that the INFINITELOADER DEMO has the same behaviour: by clicking 'Flush Cached Data' nothing happens until I start scolling the list.
So my question: what is the right way to reset/initialize?
For newer versions of InfiniteLoader
Since this question has been posted, InfiniteLoader has gained a parameter which auto-reloads the data. You can now use:
infiniteLoaderRef.resetLoadMoreRowsCache(true);
to automatically flush the cache and get new rows.
For older versions of InfiniteLoader
InfiniteLoader reacts to a range of rows being rendered. The resetLoadMoreRowsCache method just resets cached data. It doesn't automatically request a batch of rows be loaded.
Arguably it should. I don't know. It seemed easy enough for user code to auto-load the first batch of new data if the application state has changed in such a way as to require resetLoadMoreRowsCache to be called.
Anyway, tl;dr is that you should be able to do this:
infiniteLoaderRef.resetLoadMoreRowsCache(); // Reset the cache
loadMoreRows({ // Manually kick off the first batch
startIndex: 0,
stopIndex: 20 // Or whatever
});
Happy to review a PR to change the default behavior if you feel it could be improved.

Resources