I have three components which are feed, profile, profileImage. The feed will pass a profileObject to the profile and profile will extract the imageObject out from the profileImage and pass it to the profileImage. So the relationship among these three components are like below:
Feed -> Profile -> ProfileImage, where feed is the parent of Profile, and Profile is the parent of ProfileImage.
In the ProfileImage component, we have a loadFailed state which will be set to true if the image failed to load and if it is true then we will display a default avatar image.
<Image
source={
loadFailed
? defaultAvatar
: {
uri: imageObject.source,
}
}
defaultSource={loadingIcon}
onError={() => {
setLoadFailed(true);
}}
/>
Here is the difficult part, everytimes when the user refresh the page, the data is going to refetch everytime also, so the feed will again pass the object all the way down to profileImage component. As the object reference is different everytime, the useEffect in the profileImage component will always trigger and reset the loadFailed state to false again, which is the expected default behaviour, however, I dont want the render to load more than 1 time, the component is rendered at least 2 times in this case.
useEffect(() => {
if (loadFailed) {
setLoadFailedfalse);
}
}, [imageObject]);
Can someone helps? Sorry if my explanation is not clear
you also have to check if imageObject is available then setLoading as a false
useEffect(() => {
if (loadFailed || imageObject) {
setLoadFailedfalse);
}
}, [imageObject]);
Related
I have to get information about user before link work and I don't Know how can I do this.
It is not all my code but similar. On click I have to get info and then give it to component in which I link to, but link works first and info does not have time to geted.
const [userId, setUserId] = useState(null);
const filterUserbyId = (id) => {
setUserId(id);
}
return(
<Link
onClick={()=>filterUserbyId(props.id)}
to={{
pathname: `/user/${props.id}`,
state: {
userId: userId
}
}}>
)
Also this is the warning but it says exactly that I tell above
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
You have over-complicated such a simple task.
Instead of trying to fetch the data from the database and then pass that fetched data to the new component to which you will redirect to, you could just pass the user id as a URL parameter
return(
<Link to={`/user/${props.id}`} />
);
In the other component, extract the user id from the URL parameter using the useParams() hook
const { userID } = useParams();
Note: userID is the dynamic route parameter. For the above statement to work, route to this component should be defined as:
<Route path="/user/:userID" component={/* insert component name */}/>
Once you have the user id, use the useEffect() hook to fetch the data from the database and display it to the user.
useEffect(() => {
// fetch the data here
}, []);
Alternative Solution - (not recommended)
You could also do it the way originally tried but for this to work, you need to change the Link component to a normal button element and when that button is clicked, fetch the data from the database and then programmatically change the route using the useHistory() hook, passing along the fetched data to the new route.
const routerHistory = useHistory();
const filterUserbyId = (id) => {
// fetch user data
...
// redirect to another route
routerHistory.push(`/user/${props.id}`, { state: data });
}
return(
<button onClick={() => filterUserbyId(props.id)}>
Button
</button>
)
I suggest that you don't use this solution because you don't want to wait for the data to be fetched from the database before changing the route. Route should be changed as soon as user clicks and data should be fetched inside the new component.
So I have this screen that show product details, it works like a template because only the data coming navigation params changes, I am having the issue because there's no reload, it works fine when going back and mounting it again, that's not the case here since I have related products showing on that same screen, I'll need a way to be able to either reload the current route or update the state
I have checked with console.log nothing shows up on second click
constructor (props) {
super(props)
this.state = {
product: this.props.route.params.product,
id: this.props.route.params.product.id,
}
}
To navigate to use to the route I use on both the screen itself or in another route
viewProduct = (product) => {
this.props.navigation.navigate('SingleProduct', { product: product })
}
I have tried setState inside of both componentDidMount and UNSAFE_componentWillReceiveProps but the results only shows after an additional click
You can use the following function to navigate from singleProduction screen to the same screen with the different params.
this.props.navigation.replace('SingleProduct', { product: product })
use the route.params?.product directly and mention change in useEffect array.
useEffect(() => {
return () => {
// Clean up the subscription
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [route.params?.product]);
I have an user profile (/someuser) that I open using :
<Route path="/:user" component={Profile} />
The profile is like this:
export default function App(props) {
const [nick, setNick] = useState(props.match.params.user); //get the nick using props.
const [profile, setProfile_picture] = useState(profile_picture); // default one
useEffect(() => {
// get current user profile picture:
firebase.database().ref('/users/').orderByChild('user').equalTo(props.match.params.user).once('value').then(snapshot => {
if (snapshot.exists()){
var img = snapshot.val().img;
setProfile_picture(img);
}
});
}, []);
return(
<>
<img className={classes.profile} src={profile} />
<h1>{nick}</h1>
<Link to="/user2">Go to profile 2</Link>
</>
);
}
);
Supose I'm in /user profile and I click in the Link at the end of the code to go to the /user2. What I'd like to do is change the profile picture and the nick (without reload). Force after click to update this informations... Any ideas how can I do this?
Thanks.
You need to trigger the useEffect based on params change because when the params change component is re-rendered by react-router and hence the state will not be re-initialized.
Triggering the useEffect on params change will ensure data is updated when params change.
Also make sure to set nick state too ni useEffect as that too needs to update on params change
useEffect(() => {
// set nick state
setNick(props.match.params.user);
// get current user profile picture:
firebase.database().ref('/users/').orderByChild('user').equalTo(props.match.params.user).once('value').then(snapshot => {
if (snapshot.exists()){
var img = snapshot.val().img;
setProfile_picture(img);
}
});
}, [props.match.params.user]);
Do you mean without reloading the page? If so then I think you can make a boolean that toggles when you go to a different route and initiates then useEffect hook by putting it in the dependency array. Or you could move the code to a separate component and pass it in as a prop. Now to do it completely without redirecting the browser would be to use the toggle boolean as mentioned before, but using fetch api, axios, etc to send that nick to a backend and change it accordingly which I think is similar to an Ajax request I hope this helps.
I have a react functional component that shows list of tags and posts + few static text/decorations. I store the currently selected tag in a state using useState hook. Posts are fetched by using apollo's useQuery hook with tag variable. User should able to select a tag and it will replace the current tag state - thus the useQuery(POSTS_QUERY) will re-run with new tag variable.
const onTagSelectChange = (window: Window,
router: NextRouter,
name: string,
checked: boolean,
tagSetter: React.Dispatch<React.SetStateAction<string>>) => {
if (checked) {
setTagQueryInUrl(window, router, name)
tagSetter(name)
} else {
setTagQueryInUrl(window, router, null)
tagSetter(null)
}
}
const NewsList: NextPage = () => {
const router = useRouter()
const query = router.query as Query
// store tag in state
// initialize tag from `tag` query
const [tag, setTag] = useState(query.tag)
const { data: postsData, loading: postsLoading, error: postsError } = useQuery(
POSTS_QUERY,
{
variables: {
tag: tag
}
}
)
const { data: tagsData, loading: tagsLoading, error: tagsError } = useQuery(TAGS_QUERY)
// show error page if either posts or tags query returned error
if (postsError || tagsError) {
return <Error statusCode={500} />
}
return (
<div>
<h1>Here we have list of news, and I should not re-render everytim :(</h1>
<Tags
loading={tagsLoading}
data={tagsData}
isChecked={(name) => name === tag}
onChange={(name, checked) => onTagSelectChange(window, router, name, checked, setTag)}
/>
<Posts loading={postsLoading} data={postsData} />
</div>
)
}
My question is, why is my h1 block keeps re-rendering even though I don't pass anything to it? Or do I completely misunderstand how react works?
React components re-render whenever their state or props change. If I am reading this correctly then you are changing tag in state whenever the url changes and thus making the component to re-render itself.
As your state is declared on your NewsList component, any state change (as another user stated on his answer) will trigger a re-render of the whole component (NewList) and not only to the components that you have passed your state (thus to the static <h1> you have in there).
If there are parts of this component that have nothing to do with this state, you can move them outside to avoid the re-render.
Though, on cases like this, re-rendering your <h1> is not a cost for React. You should worry and follow this approach on custom components where more complex things going on (e.g. populating lists or calculating stuff etc..). In those cases, you don't want all this complex stuff to happen again, if they are not affected by a parent's state change. You should also always consider, if moving the component outside makes sense or by doing so, you make your code complex.
You should always strike a balance between well-organized and efficient code.
Let's say I have a nested URI structure, something like the following:
http://example.com/collections/{id}
http://example.com/collections/{collectionId}/categories/{id}
http://example.com/collections/{collectionId}/categories/{categoryId}/book/{id}
I can use react-router to render the correct component on page load, and when the URI changes.
Let's take the first case:
http://example.com/collections/{id}
Let's assume we have a CollectionShow component.
When the component first loads, I can pull the collection ID out of the URI and load the correct collection:
componentDidMount () {
this.loadCollection(this.props.match.params.id);
}
(Assume that loadCollection loads a collection with an AJAX call and sets it into the component's state.)
However, when the URI changes (through, e.g., the user clicking on a <Link>, react-router doesn't entirely re-build the component, it simply updates its props, forcing it to rerender. So, in order to update the compomnent's state, we also need to update the state on update:
componentDidUpdate(prevProps) {
if (!this.state.collection || this.collectionDidChange(prevProps)) {
this.loadCollection(this.props.match.params.id);
}
}
collectionDidChange(prevProps) {
return String(prevProps.match.params.id) !== String(this.props.match.params.id)
}
So far so good. But what about the second URL?
http://example.com/collections/{collectionId}/categories/{id}
Let's assume we have a CategoryShow component.
Now we don't only have to consider the collectionId changing, but also the category ID. We have to reload the collection if that ID changes, and we also have to reload the category if that changes.
The problem compounds with a third-level nesting (a BookShow component). We end up with something like this:
componentDidUpdate(prevProps) {
if (!this.state.collection || this.collectionDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId);
}
if (!this.state.category || this.collectionDidChange(prevProps) || this.categoryDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId);
}
if (!this.state.book || this.collectionDidChange(prevProps) || this.categoryDidChange(prevProps) || this.bookDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId)
.then(() => this.loadBook(this.props.match.params.id);
}
}
Not only is this unwieldy, it also results in a fair amount of code duplication across the three components, CollectionShow, CategoryShow and BookShow.
Using redux won't help matters much, because we still have to update the global state when the URI changes.
Is there a clean, efficient, React-friendly way of handling updates of nested resources such as these?
You could create a CollectionPage component that handles all the AJAX calls and keeps data in state.
This could pass down the collection, category/categories and books to the components (CollectionShow, CategoryShow and BookShow).
In CollectionPage you could use componentDidUpdate and componentDidMount as you presented it.
Your <*>Show components will know nothing about props.match.params.* and will only get the data needed to render the wanted content.
CollectionPage can be use for all your routes or you could change the route to something like
/collections/:collectionId?/:categoryId?/:bookId?
making all params options. You can check for the available ids in CollectionPage.
Hope it helps!
If I understood your problem it is something architectural. The parent component is the one that should be doing this management and injecting the result through subcomponents. Split your component in small components and render each one accordingly.
The code you shared will be splint in 3 others
The mponentDidUpdate(prevProps) method will go to the parent component simply as a componentDidMount().
Then if the router changes the component will be recreated and the new values will be sent across the modules.
If you dont wanna split you code you should at least do the step 2.
//everytime you get to the router this will be triggered and depending of the parameters of your router, you get the values you need and set the state
componentDidMount() {
if (!this.state.collection) {
this.loadCollection(this.props.match.params.collectionId);
}
if (!this.state.category) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId);
}
if (!this.state.book) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId)
.then(() => this.loadBook(this.props.match.params.id);
}
}
render() {
return (
//you can add conditions to render as well
<CollectionComponent {...this.props} {...{
collection: this.collection
}} />
<CategoryComponent {...this.props} {...{
categ: this.categ
}} />
<BookComponent {...this.props} {...{
book: this.book
}} />
)
}