Why is my component that receives props not working when I destructure out the properties, but when I use props.key it's working? - javascript

The Problem
I have an application that uses this React Redux Boilerplate: https://github.com/flexdinesh/react-redux-boilerplate
I created a new page that is connected to the injected reducer + saga.
I receive following props: posts, loading, error, loadPosts and match
When I use these directly the app is working as expected. But as soon as I start to destructure the props, the app is behaving unexpectedly.
Especially with the match props.
When I do it like this:
const SubforumPage = (props) => {
useEffect(() => {
const { id: subId } = props.match.params;
console.log('props: ', subId);
}, []);
// .... other code
}
No problem everything works.
But when I do it like this:
const SubforumPage = ({match}) => {
useEffect(() => {
const { id: subId } = match.params;
console.log('props: ', subId);
}, []);
// .... other code
}
match suddenly gets undefined!
I have really no clue what so ever why this is happening. It's the first time that I see an error like this.
This specific page is set up like this in the routing file:
<Route path="/sub/:id" component={SubforumPage} />
And it's clearly working when using (props) in the function arguments but not with ({match})
Why is this? Can please someone help me out here.
What have I tried?
I continuesly started destructuring one prop after another. At first this approach works and it's still not undefined but when I get to some props, it's different which ones, it will stop working.
I think it has to do something with how I use my useEffect() hook?
I pass an empty array so it does just run when mounting. It seems like when I refresh the page, the posts are cleared out but the useEffect doesn't run anymore, so the new posts doesn't get fetched. Because hen also the console.log inside the useEffect hook is undefined doesn't even run. But for example the loading prop in console.log outside of useEffect is indeed not undefined
(But that still does not explain why it's working with (props) as argument).
Am I just using useEffect wrong?
Many thanks

Ok guys that was completely my fault. Guess I'm too tired :D. Here is what caused the problem:
I fetch my post in the useEffect hook. I also render a component where I pass in the posts. But the posts are not available because the component has to wait for the data to come in. So I completely forgot that I have to wait for the data.
Before:
return <PostsGroup posts={posts} />;
After: (correct)
return <PostsGroup posts={posts || []} />;
I had a check in place looking like this:
if (loading) return <CircularProgress />;
(before the other return). But it doesn't matter because loading is false when the component initially renders.
So I also set the initial value from loading to true (in my initialState of the reducer). So I have now two checks in place.
Sorry guys. So stupid.

Related

component is getting loaded for fraction of second in React

I have one query call which returns me the data and also I am using await as well.
const {data, isLoading } = useQuery('getData', queryFunc);
here ,
I am trying to use it here
<div>
{ data?.addition?.isEnabled && <Notification> }
</div>
Here, for fraction of second the Notification component is getting rendered., and after 1-2 second once response comes then the component is rendered.
Is there any specific reason for this? also How do I fix this ?
I fixed this like { data && data?.addition?.isEnabled && <Notification> }
can any one help me with this?
In React undefined and false values are not rendered. Since the data you're getting is from an async function the notifications component will be rendered briefly until initialized and then removed. You can maybe use a useCallback hook to fix this behavior but without seeing more of the code I'm not sure on what to do.
Usage of useCallback is like so:
const foo = useCallback(() => { //you can pass parameters here when calling like this foo(arg1,arg2)
//do something expensive in resources...
//this will re-render when done.
return bar;
},[//dependencies go here]);
//use with useEffect hook if you want this to render on load like so:
useEffect(() => {
foo();
},[foo]);

Why React Component unmounts on each useEffect dependency change?

I am trying to learn React by building a web application. Since I want to learn it step by step, for now I don't use Redux, I use only the React state and I have an issue.
This is my components architecture:
App.js
|
_________|_________
| |
Main.js Side.js
| |
Game.js Moves.js
As you can see, I have the main file called App.js, in the left side we have the Main.js which is the central part of the application which contains Game.js where actually my game is happening. On the right side we have Side.js which is the sidebar where I want to display the moves each player does in the game. They will be displayed in Moves.js.
To be more clear think at the chess game. In the left part you actually play the game and in the right part your moves will be listed.
Now I will show you my code and explain what the problem is.
// App.js
const App = React.memo(props => {
let [moveList, setMovesList] = useState([]);
return (
<React.Fragment>
<div className="col-8">
<Main setMovesList={setMovesList} />
</div>
<div className="col-4">
<Side moveList={moveList} />
</div>
</React.Fragment>
);
});
// Main.js
const Main = React.memo(props => {
return (
<React.Fragment>
<Game setMovesList={props.setMovesList} />
</React.Fragment>
);
});
// Game.js
const Game= React.memo(props => {
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
return () => {
document.getElementById('board').removeEventListener('click', executeMove, false);
};
})
return (
// render the game board
);
});
// Side.js
const Side= React.memo(props => {
return (
<React.Fragment>
<Moves moveList={props.moveList} />
</React.Fragment>
);
});
// Moves.js
const Moves= React.memo(props => {
let [listItems, setListItems] = useState([]);
useEffect(() => {
let items = [];
for (let i = 0; i < props.moveList.length; i++) {
items.push(<div key={i+1}><div>{i+1}</div><div>{props.moveList[i]}</div></div>)
}
setListItems(items);
return () => {
console.log('why this is being triggered on each move?')
};
}, [props.moveList]);
return (
<React.Fragment>
{listItems}
</React.Fragment>
);
});
As you can see on my code, I have defined the state in App.js. On the left side I pass the function which updates the state based on the moves the player does. On the right side I pass the state in order to update the view.
My problem is that on each click event inside Game.js the component Moves.js unmounts and that console.log is being triggered and I wasn't expected it to behave like that. I was expecting that it will unmount only when I change a view to another.
Any idea why this is happening ? Feel free to ask me anything if what I wrote does not make sense.
Thanks for explaining your question so well - it was really easy to understand.
Now, the thing is, your component isn't actually unmounting. You've passed props.movesList as a dependency for the usEffect. Now the first time your useEffect is triggered, it will set up the return statement. The next time the useEffect gets triggered due to a change in props.movesList, the return statement will get executed.
If you intend to execute something on unmount of a component - shift it to another useEffect with an empty dependency array.
answering your question
The answer to your question
"why this is being triggered on each move"
would be:
"because useEffect wants to update the component with the changed state"
But I would be inclined to say:
"you should not ask this question, you should not care"
understanding useEffect
You should understand useEffect as something that makes sure the state is up to date, not as a kind of lifecycle hook.
Imagine for a moment that useEffect gets called all the time, over and over again, just to make sure everything is up to date. This is not true, but this mental model might help to understand.
You don't care if and when useEffect gets called, you only care about if the state is correct.
The function returned from useEffect should clean up its own stuff (e.g. the eventlisteners), again, making sure everything is clean and up to date, but it is not a onUnmount handler.
understanding React hooks
You should get used to the idea that every functional component and every hook is called over and over again. React decides if it might not be necessary.
If you really have performance problems, you might use e.g. React.memo and useCallback, but even then, do not rely on that anything is not called anymore.
React might call your function anyway, if it thinks it is necessary. Use React.memo only as kind of a hint to react to do some optimization here.
more React tips
work on state
display the state
E.g. do not create a list of <div>, as you did, instead, create a list of e.g. objects, and render that list inside the view. You might even create an own MovesView component, only displaying the list. That might be a bit too much separation in your example, but you should get used to the idea, also I assume your real component will be much bigger at the end.
Don’t be afraid to split components into smaller components.
It seems the problem is occurred by Game element.
It triggers addEventListener on every render.
Why not use onClick event handler
/* remove this part
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
})
*/
const executeMove = (e) => {
props.setMovesList(e.target);
}
return (
<div id="board" onClick={executeMove}>
...
</div>
)
If you want to use addEventListener, it should be added when the component mounted. Pass empty array([]) to useEffect as second parameter.
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
}, [])

