React table does not refresh when data in a cell changes - reactjs

I have a React component in which I have a list of items that I store as a dictionary:
const [list, setList] = useState<{ [id: number]: MyProps }>({});
and I render this list as a table in the render method:
{Object.entries(list).map(v =>
<MyTableRow
// #ts-ignore
id={v[0]}
start_date={v[1].start_date}
end_date={v[1].end_date}
dollar_amount={v[1].dollar_amount}
key={v[0]} />
)}
MyTableRow is another React component and basically returns a HTML tr element whose columns are populated based on props passed to it.
So far so good and I have seen that when I add items to the list, the UI gets updated without me having to call setList.
Once I add a new item to the list, I also make some AJAX calls to fetch some additional pieces of information. This is when the problem starts to kick in. When the AJAX call completes, I update the data e.g., as follows:
var e = list[id];
e.dollar_amount = new dollar amount
But the UI does not refresh. I do not want to copy the entire list which could be quite big into a new object and then call setList. The list might have 100 elements in it. Each element corresponds to a row with say 10 columns. I want to be able to update a cell in the table and only that cell should re-render in the UI. Not the whole table. I thought that was the whole value proposition of React.
How can I make it work as described?
PS: I don't want to be making AJAX calls in the MyTableRow child component because the AJAX calls post data into a database and React can call MyTableRow many times for the same data which would result in duplicate records being added to the database.

Related

How to update maps in useState

