React hangs when trying to connect to component - javascript

For some reason, when trying to connect this component, react simply hangs without giving any error:
import React, { useState, useEffect } from 'react';
import { useHttp } from '../../hooks/http.hooks';
import _ from 'lodash';
import profileImg from '../../img/system/profile/profileImg.svg';
function ProfileLink() {
const { request } = useHttp(); // To get data
const [profile, setProfile] = useState({});
const profileID = JSON.parse(localStorage.getItem('user')).id;
function takeProfileLink() {
const userID = JSON.parse(localStorage.getItem('user')).id;
setProfile({
...profile,
link: `profile/${userID}`
});
}
async function takeProfile() {
const data = await request(`http://localhost:5500/api/auth/get/${profileID}`);
setProfile({
...profile,
picture: _.get(data, 'profile.picture', 'https://i.pinimg.com/originals/0c/3b/3a/0c3b3adb1a7530892e55ef36d3be6cb8.png'),
name: _.get(data, 'profile.name', '')
});
}
async function takeProfilePicture() {
if (profile.picture) {
return `http://localhost:5500/api/upload/image_get/${profile.picture}`;
} else {
return profileImg;
}
}
async function standProfilePicture() {
const link = await takeProfilePicture();
setProfile({
...profile,
pictureLink: link
});
}
useEffect(async() => {
await takeProfile();
takeProfileLink();
}, []);
standProfilePicture();
return (
<a href={profile.link} className="profile-link">
<div className="profile-name">
{profile.name}
</div>
<div className="profile-picture">
<img src={profile.pictureLink} alt="profile picture"/>
</div>
</a>
);
}
export default ProfileLink;
Presumably the problem is with the profile object. Previously, everything was packaged in variables and everything worked, but now I replaced the variables with an object and react just stopped loading.

Try moving the call to standProfilePicture() inside the useEffect hook. A dangling function call may be causing the component re-render indefinitely, thus freezing the page.

Related

Using react + hooks, how can i call/use "navigate()" after a async redux dispatch properly?

When using "await" on "dispatch(saveItem(item))" it's not supposed to have any effct ,
meanwhile if i don't use the "await" both functions will run in the same time resulting a saved item but not a component rerender.
Although the state changes in the redux store the view doesn't,
whilst using the await actually waits for the dispatch to complete and then runs the navigation.
My main question is how to properly navigate after a redux dispatch?
import { useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate, useParams } from 'react-router-dom';
import { useForm } from '../hooks/useForm';
import { getById } from '../services/itemService';
import { saveItem } from '../store/actions/itemActions';
export function ItemEdit() {
const dispatch = useDispatch();
const navigate = useNavigate();
const [item, handleChange, setItem] = useForm(null);
const itemId = useParams().id;
useEffect(async () => {
await loadItem();
}, []);
const loadItem = async () => {
try {
const item = await getById(itemId)
setItem(item);
} catch(err) {
setErrMsg(err.name + ': ' + err.message);
}
};
const onSaveItem = async (ev) => {
ev.preventDefault();
await dispatch(saveItem(item));
navigate('/item')
}
return (
<form onSubmit={onSaveItem}>
<button>Save</button>
</form>
);
}
You can try it this way:
dispatch(saveItem(item))
.unwrap()
.then(() => navigate('/item'))
.catch(error => 'handle error')
It works for me.

React useEffect and React-Query useQuery issue?

