How can I persist id when i refresh page? - reactjs

Currently,I'm trying to store the index and id in localstorage so that i can get the specific id and Index when i refresh the page.
so I tried like this :
ParentComponent
//this index and id is what i want to get ///
const eachComponent = (index, id, name) => (
<DataSide id={id} key={index} onClick={() => setShow({ [id]: !name })}>
<SettingMenu
show={name}
chart={chart[index]}
changeLayout={changeLayout}
panelId={id}
panelIndex={index}
setChangeLayout={setChangeLayout}
/>
{chartcomponentsEle(
index,
id,
title,
barData,
scatterData,
bubbleData,
info
)}
</DataSide>
);
const layout = [
//this index and id is what i want to get ///
eachComponent(0, "first", show.first),
eachComponent(1, "second", show.second),
eachComponent(2, "third", show.third),
eachComponent(3, "fourth", show.fourth),
eachComponent(4, "fifth", show.fifth),
eachComponent(5, "sixth", show.sixth),
eachComponent(6, "seventh", show.seventh),
eachComponent(7, "eighth", show.eighth),
];
when i click menu there is Link like this:
...
<Box>
<Link
to={{
pathname: `/csvFile/${chart}`,
state: {
panelIndex: panelIndex,
panelId: panelId,
},
}}
>
<span>
<InsertDriveFileIcon style={{ fontSize: 30 }} />
<p>csv파일</p>
</span>
</Link>
</Box>
this is the child Component where i want to set the id and index to localstorage :
const { panelId, panelIndex } = location.state;
const { info } = state;
const { setInfo, AxisUpdate } = data;
let savedState = JSON.stringify(location.state);
localStorage.setItem("myState", savedState);
So i tried to get the item using getItem in the component where there is Link
but it didn't work at all
So I want to know how and where i should use getItem .
Thank you in advance

Make sure
localStorage.setItem("myState", savedState);
is getting called on click, pls debug.
As localStorage is global, you can easily get it in any scope with localStorage.getItem("myState");

Related

Triggering multiple mutations from parent component with react-query

I'm working on a project using react-query where I'm displaying a list. Each entry in the list consists of multiple input fields and it should be able to save just a single entry as well as possible to save all the entries at once.
While saving the data I want to display loading indicators for the elements that are being saved a retry button in case the saving fails and a success message when it works.
I see it's possible to get ahold of the MutationCache, but I can't seem to find anything about triggering mutations from outside the component where it's used.
I made a small codesandbox to illustrate the setup, otherwise my code is pasted below.
https://codesandbox.io/s/react-query-forked-5cuxgb?file=/src/Form.jsx
Form.js
import * as React from "react";
import { Person } from "./Person";
export const Form = () => {
const people = [
{
id: 1,
name: "John Doe",
age: 37
},
{
id: 2,
name: "Jack Johnson",
age: 45
},
{
id: 3,
name: "Jimmie Jones",
age: 23
}
];
const saveAll = () => {
// Trigger mutations here?
};
return (
<div>
{people.map((person) => (
<Person key={person.id} {...person} />
))}
<hr />
<button onClick={saveAll}>Save all</button>
</div>
);
};
Person.js
import * as React from "react";
import { useCreatePersonMutation } from "./useCreatePersonMutation";
export const Person = (props) => {
const { mutate, status } = useCreatePersonMutation(props.id);
return (
<div>
{status === "loading" && <span>Saving...</span>}
{status === "success" && <span>Success</span>}
{status === "error" && (
<button onClick={mutate} style={{ marginRight: 12 }}>
Retry
</button>
)}
{status === "idle" && (
<button onClick={mutate} style={{ marginRight: 12 }}>
Create Person
</button>
)}
<input value={props.name} disabled={status === "loading"} />
<input value={props.age} disabled={status === "loading"} />
</div>
);
};
useCreatePersonMutation
import { useMutation } from "react-query";
export const useCreatePersonMutation = (id) => {
return useMutation({
mutationKey: ["Create_Person", id],
mutationFn: () => new Promise((resolve) => setTimeout(resolve, 3000))
});
};
You can't really go into the mutation cache (queryClient.getMutationCache()) and look for existing mutations and invoke them, because mutations only "exist" once they have been invoked with .mutate or .mutateAsync.
So the mutations in your component aren't really "there yet".
The easiest solution would imo be to:
have a separate mutation that lives in the Form component
you invoke all requests in there in parallel to create all users
this will give you a separate loading state that you can either pass down to all components, or you just add one extra loading state (overlay) to the whole form while this mutation is running.
I ended up achieving the desired behaviour by executing a mutation for each person being saved.
const saveAll = () => {
Promise.allSettled(people.map((person) => mutateAsync(person)));
};
In the Person component that renders each row, I listen to the mutation cache and try to find the matching mutation by comparing the persons name with the name being passed to the mutation.
React.useEffect(() => {
queryClient.getMutationCache().subscribe((listener) => {
if (!listener) return;
if (listener.options?.variables.name !== name) return;
setStatus(listener.state.status);
});
}, [queryClient, name]);
This allows each Person component to show the status of the mutation. And retrying a mutation is as simple as executing the mutation.
const retry = () => {
const mutation = queryClient.getMutationCache().find({
predicate: (mutation) => mutation.options.variables.name === name
});
if (mutation) {
mutation.execute();
}
};
It doesn't scale well performance wise if you work with large lists, since each Person component gets notified about each and every mutation that gets triggered.
However the lists I work with are of limited size, so for now it seems to suit my needs.
https://codesandbox.io/s/react-query-forked-5cuxgb

