I'm building a widget and adding it on a react website. Widget does specific tasks based on the URL.
Let's say, I want widget to appear exactly when URL is www.company.com. But the widget appears on "www.company.com/welcome" as well if the user navigated through history.push/ or replace.
Only when a state Change happens inside widget on www.company.com/welcome, it realizes, "oh I am not supposed to appear here".
I'm quite new to programming. I want the widget to re-render everytime the parent website URL changes(and not wait for any state change inside Widget). How to do this?
Currently, this is how finding current URL. I tried 2 approaches.
In the first approach here, on some state change, widget realises it shouldn't come here.....but it requires that state change.
const Main =() ={
let currentUrl = window.location.href;
}
In the 2nd approach, it doesn't work even after a state change.
useEffect(() => {
let url = window.location.href;
setCurrentURL(url)
},[currentUrl]);
3rd approach suggested by user, behaves exactly like the above issue.
const Main =() ={
const [url, setUrl] = useState(window.location.href);
}
Something interesting : The widget works properly when it is added on a html website when moved between pages or when navigation is through window.location ="/"
Only when Parent website is navigated through history.push or history.replace and widget doesn't seem to render.
If it's a stupid mistake, feel free to let me know. I have wasted too much time on this problem already and would appreciate any sort of help
have you tried looking at useEffect?
DOC: https://reactjs.org/docs/hooks-effect.html
useEffect(() => {
//****DO SOMETHING HERE WITH YOUR WIDGITS HERE****
}, []); //
you are currently storing the
let currentUrl = window.location.href;
in a variable are you sure you don't want to use useState() hook?
import { useState } from "react";
const Main =() ={
const [url, setUrl] = useState(window.location.href);
}
Related
I have been trying to fix this issue for a while and I haven't found any suitable solution on the net.
I am trying to solve a rendering issue using nextJS and Arcgis JS API. In this codesandbox, I have created a simple app where I am incrementing a counter every 5 seconds and display a map.The counter is an useState hook and it seems that every time it is updated, the map is re render again. How can I render the map a single time.
I have been following this recommendation and the get started page on the NextJS website.I had a read at some questions on stackoverflow as well...
My code to add the map is the following...
const EsriMap = ()=>{
const mapDiv = useRef(null);
useEffect(() => {
console.log("load map")
// Grab the webmap object out of the UseRef() hook
// so that we can mutate it.
const map=new ArcGISMap({
basemap: "osm",
})
// let map = webmap.current;
const view = new MapView({
map,
container: mapDiv.current,
center: [140, -27],
scale: 40000000,
});
// Configure the map...
}, []);
return <div className={styles.mapDiv} ref={mapDiv}></div>
}
export default EsriMap;
I would appreciate if someone can help me to solve this one.
Problem:
I noticed that you are using the counter directly in App.js
This will cause the application to rerender each time the state of the counter changes
Solution:
You may consider moving your counter into a separate component or custom hook to avoid the rerender of all app
Ps: if you did not figure out how to do that I could prepare an example for you
I hope this helps you
Is there any way to setState in react immediately and not wait for re-render. I have 2 cases where if the values are set immediately, I can solve half the issues.
The first thing is making visibility to false immediately
const [visibilty, setVisibility] = useState(false);
setVisibility(false);
and the other is I'm picking the current URL from the window.location.href.But when I'm setting URLs like this, I'm not able to pick URLs when users navigate through the history.push(Where page doesn't refresh so........need one more render the setState to current URL)
const [currentUrl, setCurrentURL] = useState("");
let url = window.location.href;
setCurrentURL(url);
I'm new to programming,so appreciate any help.
I'm trying to use scroll position for my animations in my web portfolio. Since this portfolio use nextJS I can't rely on the window object, plus I'm using navigation wide slider so I'm not actually scrolling in the window but in a layout component called Page.
import React, { useEffect } from 'react';
import './page.css';
const Page = ({ children }) => {
useEffect(() => {
const scrollX = document.getElementsByClassName('page')
const scrollElement = scrollX[0];
console.log(scrollX.length)
console.log(scrollX)
scrollElement.addEventListener("scroll", function () {
console.log(scrollX[0].scrollTop)
});
return () => {
scrollElement.removeEventListener("scroll", () => { console.log('listener removed') })
}
}, [])
return <div className="page">{children}</div>;
};
export default Page;
Here is a production build : https://next-portfolio-kwn0390ih.vercel.app/
At loading, there is only one Page component in DOM.
The behaviour is as follow :
first listener is added at first Page mount, when navigating, listener is also added along with a new Page component in DOM.
as long as you navigate between the two pages, no new listener/page is added
if navigating to a third page, listener is then removed when the old Page is dismounted and a new listener for the third page is added when third page is mounted (etc...)
Problem is : when you navigate from first to second, everything looks fine, but if you go back to the first page you'll notice the console is logging the scrollX value of the second listener instead of the first. Each time you go on the second page it seems to add another listener to the same scrollElement even though it's not the same Page component.
How can I do this ? I'm guessing the two component are trying to access the same scrollElement somewhat :/
Thanks for your time.
Cool site. We don't have complete info, but I suspect there's an issue with trying to use document.getElementsByClassName('page')[0]. When you go to page 2, the log for scrollX gives an HTMLCollection with 2 elements. So there's an issue with which one is being targeted. I would consider using a refs instead. Like this:
import React, { useEffect, useRef } from 'react';
import './page.css';
const Page = ({ children }) => {
const pageRef = useRef(null)
const scrollListener = () => {
console.log(pageRef.current.scrollTop)
}
useEffect(() => {
pageRef.addEventListener("scroll", scrollListener );
return () => {
pageRef.removeEventListener("scroll", scrollListener )
}
}, [])
return <div ref={pageRef}>{children}</div>;
};
export default Page;
This is a lot cleaner and I think will reduce confusion between components about what dom element is being referenced for each scroll listener. As far as the third page goes, your scrollX is still logging the same HTMLElement collection, with 2 elements. According to your pattern, there should be 3. (Though there should really only be 1!) So something is not rendering properly on page 3.
If we see more code, it might uncover the error as being something else. If refs dont solve it, can you post how Page is implemented in the larger scope of things?
also, remove "junior" from the "junior developer" title - you won't regret it
At the moment I'm setting the document title in my function component like this:
useEffect(() => { document.title = 'My title' }, []);
I want my site to support different languages, so I've implemented react-i18next.
I have buttons to change the language calling the function below. This changes the strings immediately on my web page.
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);
}
Changing the code mentioned earlier to the one below works (note the t('...') function), but only on page load. If I press my language buttons, the page title isn't updated.
useEffect(() => { document.title = t('My title') }, []);
How can I achieve this?
The same issue exists on strings passed to other components. For instance
// inside parent
<ChildComponent title={t('My title')} />
// inside child
<h1>{props.title}</h1>
Your first problem is that with the empty Array on useEffect, you tell React to only execute the code inside onMount, which means when you update your Language, this function doesn't get triggered. To fix that, use something like this:
useEffect(() => { document.title = t('My title') }, [lang]);
I couldn't reproduce the second problem. Make sure that if you change the language, the child prop rerenders. Test by using a simple console.log('rendered');
I have a React component which has 2000 elements and based on some filter conditions I update my state, which internally causes re-rendering. Everything seems to be working fine. But when I togglefilter from 2000 elements to say 1000 elements and back&forth, the rendering takes a lot of time and sometimes the browser freezes. I did chrome timeline profiling, the major time consuming piece is rendering. Any help would be appreciated.
As suggested by #enjoylife is a great step but what if you have many components structures in your view, that would be very difficult to debug even memoising the component won't be able to subside the continuous or loop rendering.
I learnt this after I ran into strange freezing and weird error that wouldn't stop any time a user logged in on the homepage. Imagine of all screens. Sometimes, you would hardly notice your component re-rending.
Detect your screen/page (loop) re-rendering with console log
const Home = () => {
conso.log('home re-rending')
// some hooks
return <BigComponent />
}
As written above. The logs must not show more than a limited time deemed after a component has mounted. In my case, it's once. But if it is too much(logs) and would certainly freeze your pc. Therefore, follow the below steps carefully and retrace your steps.
Tips and prerequisite before trying out this proposed solution. Please make sure you have style guide setup e.g. Eslint, it's great. In my case, I reproduced the source code with cra, then sorted out the first and last listed problem which I encountered.
Be careful with the use of React hooks such as useEffect especially. Avoid causing a side effect in a component.
In my case, I created a reusable useUpdateEffect hook and what I intend it to solve as par the name was to detect an update of React props or window props, but it backfires, I won't share the code.
Also, do extra check if you passed correct and expected dependencies, on this Eslint deserve an accolade.
Avoid random keys in React list. Use unique and constant keys in a component list as react depend on it to identify each item. According to react library
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity. You may use the item index as a key as a last resort:
Avoid variable name conflict in your reducer and React component. Please consider the use of style guides as your friend to avoid this fall.
I made the stupid mistake to create a Foo class and use in its render function, which also leads to the freezing scene. Write here for anyone who could meet this problem again.follow this thread.
Avoid infinite loops, Imagine rendering a lot of data at a go. this happen
just in case you share my fate, I urge you to check your loops and make sure you do not have a += instead of -= (or vice versa). Those infinite loops can be quite a big pain in the neck.
Keep your reducer as a reducer, Avoid Action creator, an API call in your reducer or using another reducer in your reducer so, for instance, reducerA in reducerB. When you call to update reducerA in reducerB, the update in reducerA would trigger an update in reducerB whereby cause page/screen to re-render multiple times. for example
// this react reducer in my case
// reducer js file - reducerB
const useBusinesses = () => {
// reducerB as discussed above - the loading context
const { loading } = useLoadingContext(); // the culprit
const [data, setData] = useState(initialState); // initial state,
const [state, dispatch] = useReducer(reducer, data);
useEffect(() => setData(state), [state, setData]);
const { businesses, errorMessage } = state;
const setBusinesses = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_SUCCESS`, data: payload });
const setBusinessesError = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_ERROR`, data: payload });
const fetchBusinesses = async (lglt, type = 'food', limit = 12) => {
try {
// update reducerB: triggers multiple update in reducerA while requesting is pending
loading(FETCH_BUSINESSES, true);
const request = await API.businesses.getWithquery(
`long=${lglt[0]}&latt=${lglt[1]}&limit=${limit}&type=${type}`
);
loading(FETCH_BUSINESSES, false);
setBusinesses(request.data);
} catch (err) {
loading(FETCH_BUSINESSES, false);
// if (!err.response) dispatch(alertMessage(FETCH_BUKKAS, true, 'Please check your network'));
setBusinessesError(err.response.data);
}
});
return { businesses, errorMessage, fetchBusinesses };
};
export const [BusinessesProvider, useBusinessesContext] = constate(useBusinesses);
//home js file
Home = () => {
const { fetchBusinesses } = useBusinessContext();
conso.log('home re-rending')
// some hooks
useEffect(() => {
console.log('am i in trouble, yes!, how many troubles')
fetchBusinesses(coordinates)
}, [fetchBusinesses, coordinates])
return <BigComponent />
}
A quick fix is to implement shouldComponentUpdate See the docs, for whichever child component is being rendered ~2000 times.
shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value !== nextProps.value;
}
Another quick check is to ask yourself if your following the convention of using small, stateless children, passing only props. If not, it might be time to refactor.