I'm still new to React so forgive me if this is a silly approach to this problem.
My goal: Global error handling using a context provider and a custom hook.
The Problem: I can't remove errors without them immediately being re-added.
I display my errors via this component in the shell...
import React, { useState, useEffect } from 'react'
import Alert from '#mui/material/Alert'
import Collapse from '#mui/material/Collapse'
import { useAlertContext } from '#/context/alert-context/alert-context'
export default function AppAlert () {
const [show, setShow] = useState(false)
const alertContext = useAlertContext()
const handleClose = () => {
alertContext.remove()
setShow(false)
}
useEffect(() => {
if (alertContext.alert) {
setShow(true)
}
}, [alertContext.alert])
return (
<Collapse in={show}>
<Alert severity='error' onClose={handleClose}>
{alertContext.alert}
</Alert>
</Collapse>
)
}
I have a provider setup that also exposes a custom hook...
import React, { useState, createContext, useContext } from 'react'
const AlertContext = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = () => setAlert(null)
const addAlert = (message) => setAlert(message)
return (
<AlertContext.Provider value={{
alert,
add: addAlert,
remove: removeAlert
}}
>
{children}
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
And finally I have a hook setup to hit an API and call throw errors if it any occur while fetching the data. I'm purposely triggering a 404 by passing a bad API path.
import { useEffect } from 'react'
import { useQuery } from 'react-query'
import ApiV4 from '#/services/api/v4/base'
import { useAlertContext } from '#/context/alert-context/alert-context'
export const useAccess = () => {
const alertContext = useAlertContext()
const route = '/accessx'
const query = useQuery(route, async () => await ApiV4.get(route), {
retry: 0
})
useEffect(() => {
if (query.isError) {
alertContext.add(query.error.toString())
}
}, [alertContext, query.isError, query.error])
return query
}
This code seems to be the issue. Because alertContext.remove() triggers useEffect here and query.error still exists, it immediately re-adds the error to the page on remove. Removing alertContext from the array works, but it is not a real fix and linter yells.
useEffect(() => {
if (query.isError) {
alertContext.add(query.error.toString())
}
}, [alertContext, query.isError, query.error])
This is a perfectly fine approach to the problem. You've also accurately identified the problem. The solution is to create a second hook with access to the methods that will modify the context. AppAlert needs access to the data in the context, and needs to update when AlertContext.alert changes. UseAccess only needs to be able to call AlertContext.add, and that method wont change and trigger a re-render. This can be done with a second Context. You can just expose one Provider and bake the actions provider into the outer context provider.
import React, { useState, createContext, useContext } from 'react'
const AlertContext = createContext()
const AlertContextActions = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = () => setAlert(null)
const addAlert = (message) => setAlert(message)
return (
<AlertContext.Provider value={{ alert }}>
<AlertContextActions.Provider value={{ addAlert, removeAlert }}>
{children}
</AlertContextActions.Provider>
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
Now, where you need access to the alert you use one hook and where you need access to the actions you use the other.
// in AppAlert
import { useAlertContext, useAlertContextActions } from '#/context/alert-context/alert-context'
...
const { alert } = useAlertContext()
const { removeAlert } = useAlertContextActions()
And finally
// in useAccess
import { useAlertContextActions } from '#/context/alert-context/alert-context'
...
const { addAlert } = useAlertContextActions()
So I found a solution that seems to work for my purposes. I got a hint from this article. https://mortenbarklund.com/blog/react-architecture-provider-pattern/
Note the use of useCallback above. It ensures minimal re-renders of components using this context, as the function is guaranteed to be stable (as its memoized without dependencies).
So with this I tried the following and it solved the problem.
import React, { useState, createContext, useContext, useCallback } from 'react'
const AlertContext = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = useCallback(() => setAlert(null), [])
const addAlert = useCallback((message) => setAlert(message), [])
return (
<AlertContext.Provider value={{
alert,
add: addAlert,
remove: removeAlert
}}
>
{children}
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
My goal: Global error handling
One problem with the above useEffect approach is that every invocation of useAccess will run their own effects. So if you have useAccess twice on the page, and it fails, you will get two alerts, so it's not really "global".
I would encourage you to look into the global callbacks on the QueryCache in react-query. They are made for this exact use-case: To globally handle errors. Note that to use context, you would need to create the queryClient inside the Application, and make it "stable" with either useRef or useState:
function App() {
const alertContext = useAlertContext()
const [queryClient] = React.useState(() => new QueryClient({
queryCache: new QueryCache({
onError: (error) =>
alertContext.add(error.toString())
}),
}))
return (
<QueryClientProvider client={queryClient}>
<RestOfMyApp />
</QueryClientProvider>
)
}
I also have some examples in my blog.

Firebase Storage Context with Storage Data

I am using Firebase and React and learning how to use React Contexts. I created a context that queries Firebase to get data from there (user's files URL's for example). The context works fine, however, I can't get the context to become asynchronous. I haven't seen any examples of this and am not sure if it's possible. My code is below.
StorageContext.js:
import React, { useContext, useEffect, useState } from 'react';
import { auth } from './FirebaseConfiguration';
import fire from './FirebaseConfig';
import { useAuth } from './AuthContext';
const StorageContext = React.createContext();
export function useStorage() {
return useContext(StorageContext);
}
export function StorageProvider({ children }) {
const { currentUser } = useAuth();
const [fileURLs, setFilesURL] = useState([]);
async function getUserData() {
var storage = fire.storage();
var storageRef = storage.ref(currentUser.uid);
storageRef
.listAll()
.then(function (result) {
result.items.forEach(function (imageRef, i) {
let temp = filesUploaded;
temp.push(imageRef.name);
setFilesUploaded(temp);
});
console.log(filesUploaded);
// console.log(getData(filesUploaded));
getData(filesUploaded);
})
.catch(function (error) {
console.log(error);
});
}
const value = { getUserData };
return (
<StorageContext.Provider value={value}>{children}</StorageContext.Provider>
);
}
Dashboard.js:
import React, { useState, useEffect } from 'react';
import { useStorage } from './Contexts/StorageContext';
export default function Dashboard() {
const { getUserData } = useStorage();
async function getData() {
await getUserData().then((data) => {
console.log(data);
});
}
useEffect(() => {
getData();
}, []);
return (
<div>
console.log('data');
</div>
The useEffect in Dashbaord.js runs fine, the problem is that getUserData() returns immediately even though it should be waiting until (and thus the .then((data) => { console.log(data) } is empty.
Is it possible to run a Context Asynchronously? Or is there another problem that I am missing?
Thanks
The reason it returns immediately is that you use then and not await. Rewrite your function to this and it should work:
async function getUserData() {
var storage = fire.storage();
var storageRef = storage.ref(currentUser.uid);
const result = await storageRef.listAll();
result.items.forEach(function (imageRef, i) {
let temp = filesUploaded;
temp.push(imageRef.name);
setFilesUploaded(temp);
});
console.log(filesUploaded);
// console.log(getData(filesUploaded));
getData(filesUploaded);
}

request data from Github api

I wanted to import data(get users) from https://docs.github.com/en/rest/reference/users#list-users
the code in JS file(reactjs) is like
const { Octokit } = require("#octokit/rest");
const octokit = new Octokit();
async function myAsyncMethod() {
const result = await octokit.request("GET /users");
console.log(result.data);
}
myAsyncMethod();
but in the browser, didn't show anything, it's the correct way to get the data?
It all depends on where do you want to call this piece of code.
For instance, let's say that you need to call it as soon as the component that you're going to display is rendered. In that case, you will need to use the useEffect hook, and call your function from there.
See the following piece of code (I'm using the component App just for the example).
import React, { useEffect } from "react";
const { Octokit } = require("#octokit/rest");
export default function App() {
const octokit = new Octokit();
useEffect(() => {
getGithubUsers();
});
async function getGithubUsers() {
const result = await octokit.request("GET /users");
console.log(result.data);
}
return <></>;
}

How to call useState from another Page?

Bascially whenever I deleted an item in my handleDelete function it would route back to the homePage and I wanted to display a message that says your product succesully deleted for about 5 seconds.
In my index.js I first set message to false. and inside my ProductAttribute whenever I click it the set message will be true and will show the message in Index.js/ in my UI.
my handleDelete function
import React, { useState } from "react";
import { Header, Button, Modal } from "semantic-ui-react";
import axios from "axios";
import baseUrl from "../../utils/baseUrl";
import { useRouter } from "next/router";
function ProductAttributes({ description, _id }) {
const [modal, setModal] = useState(false);
const router = useRouter();
async function handleDelete() {
const url = `${baseUrl}/api/product`;
const payload = { params: { _id } };
await axios.delete(url, payload);
router.push("/");
setMessage(true);
setTimeout(function () {
setMessage(false);
}, 5000);
}
while in my Index.js. The setMessage in my useState isn't getting called from ProductAttributes file.
import React, { useEffect, useState } from "react";
import axios from "axios";
import ProductList from "../components/Index/ProductList";
import baseUrl from "../utils/baseUrl";
import { Message, Container } from "semantic-ui-react";
function Home({ products }) {
const [message, setMessage] = useState(false);
return (
<>
<Container>
{message ? (
<Message
deleted
icon="checked"
color="red"
content=" Product Successfully Deleted"
/>
) : (
""
)}
</Container>
<ProductList products={products}></ProductList>
</>
);
}
How can I make this setMessagebe callable in ProductAttributes? am I doing it right with the Parent to Child Relation or should I bring the useState in the child to parent?
You can create an handler in the Home Component like this
const handleSetMessage = (message) => {
setMessage(message)
}
this handler will be responsible of updating the value of message state in the Home component. and this methode you can pass it as props to ProductList component which will also pass it down to ProductAttribute. This will force you to pass props till the lowest level in your APP where you need to call that method.
Or you can take advantage of Context API which will allow you to have access to that method without passing it down as props.
const MessageContext = React.createContext("");
And in the Home component you use that Context like this
function Home () {
const [message, setMessage] = useState('');
const handleSetMessage = () => {
setMessage(true)
}
return <MessageContext.Provider> value={{setMessage: handleSetMessage}}>
// The code which render the component child goes here.
</MessageContext.Provider>
}
After that in your ProductAttribute Component you access to that setMessage function like this
import React, { useContext} from 'react';
const ProductAttribute = (props) => {
const { setMessage } = useContext(MessageContext);
const handleDelete = async () => {
// Here you call the setMessage function which will update state in the `Home` Component
setMessage();
}
return <div>
</div>
}
How can I make this setMessagebe callable in ProductAttributes?
A good practice would encompass you creating a handler function which delegates to the setState function and passing the reference of this function to ProductAttributes as props.
this is an example:
const [counter, setCounter] = useState(0);
const handleIncrementCounter = () => setCounter(counter + 1);
<ChildComponent handleIncrementCounter ={handleIncrementCounter }/>
then in ChildComponent..
function ChildComponent(props) {
return (
<button onClick={props.handleIncrementCounter}/>
);
}
Once you show you a message, wait for 5sec to close the message and redirect back to the home directory. just place the route.push('/') inside setTimeout so it will wait for 5sec to be redirected.
async function handleDelete() {
const url = `${baseUrl}/api/product`;
const payload = { params: { _id } };
await axios.delete(url, payload);
setMessage(true);
setTimeout(function () {
router.push("/");
setMessage(false);
}, 5000);
}

Categories

Resources