When I first load the page, information is pulled from the database and then mapped into the variable data, where all the information is displayed as I want it.
//Maps the array and then displays the data from the database
var data = dataArray.map(info => {
//If/Else statements to display the teams in the right spacings
if(!checkIfNone(info.P1A) && !checkIfNone(info.P1M) && !checkIfNone(info.P2M) && !checkIfNone(info.P2A)){
return(/*A whole bunch table stuff*/)
}else if //etc etc
I display this in the component just fine using
return(
{data}
)
However, I am trying to implement a way to search through the data and through console.log testing I am able to store the updated information back into the data variable, but it does not update. I am unsure how to properly update this variable and I am unsure if I can use UseState and SetState since I set the state originally after a get method, then set it a different way later. Is there a good way to refresh the component while maintaining the current variable information? Or is there a way I can set the state of the map originally and change it later even if I can't set it until after the get is completed?
Since in the way I update the map, I map through each element in the dataArray, I am unsure how I would use UseState to update each element in the data map.

Prevent caching in a specific page react

I having problem when switching pages for my react app
supposedly i have this page that displays item information for example
items/1231231231231 = display the items for that specific id and then when i move to another item using Link
<Link to{'/items/22334567'}
to move to another item and display the item infos, on its initial render the item infos from the first item (items/1231231231231) is displayed first or loaded first then after the data has been fetched for the new item, it will only display the correct information for the next item ('/items/22334567') . I understand that it is important to cache files so that when user access the same data again the browser will not fetch the data again, but the thing is this interaction is kinda confusing in my part, so I want to disable caching or displaying of the first information before loading the next information by comparing their (id) this is what im thinking. Is there any way to achieve this?
You simply need to clear the state when you start loading new data on change of item id. I have to assume how your Item component code looks like but let us say you have a state items and a setter setItems. If you load your data in a useEffect, you can do something like this:
useEffect(() => {
setItems([]);
// display loader
// code to fetch items
}, [props.itemId]
If you load new items, another way e.g. in Redux or something, please think about the exact step where you need to reset the items to an empty array. It could be the reducer or some other place.

Create and Read State for thousands of items using Recoil

I've just started using Recoil on a new project and I'm not sure if there is a better way to accomplish this.
My app is an interface to basically edit a JSON file containing an array of objects. It reads the file in, groups the objects based on a specific property into tabs, and then a user can navigate the tabs, see the few hundred values per tab, make changes and then save the changes.
I'm using recoil because it allows me to access the state of each input from anywhere in my app, which makes saving much easier - in theory...
In order to generate State for each object in the JSON file, I've created an component that returns null and I map over the initial array, create the component, which creates Recoil state using an AtomFamily, and then also saves the ID to another piece of Recoil state so I can keep a list of everything.
Question 1 Is these a better way to do this? The null component doesn't feel right, but storing the whole array in a single piece of state causes a re-render of everything on every keypress.
To Save the data, I have a button which calls a function. That function just needs to get the ID's, loop through them, get the state of each one, and push them into an Array. I've done this with a Selector too, but the issue is that I can't call getRecoilValue from a function because of the Rules of Hooks - but if I make the value available to the parent component, it again slows everything right down.
Question 2 I'm pretty sure I'm missing the right way to think about storing state and using hooks, but I haven't found any samples for this particular use case - needing to generate the state up front, and then accessing it all again on Save. Any guidance?
Question 1
Get accustomed to null-rendering components, you almost can't avoid them with Recoil and, more in general, this hooks-first React world 😉
About the useRecoilValue inside a function: you're right, you should leverage useRecoilCallback for that kind of task. With useRecoilCallback you have a central point where you can get and set whatever you want at once. Take a look at this working CodeSandbox where I tried to replicate (the most minimal way) your use-case. The SaveData component (a dedicated component is not necessary, you could just expose the Recoil callback without creating an ad-hoc component) is the following
const SaveData = () => {
const saveData = useRecoilCallback(({ snapshot }) => async () => {
const ids = await snapshot.getPromise(carIds);
for (const carId of ids) {
const car = await snapshot.getPromise(cars(carId));
const carIndex = db.findIndex(({ id }) => id === carId);
db[carIndex] = car;
}
console.log("Data saved, new `db` is");
console.log(JSON.stringify(db, null, 2));
});
return <button onClick={saveData}>Save data</button>;
};
as you can see:
it retrieves all the ids through const ids = await snapshot.getPromise(carIds);
it uses the ids to retrieve all the cars from the atom family const car = await snapshot.getPromise(cars(carId));
All of that in a central point, without hooks and without subscribing the component to atoms updates.
Question 2
There are a few approaches for your use case:
creating empty atoms when the app starts, updating them, and saving them in the end. It's what my CodeSandbox does
doing the same but initializing the atoms through RecoilRoot' initialState prop
being updated by Recoil about every atom change. This is possible with useRecoilTransactionObserver but please, note that it's currently marked as unstable. A new way to do the same will be available soon (I guess) but at the moment it's the only solution
The latter is the "smarter" approach but it really depends on your use case, it's up to you to think if you really want to update the JSON at every atom' update 😉
I hope it helps, let me know if I missed something 😊

Integrate mui-datatable with admin-on-rest

How to integrate mui-datatable with admin-on-rest?
Need the following options in the list view :
move around ordering of columns
show/hide columns
change the number of rows for display
print preview of table
Mui-datatable has these features. According to admin-on-rest documentation, we can use custom Datagrid. Can anyone explain how to do it?
Say you are making a custom-datagrid component 'MyIterator'. This component has to just make use two props that will 'auto-magically' be available to the child of 'List' component. One prop is ids and another is data.
The ids prop is an array of id of currently displayable data-items. And the data is an object containing the data for all the items displayable.
Something like this should do the part of making a custom-datagrid -
const MyIterator = ({ids, data}) => ids.map(id=><div key={id}><span>Label</span><span>{data[id].label}</span></div>)
Above example presumed the data-item object to have a property of 'label'.
And this is how a custom-datagrid can be made.

ReactJS issue on my test app

So, I've been working through my first ReactJS app. Just a simple form where you type in a movie name and it fetches the data from IMDB and adds them as a module on the page. That's all working fine.
However each movie module also had a remove button which should remove that particular module and trigger a re-render. That's not working great as no matter which button you click it always removes the last movie module added rather than the one you're clicking on.
App:
http://lukeharrison.net/react/
Github codebase:
https://github.com/WebDevLuke/React-Movies
I'm just wondering if anybody can spot the reasoning behind this?
Cheers!
Just a hunch, but you should use a unique key, not just the index of the map function. This way React will understand that the movies are identified not by some iterating index, but an actual value, and that will probably solve your issue.
var movies = this.state.movies.map(function(movie, index){
return (
<Movie key={movie} useKey={index} removeMovieFunction={component.removeMovie} search={movie} toggleError={component.toggleError} />
);
});
This is because React re-evaluates your properties, sees that nothing has changed, and just removes the last <Movie /> from the list. Each Movie's componentDidMount function never runs more than once, and the state of Movie 1, Movie 2 and Movie 3 persists. So even if you supply search={movie} it doesn't do anything, because this.props.search is only used in componentDidMount.
I'm not exactly sure why it isn't rendering correctly as the dataset looks fine.
Looking at the code, I would change your remove function to this...
var index = this.state.movies.indexOf(movieToRemove);
console.log(this.state.movies);
if (index > -1) {
this.state.movies.splice(index, 1);
}
console.log(this.state.movies);
this.setState(this.state.movies);
My assumption is that, the state isn't being updated correctly. Whenever updating state, you should always use setState (unless the convention changed and I wasn't aware).
Also, you shouldn't need to explicitly call forceUpdate. Once setState is called, React will automatically do what it needs to and rerender with the new state.
State should be unidirectional (passed top down) from your top level component (known as a container). In this instance, you have state in your top level component for search strings and then you load individual movie data from within the "Movie" component itself via the IMDB API.
You should refactor your code to handle all state at the top level container and only pass the complete movie data to the dumb "Movie" component. all it should care about is rendering what you pass in it's props and not about getting it's own data.

Resources