How to handle toasts with timeout on React + NextJS - javascript

I got a strange problem. Let's start with my stack, we're using React + NextJS + Tailwind css . I made an toast component using react context, inside my context I have methods which is managing my toasts. Now let's take a look at my code(I cant show all my code , but here is part im getting troubles with)
Context file:
export const ToastContextProvider:FC<ToastProps> = ({ children }) => {
const [toasts, setToasts] = useState<ToastComponentProps[]>([]);
const generateId = () => Math.random().toString(36).substring(2, 9);
const removeToast = (_id: string): void => {
setToasts(toasts.filter(({ id }) => id !== _id));
};
const showToast = (toast: ToastComponentProps) => {
const toastWithId = { ...toast, id: generateId() };
setToasts((oldToasts) => [...oldToasts, toastWithId]);
if (toast.timeout !== 0) {
setTimeout(() => removeToast(toastWithId.id), toast.timeout ?? 3000);
}
};
As you can see , every toast object have a proprety name timeout , when timeout = 0 we dont have to remove it automatically by code , and user have to close the toast by himself.
The main problem:
I call a toasts with timeout and without , and they are deleted randomly. I have tried a lot of things but Im still stuck.

Related

How Rerender component NextJS with changing state

I have an app that sends Images.
So at the first render, I have all the images shown in the frontend.
Then I send all the images in batch to x person.
And what I'm trying to do, is each time an image is sent, I want it to be deleted from the frontend.
So the client can see a kind of countdown of the images being sent and left.
I'm sending the setter of the useState to the backend, but each time it's being used isn't it supposed to refresh ?
I tried to put a button to see if the code works and yes it works fine, each time I click on it, its rerenders the images left correctly, does kind of refresh when I get the state I guess.
But how to do it automatically, refreshes each time an image is sent.
In the frontend where I display the images I did like so :
export default function Home() {
const [imageLinks, setImageLinks] = useState([""])
//Getting the image links set them to state and display them in the browser
const getImageLinks = async (cids) => {
cids.forEach((link) => {
let CIDImages = axios.get('https://ipfs.io/' + link)
.then(linkMetadata => {
let rawLinks = linkMetadata.data.image
let correctFormatLink = rawLinks.slice(7);
setImageLinks(oldArray => [...oldArray, correctFormatLink]);
})
})
}
//Images component
const images = () => {
return (
<>
{
imageLinks.map((link, index) => {
return <img src={`https://ipfs.io/ipfs/${link}`} alt="" key={index}/>;
}
)}
</>
)
}
useEffect(() => {
images()
}, [imageLinks, setImageLinks])
return (
{
images()
}
)
}
Then where I have my functions in a separate file :
export async function mint(setImageLinks, imageLinks, cids) {
//Sending images
let imageTower = [];
for (let i = 1; i < cids.length; i++) {
imageTower[i] = await tokenMinterFcn(cids[i])
let imageLink = await axios.get('https://ipfs.infura.io' + cids[i])
.then(linkMetadata => {
//Correct formay of the link
let rawLinks = linkMetadata.data.image
let correctFormatLink = rawLinks.slice(7);
// Filter to remove the link from imageLinks array
imageLinks.filter(imageLink => {
if (imageLink === correctFormatLink) {
imageLinks.splice(imageLinks.indexOf(imageLink), 1)
}
})
setImageLinks(imageLinks);
}).catch(err => {
console.log(err)
})
}
}

ToDo complete status not staying saved in storage in React Native

Edit - added minimally reproducible example: https://snack.expo.dev/#hdorra/code
I hope everyone can access the snack. So if you add a task, you can see it show up in the log. Click on the circle, it shows as true (meaning it is clicked). Save and refresh and everything is stored (the task) but the checkbox is not. I stripped the code to make it as bare minimum as possible but it shows the problem.
It has been days of me on this error. I am relatively new to stackoverflow so my apologies if my question isn't clear or I am not asking it in the correct format. I am trying to create a to do app in react native that is using async storage. I created a toggle button that saves the toggle to a state. This button is located in a component:
const [checkBoxState, setCheckBoxState] = React.useState(false);
const toggleComplete = () => {
setCheckBoxState(!checkBoxState)
handleEdit();
console.log(checkBoxState)
}
When the user checks on it - seems to be showing up correctly as marked true and false in the console.
Then, this is passed to an edit handler to update the array, again console shows it is the correct state:
const handleEdit = () => {
props.editHandler(props.todoKey, text, checkBoxState);
console.log(text2, checkBoxState)
};
Then it shows that it saved correctly:
const [todos, setTodos] = React.useState([]);
const handleEdit = (todoKey, text, newStatus) => {
const newTodos = [...todos];
const index = newTodos.findIndex(todos => todos.key === todoKey);
newTodos[index] = Object.assign(newTodos[index], {title: text, status: newStatus});
setTodos(newTodos);
console.log(todos, newStatus)
};
The async function to save to the device and load are as follows:
To save:
const saveTodoToUserDevice = async (todos) => {
try {
const stringifyTodos = JSON.stringify(todos);
await AsyncStorage.setItem('todos', stringifyTodos);
} catch (error) {
console.log(error);
}
};
To load from the device:
const getTodosFromUserDevice = async () => {
try {
const todos = await AsyncStorage.getItem('todos');
if (todos != null) {
setTodos(JSON.parse(todos));
console.log("loaded successfully");
}
} catch (error) {
console.log(error);
}
};
So here is the issue - I get the console log that says it is saved correctly and loaded. BUT, when I refresh, the checkbox state is not saved at all, just the title text (so it is saving but the checkbox would always be false (the initial state set). If I clicked on true, it would show as true and then when I refresh, it goes back to false.
I have spent days and days on this and can't figure it out. Any direction would be helpful Thank you!
I have gone through your code and found some errors you are making in different places. In Task.js you can do without that checkBoxState. For that, pass the status to Task as props while rendering it in FlatList, like so:
<Task
key={item.key}
todoKey={item.key}
title={item.title}
status={item.status}
editHandler={handleEdit}
pressHandler={handleDelete}
/>
Then as below, change the button to toggle the status, so you use what's coming from the props and create a function called toggleStatus and pass it to onPress:
<TouchableOpacity onPress={toggleStatus}>
<View
style={[
styles.circle,
!props.status ? styles.completeCircle : styles.incompleteCircle,
]}
></View>
</TouchableOpacity>
The code for toggleStatus:
const toggleStatus = () => {
props.editHandler(props.todoKey, props.title, !props.status);
};
And handleEdit would be simplified to:
const handleEdit = () => {
props.editHandler(props.todoKey, text2, props.status);
setEdit(false);
console.log(props.status);
};
Lastly, in TasksMain.js so you don't replace what's in the storage with that initial array given to useState, make sure saveTodoToUserDevice runs after getTodosFromUserDevice. For that, add the below state in TasksMain.js and slightly change the two functions as follow:
const [loading, setLoading] = React.useState(true);
const saveTodoToUserDevice = async (todos) => {
if (loading) return;
try {
const stringifyTodos = JSON.stringify(todos);
await AsyncStorage.setItem("todos", stringifyTodos);
} catch (error) {
console.log(error);
}
};
const getTodosFromUserDevice = async () => {
try {
const todos = await AsyncStorage.getItem("todos");
if (todos != null) {
setTodos(JSON.parse(todos));
console.log("loaded successfully");
}
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};

CoinbaseWalletSDK does not work with server side rendering

I was trying to load CoinbaseWalletSDK in my NextJS application, but it always throw an error of ReferenceError: localStorage is not defined due to it was imported before the window is loaded. I tried dynamic loading but it doesn't work. The following is what I am using at this moment.
export async function getServerSideProps({
params,
}: {
params: { project_id: string };
}) {
const project_id = params.project_id;
let project: any = fakeProjects[0];
if (project_id && typeof project_id === 'string' && !isNaN(parseInt(project_id))) {
const id = project_id;
project = fakeProjects.find(p => p.id === parseInt(id));
// Fetch project detail here
let item = await (
getNFTStatsByProjectId(
parseInt(project_id)
)
);
if (project && item && item['nftTotal'] && item['nftSold']) {
if (item.nftSold > item.nftTotal) {
item.nftSold = item.nftTotal;
}
project.nftTotal = item.nftTotal;
project.nftSold = item.nftSold;
}
}
const { coinbaseEth } = (await import('../../components/services/coinbase'));
return {
props: {
project: project,
coinbaseEth: coinbaseEth
},
};
}
And this is what I have in the coinbase service:
// TypeScript
import CoinbaseWalletSDK from '#coinbase/wallet-sdk'
import Web3 from 'web3'
const APP_NAME = 'Practice App'
const APP_LOGO_URL = process.env.WEBSITE_URL + '/logo.png'
const DEFAULT_ETH_JSONRPC_URL = 'https://mainnet.infura.io/v3/' + process.env.INFURA_PROJECT_ID
const DEFAULT_CHAIN_ID = 1
// Initialize Coinbase Wallet SDK
export const coinbaseWallet = new CoinbaseWalletSDK({
appName: APP_NAME,
appLogoUrl: APP_LOGO_URL,
darkMode: false
})
// Initialize a Web3 Provider object
export const coinbaseEth = coinbaseWallet.makeWeb3Provider(DEFAULT_ETH_JSONRPC_URL, DEFAULT_CHAIN_ID)
// Initialize a Web3 object
export const web3 = new Web3(coinbaseEth as any)
The new CoinbaseWalletSDK is where the error was thrown if that's a concern.
Based on my research, I will need to get it imported after the page is fully loaded (which is the point when "window" become available, as well as "localStorage"), which I have no clue how to achieve. Can anyone help me out on this?
I solved it by loading it later. What I did was to assign this variable with this function.
setTimeout(async () => {
coinbaseEth = (await import('../../components/services/coinbase')).coinbaseEth;
}, 1000)
I choose not to use useEffect because the value will be lost on render, which prevents the function to work properly.

React app using empty state inside onMessage from WEbsocket

I've created a application for my company to manage diffrent things.
I first started realizing it with Class-Components, now switched to Functional components.
The issue that i'm facing is, that in one function inside Websocket onMessage uses old/empty state.
This is how the application currently is built.
const DocumentControl = ({ createWebsocket, logOut }) => {
const [elementList, setElementList] = useState([]);
const webSocket = useRef(null);
useEffect(() => {
webSocket.current = createWebsocket();
webSocket.current.onmessage = (messageEvent) => {
console.log(elementList);
if (devComm) {
console.log(JSON.parse(messageEvent.data));
}
processMessage(JSON.parse(messageEvent.data));
};
window.addEventListener("beforeunload", () => {
logOut(true);
});
}, []);
useEffect(() => {
loadElements();
}, [menu]);
const processMessage = (message) => {
var newArr, index;
switch (message.type) {
case "document":
var elementItem = message.data;
newArr = [...elementList]; // here elementList is always []
...
break;
default:
if (devComm) {
console.log("---Unknown WSMessage!---");
}
}
};
}
There is obviously more logic but this may be sufficient to describe my problem.
Basically i'm loading from the Server my ElementList if the Menu changes.
Whenever an Element on the Server-Side is updated, I send a message through Websockets to all clients, so all are up-to-date.
---My Issue---
Loading the Elements from Server and using setElementList works fine. But whenever I receive a message through websockets, the state elementList is always empty.
I made a button which displays the current elementList and there the List is correctly.
For example: I have 4 Elements in my list. Because of Message in Websockets i want to add to my current list the new Element. But the elementList is [] inside processMessage, so I add the new element to an [] and after setElementList all other elements are gone (because of empty array previously)
My first thought was, that (because I get the Websocket element from parent) maybe it uses diffrent instance of elementList, which is initialized as empty [].
But it would not make sense, because setElementList still affects the "original" elementList.
I also tried using elementList with useRef(elementList) and accessing it with ref.current but still didn't change the outcome.
here is how the createWebsocket is implemented:
const createWebsocket = () => {
if (!webSocket.current) {
var uri = baseURI.replace("http://", "ws://");
console.log("Create new Websocket");
console.log("Websocket: " + uri + "websocket/");
let socket = new WebSocket(uri + "websocket/" + user.Token);
socket.onopen = () => {};
socket.onclose = (closeEvent) => {
console.log("closed socket:");
if (closeEvent.code !== 3001) {
logOut(false);
if (closeEvent !== null && closeEvent.reason.length > 0) {
alert(closeEvent.reason);
}
}
};
socket.onerror = () => {
logOut(true, false);
alert("Something went wrong");
};
webSocket.current = socket;
}
return webSocket.current;
};
Any Ideas why elementList is diffrent inside ProcessMessage then in other functions of the Component?
--------- UPDATE -------
I could temporary fix it, by using this workaround:
const elementsRef = useRef([]);
useEffect(() => {
elementsRef.current = elementList;
}, [elementList]);
and then accessing elementsRef.current
But there must be an more elegant soltuion to this

Why is my custom hook called so many times?

I'm trying to implement a custom hook to provide the app with a guest shopping cart. My hook wraps around the useMutation hook from Apollo and it saves the shopping cart id in a cookie while also providing a function to "reset" the cart (basically, to remove the cookie when the order is placed).
Code time! (some code omitted for brevity):
export const useGuestCart = () => {
let cartId;
const [createCart, { data, error, loading }] = useMutation(MUTATION_CREATE_CART);
console.log(`Hook!`);
if (!cartId || cartId.length === 0) {
createCart();
}
if (loading) {
console.log(`Still loading`);
}
if (data) {
console.log(`Got cart id ${data.createEmptyCart}`);
cartId = data.createEmptyCart;
}
const resetGuestCart = useCallback(() => {
// function body here
});
return [cartId, resetGuestCart];
};
In my component I just get the id of the cart using let [cartId, resetCart] = useGuestCart(); .
When I run my unit test (using the Apollo to provide a mock mutation) I see the hooked invoked several times, with an output that looks something like this:
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:59
Still loading
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:62
Got cart id guest123
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
I'm only getting started with hooks, so I'm still having trouble grasping the way they work. Why so many invocations of the hook?
Thank you for your replies!
Think of hooks as having that same code directly in the component. This means that every time the component renders the hook will run.
For example you define:
let cartId;
// ...
if (!cartId || cartId.length === 0) {
createCart();
}
The content inside the statement will run on every render as cartId is created every time and it doesn't have any value assigned at that point. Instead of using if statements use useEffect:
export const useGuestCart = () => {
const [cartId, setCartId] = useState(0);
const [createCart, { data, error, loading }] = useMutation(
MUTATION_CREATE_CART
);
const resetGuestCart = () => {
// function body here
};
useEffect(() => {
if(!cartId || cartId.length === 0){
createCart();
}
}, [cartId]);
useEffect(() => {
// Here we need to consider the first render.
if (loading) {
console.log(`Started loading`);
} else {
console.log(`Finished loading`);
}
}, [loading]);
useEffect(() => {
// Here we need to consider the first render.
console.log(`Got cart id ${data.createEmptyCart}`);
setCartId(data.createEmptyCart);
}, [data]);
return [cartId, resetGuestCart];
};
Also notice that there is no actual benefit from using useCallback if the component which is receiving the function is not memoized.

Categories

Resources