I don't get the expected value of a react props in child component

I'm trying to manage some clients in a react js application (that I'm maintaining), it was first written in classes components but I'm now adding a functional child component and im not sure if this is the source of the problem or not (react-table examples only use functional component)
I have a main component that will do the data GET from a rest API and save it in state "entries" then I passe it to a child component as a props to render the data in a react-table, the problem is in this section as I have some buttons to edit and delete the data in react-modal, when I try access the props.entries after the buttons clicks I have an empty array of props.entries.
Here's the sandbox of the issue : https://codesandbox.io/s/stale-prop-one-forked-r6cevx?file=/src/App.js
I did a console.log when the delete button is clicked, and you can see that en entries array is empty.
You need to pass the showEditModal & showEditModal in useMemo dependency array. Since you dependency array is empty, when you click on edit or delete, it just points to the old function reference and it have the old entries value.
You can check the entries values by console.log.
Hope this solve your problem
const showEditModal = useCallback(
(client_id) => {
const tmpClient = props.entries.filter(function (el) {
return el._id === client_id;
})[0];
setClient(tmpClient);
setEditModal(true);
console.log('aaa', props);
console.log(client_id);
console.log(props.entries);
console.log(tmpClient);
},
[props.entries]
);
const showDeleteModal = useCallback(
(client_id) => {
console.log('showDeleteModal entries : ', entries);
const tmpClient = entries.filter(function (el) {
return el._id === client_id;
})[0];
setClient(tmpClient);
setDeleteModal(true);
console.log('Delete', entries);
console.log(client_id);
console.log(tmpClient);
},
[props.entries]
);
const columns = React.useMemo(
() => [
{
Header: 'fact',
accessor: 'fact'
},
{
Header: 'Actions',
accessor: 'length',
Cell: ({ cell }) => (
<>
{cell.row.values.length}
<Tooltip title='Supprimer' placement='top'>
<IconButton
variant='outlined'
id={cell.row.values._id}
onClick={() => showDeleteModal(cell.row.values.length)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
<Tooltip title='Modifier' placement='top'>
<IconButton
variant='outlined'
id={cell.row.values.length}
onClick={() => showEditModal(cell.row.values.length)}
>
<EditIcon />
</IconButton>
</Tooltip>
</>
)
}
],
[showEditModal, showDeleteModal]
);

How can I rerender only one item in a flatlist?

I have products with a star icon to add this product in wishlist. I map 10 list of products and each map has 3 products like:
(I Map it in Pagerview to swipe to the next products)
Products Component
const ListProducts = [
{
id: 1,
products: [{
product_id: 1,
photos: [...]
}]
},
{
id: 2,
products: [{
product_id: 1,
photos: [...]
}]
}
{
id: 3,
products: [{
product_id: 1,
photos: [...]
}]
},
{
id: 4,
products: [{
product_id: 1,
photos: [...]
}]
}
...
];
function isEq(prev, next) {
if(prev.is_wishlist === next.is_wishlist) {
return true;
}
}
const Item = memo(({ id, photos, is_wishlist, onPress, onPressWishlist }) => {
const findProductIdInWishList = is_wishlist.find((el => el.product_id === id));
return (
<Button style={s.imgBtn} onPress={() => onPress(id)}>
<Button onPress={() => onPressWishlist(id)} style={s.starBtn}>
<AntDesign name={findProductIdInWishList ? 'star' : 'staro'} size={20} color={globalStyles.globalColor} />
</Button>
<ImageSlider photos={photos} />
</Button>
)
// #ts-ignore
}, isEq);
const wishlist = useSelector((state) => state.WishList.wishlist);
const dispatch = useDispatch();
const renderItem: ListRenderItem<IProduct> = ({ item }) => (
<Item
id={item.id}
photos={item.photos}
is_wishlist={wishlist}
onPressWishlist={handlePressWishList}
/>
)
const handlePressWishList = (product_id: string) => {
dispatch(addAndRemoveProductToWishList({product_id}));
};
List of Products component
Products Map:
<PagerView onPageSelected={(e) => handleSetAllIndexes(e.nativeEvent.position)} style={s.container} initialPage={index}>
{
allProducts.map((el, i) => (
<View style={s.viewsContainer} key={i}>
{ allIndex.includes(i) ? (
<View style={s.viewsInnerContainer}>
{ /* products */ }
<Products products={el.products as IProduct[]} total_price={el.total_price} product_name={el.packet_name} />
</View>
) : (
<View style={s.loadingContainer}>
<Loader size={'large'} color='#fff' />
</View>
)
}
</View>)
)
}
</PagerView>
if I click on star icon its dispatch and it goes fast but if I swipe to other products maybe to the last, then I press the star icon to dispatch then its a delay/lag you can see it
I dont add the full code because there are some snippets that has nothing to do with problem.
PS:
Video
I think there are a few issues in your code:
1. Wrong dependency list for useMemo.
In your Item component, you should pass the list of dependency, rather than a compare function:
const Item = memo(({ id, photos, is_wishlist, onPress, onPressWishlist }) => {
...
// #ts-ignore
}, isEq); // <- this is wrong
// Instead, you should do this:
}, [is_wishlist]); // <- this is correct, if you only want to update Item component when `is_wishlist` is changed
2. Never use index as key if item can be reordered
In your products maps component, you are doing:
allProducts.map((el, i) => (
<View style={s.viewsContainer} key={i}>
You should pass id instead, so React will not re-render all items when you insert a new item at the beginning or in the middle:
<View style={s.viewsContainer} key={el.id}>
3. Passing wishlist to all Items, however, wishlist will be updated whenever user click star button on any item.
This causes all Item to re-generate memoized component, because wishlist is changed.
What you want to do here is only passing essential data to Item, which is inWishList (or findProductIdInWishList in your code), which only get changed for affected item:
const renderItem: ListRenderItem<IProduct> = ({ item }) => {
const inWishList= wishlist.find((el => el.product_id === id));
return (
<Item
id={item.id}
photos={item.photos}
inWishList={inWishList}
onPressWishlist={handlePressWishList}
/>
)
}
I am going to edit my answer instead of comment. Before my code, let me explain first. In your current code, whenever 'allProducts' changes, everything will re-render. Whenever 'allIndex' changes, everything will re-render too. The longer the list, the more lag it will be.
Can you try 'useCallback' in this case?
const renderItem = React.useCallback((el, i) => (
<View style={s.viewsContainer} key={i}>
{allIndex.includes(i) ? (
<View style={s.viewsInnerContainer}>
<Products />
</View>
) : (
<Loading />
)}
</View>
),[allIndex])
{allProducts.map(renderItem)}
Now, renderItem will re-render when 'allIndex' changes. Instead of 'PagerView', I still recommend 'FlatList' with 'horizontal={true}' and 'some styles'. If still wanna use 'PagerView', how about 'Lazy' components? 'Lazy components' does not render the components before they came into user view. So, before they are seen, they do not take part in re-redner.
The issue is connected to a way how you edit your data to hold the new value. It looks like the act of adding an item to a wishlist causes all the previous items to re-render.
Therefore the first one works without an issue, but the last one takes a while as it needs to re-render all the other items before.
I would start by changing the key from index to an actual ID of a product block since that could prevent the other "pages" from re-rendering.
If that fails you will probably need to take this block of code into a workshop and check for useless re-renders.

TypeError: Cannot read property 'map' of undefined even though component isn't called yet

One of the pages in an app I'm currently building is a 'Category' page. It renders a table of rows from a particular category. These rows also have comments on them.
col x
col y
Comments
row1 x
row1 y
view
row2 x
row1 y
view
I'm using react redux, when I go to a 'Category' page, no comments have been added to state yet, only the relevant rows to the category have been added (their comments are in a different table in my database. I am trying to make a component (the 'view' button) that will open up a dialog where the comments for the chosen row are fetched and displayed.
The problem is that as soon as I go to the Category page, I get the error in my title (the error pointing to the map function I've starred below)
class MyComments extends Component {
state = {
open: false,
oldPath: "",
newPath: "",
};
handleOpen = () => {
let oldPath = window.location.pathname;
const { categoryId, rowId } = this.props;
const newPath = `/categories/${categoryId}/row/${rowId}`;
if (oldPath === newPath) oldPath = `/categories/${categoryId}`;
window.history.pushState(null, null, newPath);
this.setState({ open: true, oldPath, newPath });
this.props.getRow(this.props.rowId);
};
handleClose = () => {
window.history.pushState(null, null, this.state.oldPath);
this.setState({ open: false });
this.props.clearErrors();
};
render() {
const {
row: { comments },
UI: { loading },
} = this.props;
const commentsDialog = loading ? (
<div>
<CircularProgress size={200} thickness={2} />
</div>
) : (
<List>************
{comments.map((comment) => (
<ListItem>{comment.body}</ListItem>
))}
</List>***********
);
return (
<Fragment>
<Button onClick={this.handleOpen}>
<UnfoldMore color="primary" />
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
fullWidth
maxWidth="sm"
>
<Button onClick={this.handleClose}>
<CloseIcon />
</Button>
<DialogContent>{commentsDialog}</DialogContent>
</Dialog>
</Fragment>
);
}
}
MyComments.propTypes = {
clearErrors: PropTypes.func.isRequired,
getRow: PropTypes.func.isRequired,
rowId: PropTypes.string.isRequired,
row: PropTypes.object.isRequired,
UI: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
row: state.data.row,
UI: state.UI,
});
const mapActionsToProps = {
getRow,
clearErrors,
};
export default connect(mapStateToProps, mapActionsToProps)(MyComments);
One thing you might notice is that I push to a new url when the dialog is opened. One thing I did to debug this was I commented out the comments.map above (surrounded by *'s), then I was able to click on the dialog without error and go to the new url. once on the url, I changed my code back to the comments.map, and they displayed in a list in the dialog. So my problem is, my app is trying to render these options before they have been added to my state. I guess I'm wondering why my app cares about this map function before the dialog has opened?
My 'getRow' function returns a json as below so that's not the problem, I've console logged it to make sure the data is being fetched correctly, between this and what I just said above, I dont think that my actions or reducers are the problem, so I didnt add them but let me know if theyre relevant.
{
"categoryId": "id",
"index": "2",
"body": "New test",
"disapproveCount": 0,
"approveCount": -1,
"createdAt": "2021-03-05T23:16:26.142Z",
"rowId": "id",
"comments": [
{
"index": 2,
"body": "hello",
"rowId": "id"
}
]
}
I've had this problem for about a week but I finished everything else first as I couldn't figure it out and also thought it would be too difficult to explain on stackoverflow. I hope this makes some sort of sense, still don't know if im explaining it correctly/fully so I'll be very grateful/surprised with any suggestions.
Also, ideally I would just like to display these comments not in a dialog, so that no buttons need to be pressed to see them, but I was having this problem before I tried a dialog, and I thought loading a dialog and on a new url would fix the problem, i.e., the options wouldn't be attempted to be rendered until I opened this new page/url
This could possible be relevant:
Category page (called document)
class document extends Component {
componentDidMount() {
const categoryId = this.props.match.params.categoryId;
this.props.getCategory(categoryId);
}
render() {
const categoryId = this.props.match.params.categoryId;
const { category, loading } = this.props.data;
let placeholder = !loading ? (
<div>
<center>
<h1>{categoryId}</h1>
<h1> </h1>
</center>
<br></br>
<Paper>
<TableContainer>
<TableHead>
(...)
</TableHead>
<TableBody>
{category.map((row) => (
<TableRow key={row.rowId}>
(...)
<TableCell align="left">
<MyComments rowId={row.rowId} categoryId={categoryId} />
</TableCell>
(...)
</TableRow>
))}
</TableBody>
</TableContainer>
</Paper>
<br></br>
<MyForm categoryId={categoryId} />
</div>
) : (
<p>Loading...</p>
);
return <div>{placeholder}</div>;
}
}
document.propTypes = {
getCategory: PropTypes.func.isRequired,
data: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
data: state.data,
});
export default connect(mapStateToProps, { getCategory })(document);
I'm not sure if this is the exact problem, but I think a Tl;Dr is: why does the map function in my dialog care about something undefined when it hasn't been called yet?
Any help greatly appreciated!
Place a ? symbol after the object key. It will check if the object key's value is undefined or null before proceeding further.
<List>
{comments?.map((comment) => (
<ListItem>{comment.body}</ListItem>
))}
</List>

React map function to create <Redirect> which carry wrong parameter

I am a beginner in React.
A.js
const obj ={
{name:objA, id:11111},
{name:objB, id:22222},
};
const [isRedirect, setIsRedirect] = useState(false);
xxxxxxx.....................
return(
obj.map((data) => (
<TableCell onClick = {setIsRedirect(true)}>
data.name
</TableCell>
{ isRedirect ?
<Redirect to={{
pathname:'/user-management',
state:{userID:data.id},
}} /> : "" }
)
)
user-management.js
useEffect(() => {
const history = createHashHistory();
if (history.location.state && history.location.state.transaction)
{
let state = { ...history.location.state };
delete state.transaction;
history.replace({ ...history.location, state });
}
console.log(props.location.state.userID); // show 22222
}
After I click the objA table cell,
The code in user-management.js executes,
but when I console.log(props.location.state),
It shows the user id as 22222 instead of 11111.
It seems props.location.state in user-management always stored the last value of json array obj.
How can I modify to code?
I would be grateful if u guys help.
And Sorry for the code structure.
There's probably more going on, but I think the onClick handler should be onClick={() => setIsRedirect(true)}.
I see you are trying to render multiple Redirects, but you've only a single isRedirect state. The entire component renders... every element you map. I suspect when once you toggle the isRedirect true that you probably get a redirect for every element mapped. They all redirect to the same path though, so the last redirect with data is the one you see.
Suggestion
Only render one Redirect conditionally, and set isRedirect to be the data.id value (assumes id's will always be truthy!!!)
...
const [redirectId, setRedirectId] = useState(null);
...
if (redirectId) {
return (
<Redirect
to={{
pathname: '/user-management',
state: { userID: redirectId},
}}
/>
);
}
return obj.map((data) => (
<TableCell onClick={() => setRedirectId(data.id)}>
{data.name}
</TableCell>
);

Resources