React setState() not setting state on certain items

I'm new to React and have some pretty simple code that is acting strange (I think).
The main app fetches a list of blog posts from a server, then passes them through props to a child component which spits the list out. By default, I'm trying to make the posts only show a preview like a title, and each post will have a state attached to it so I can keep track of which ones are fully shown or previewed.
I have the states set up like this:
const [posts, setPosts] = useState([])
const [postFullView, setPostFullView] = useState([])
The list initially is rendered as an empty list so nothing gets returned. When the data fetch finishes, it re-renders with all the posts.
I use useEffect for this in the child component:
useEffect(() => {
console.log('render') //Just to verify this got called
setPosts(props.posts) //Logs empty array 3 lines down,
//setPosts([4,5,6]) //Works fine, gets logged as [4,5,6]
console.log(props.posts) //Logs an array of 32 objects - so props is clearly not empty
console.log(posts) //Logs empty array as if setPosts did nothing, but logs [4,5,6] if I comment out setPosts(props.post) and use setPosts([4,5,6])
setPostFullView(posts.map(post => {return {id: post.id, view: false}}))
console.log(postFullView) //Will be empty since posts is empty
}, [props])
Hopefully, I explained clearly what I'm confused about - I can setState using a hard-coded array, but passing in props.posts does not do anything, even though it has content.
There is nothing wrong about your code, and the reason console.log(posts) spits empty array, it because setPosts(props.posts) is async call and not executed immediately, but tells react it should render again with new value for state.
Sometimes, like in your hardcoded array case, the code will work "fine", but it not guaranteed, for sure in production when code executed faster
yea its nothing wrong because the setState api is async did not show the changes in log but code is work properly and also if you need to check your state you can use React developer Tools extension for browser too see the state status
https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
Actually, your first issue is about understanding the state changing and the re-rendering on ReactJS. why you do not use the first initial state in the first render just like below:
const YourComponent = ({ posts: initialPosts }) => {
const [posts, setPosts] = useState(initialPosts);
Also, there is no need to have the first line you can use it exactly on the second line initializing, like this:
const YourComponent = ({ posts: initialPosts }) => {
const [postFullView, setPostFullView] = useState(
({ id }) => ({ id, view: false }) // es6 arrow function with using restructuring assignment and returning the object
);
After all, when you are using a useEffect or other hooks APIs, please add the specific internal state or prop name to dependencies, no put all the props in the array of dependencies, it caused bad costs and make your project slow to run.

Using React and Redux Hooks, why is my action not firing?

Edit: SOLVED! Please see below.
I want my Blog component to fire the fetchBlog action creator every time the browser requests its URL, be it via a link or a refresh. I'd like to do it with the React useEffect Hook and with the React-Redux useDispatch and useSelector Hooks. However, my action only fires when following the link to the page; I do not understand why, even after reading several explanations (like the official docs).
Here is the code:
// Everything duly imported, or else VSC would yell at me
export default function Blog() {
const dispatch = useDispatch();
// slug is set here with useSelector, this always works
useEffect(() => {
dispatch(fetchBlog(slug))
}, [slug, dispatch]);
const blog = useSelector((state) => state.blogs[0]);
// return renders the blog information from the blog constant
// since the action does not fire, blog is undefined because state.blogs is an empty array
}
I know that, on refresh, fetchBlog does not fire because of Redux DevTools and also because I put a debugger there. (And the back-end logs don't show the request coming in.) The action creator itself and the reducer must be working; if they weren't, the page would not load correctly when visited through a link.
Edit: I have determined useSelector and useDispatch are not the root cause of the problem, as changing the code to use connect with mapStateToProps and mapDispatchToProps gives the same result. The issue seems to be with useEffect.
I think the problem is you are returning the call to dispatch. Functions returned from useEffect are clean up functions, so I don't think this would run on mount, or update - only before unmount. Try this:
export default function Blog() {
// ...
// Don't return from useEffect. Just call dispatch within the body.
useEffect(() => {
dispatch(fetchBlog(slug);
}, [slug, dispatch]);
// ...
}
https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect
I'd like to clarify what the issue was, which #Trace guided me to finding.
useEffect wasn't being called on refresh because it gets called after the component renders/returns. When refreshing, the state - including the blog data - is lost; instead of returning, a TypeError is thrown because data.title doesn't exist. So useEffect never gets the chance of being called and fetch the blog's content.
The solution to that goes like this:
export default function Blog() {
// ...
useEffect(/* ... */)
const blog = useSelector((state) => state.blogs[0]);
if (!blog) {
return <p>Loading...</p>
}
// return actual blog contents here
}
So now fetchBlog does get called, updating blog and rendering the content.
It isn't clear to me where the slug comes from.
In theory useEffect runs after every render. In case of multiple parameters it will run the callback when one of the array parameters passed in the second argument changes.
Either create a useEffect with empty array as second argument to run it 'once' (e.g. when you refresh) or check the slug value.
Edits after checking the repo:
It's not going to work because useEffect is run AFTER the render (which was included in my answer although someone downvoted it). Dispatching the call will only happen after, or not at all if the exception was thrown before (in this case a nullpointer).
You can get the slug from react-router with match, may be handy for you to know.
export default function Blog({ match }) {
const slug = match.params.slug;
etc
The git repo shows how dispatch as is added as array parameter to useEffect, which is not necessary.

Where is it better to update the values of the hooks if the value received from the server?

I have useEffect, which gets two values from server 1, price of item 2, name of item. when I use the setPriceSquanchy functions (obj.price) setNameSquanchy (obj.name). my code is updated twice there is no way to make it so that it is updated only once.
import React, { useState,useEffect} from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [priceSquanchy, setPriceSquanchy] = useState("");
const [nameSquanchy, setNameSquanchy] = useState("");
useEffect(() => {
(async () => {
const res = await fetch(
`https://foo0022.firebaseio.com/vz.json`
);
const obj = await res.json();
setPriceSquanchy(obj.price)
setNameSquanchy(obj.name)
})();
}, []);
console.log("Hello")
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
TL;DR Your sample. Here is the fixed version (another fix with ReactDOM.unstable_batchUpdates here)
So your component is rendered twice after the promise is resolved right? Like this logs "App invoked" twice after logging "setting states". This is because first setA is called, then setB is called both causing a render.
In my opinion this is fine because anyway React will only apply the necessary patches to the DOM. It won't be a huge performance difference even if you fix it.
But if you want to fix it you can have a state containing both price and name something like { price: "", name: "" } in that way you'll only call setPriceName({ price: newPrice, name: newName }). Demo. As you see in this demo "App invoked" is only logged once after "setting states" is logged.
If you don't want to do that you can also use ReactDOM.unstable_batchedUpdates like this. As you can see this also works but it's "unstable". More on the API by Dan here and in this thread too
Also, at times React could also batch updates from setA and setB thus causing only one render. Like here. Here it got batched together because it didn't had a timeout and it was immediately after first render.
Focusing more on the question "Where is it better to update the values of the hooks if the value received from the server?"...
What you are doing is pretty correct. Another way would be making a container component for fetching the data then having a presentational component to actually render it. I don't really like this approach (nor does Dan suggests it now xD) it's really an overkill. You can read more on that here.
PS: Also in case you are wondering why there are still two renders in your sample's fixed version, well the first one is the initial render. So there's basically only one update after the promise is resolved.
useEffect is used for side effects.
From React Docs
Accepts a function that contains imperative, possibly effectful code.
Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React’s render phase). Doing so will lead to confusing bugs and inconsistencies in the UI.
Instead, use useEffect. The function passed to useEffect will run after the render is committed to the screen.
Now, what you're doing is in fact a side effect ( AJAX call) and here's a working demo to achieve the same via useEffect.
Notice the use of second argument to useEffect. This is dependency array and useEffect will only fire accordingly. By supplying an empty array, I make sure it fires runs only once. Failing to do this will continuously call this function in our component. You can also control it's firing based on some other state variables. More on reading here.
useEffects is a good place to do this. useEffects hook is intended for side effects, e.g. fetch request.
If a request is supposed to be done once on component mount and not on every render, useEffects should be used with empty inputs.
The function mixes async..await and promises in unnecessarily complex way.
It should be:
useEffect(() => {
(async () => {
const res = await fetch(
`......`
);
const obj = await res.json();
setPriceSquanchy(obj.price)
setNameSquanchy(obj.name)
})();
// return fetch cancellation function
}, []);

Categories