Route not updating state from navigation params - javascript

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]);

Related

How do I stop the re-rendering of my react component? It rerenders for every object in a drop down

After building the homepage of my website I finally figured out how to dynamically navigate to other pages. I wanted the browser to render the State homepage when a user clicked on a dropdown and selected a state. The navigation works, but it re-renders the component 50 times which I do not understand. I suspect it is due to the map function that is creating the menuitems. I could build out 50 individual menuitems but that is really ugly.
I am just starting out learning React. I have 7 YRS experience in backend development, but I am still trying to get a handle on React development. I have created a wepage with Material UI that has a dropdown that looks like this
<FormControl>
<InputLabel>Select a State</InputLabel>
<Select value={location} onChange={selectionChangeHandler}>
{locations.map((value) => (
<MenuItem value={value.toLowerCase()} key={value.toLowerCase()} component={Link} to={`${value.toLowerCase()}/home`} >
{value}
</MenuItem>
))}
</Select>
</FormControl>
This returns a dropdown with the 50 states in it. When the user clicks on a state I want the program to route to that page on click. This dynamic routing works BUT. It re-renders my component 50 times. I think this is happening because the dropdown is being built inside of a .map functions and there are 50 entries in that list.
I can remove the map function and hardcode in 50 menuitems but that is ugly.
Here is my onChange Function
const selectionChangeHandler = (event) => {
console.log(event.target.value)
}
I have also tried removing the component={Link} and using the useNavigate hook in the selectionChangeHandler like so
const selectionChangeHandler = (event) => {
console.log(event.target.value)
setlocation(event.target.value)
link = `${event.target.value}/home`
navigate(link)
}
This works but also renders 50 times. I am at a loss.
I cross posted the above to reddit and then I researched a little more. It turns out in React. When a parent component's state is updated it re-renders all child components. This may be what is going on here, but I do not know how to fix it. Even if I pass the state as a prop to the child component I still have to link to the correct page.
I am kind of nervous about posting I really tried to put work into solving my own problem before reaching out for help, and I might be reaching out for help a lot as I learn. I am committed to learning, but some problems I just cannot figure out on my own.
Link to Code Link to Code
The problem here is inside StateHome.js. You use a naked axios.get call directly in your component so it's going to re-render anytime the component changes from state change.
When you get the results you set state inside the same component which re-renders the component, which then re-runs the axios.get call, causing the loop.
I understand you want to call that endpoint when someone lands on the /alabama/home page. To tell React "Do this one time when the component loads" you must use a useEffect with an empty dependency array. So instead of this:
const StateHome = () => {
const { location } = useParams();
const [PageData, SetPageData] = useState();
axios.get(`http://localhost:4000/${location}/home`).then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
SetPageData(response.data);
});
return <h1>This is my State Home Page Component for State {location}</h1>;
};
You need to use this:
const StateHome = () => {
console.log("StateHome");
const { location } = useParams();
const [PageData, SetPageData] = useState();
useEffect(() => {
axios.get(`http://localhost:4000/${location}/home`).then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
SetPageData(response.data);
});
}, []); // <--- indicates "on mount", loads once regadless of other side-effects (setState)
return <h1>This is my State Home Page Component for State {location}</h1>;
};

how to make Redux state reset every time React component starts rendering

I have a state in redux that tells me the current number of posts that are rendered on the page
it looks like this
{
postReducer:{
...state,
loaded:0,
}
}
I have multiple components that should use this state when I switch between them.
Example:
I am on the home page all the posts from people I follow are there the state looks like this
{
postReducer:{
...state,
loaded:15,
posts: [],
profilePosts: [],
}
}
when I go from home to my profile or any other persons profile I want to see their posts only
but in order to start fetching their posts, the loaded state must be 0 but it's the same 15
I want the loaded to be 0 every time I go to a new component and keep its value while I am on the same component
I am using react-router-dom to switch between pages
I tried resetting it in the useEffect but the first batch of posts are the ones that are loaded only
I am not sure, what is the issue with useEffect.
I believe that you can do this in two ways:
Once your component loaded you can set 0 to loaded posts number:
const dispatch = useDispatch();
useEffect(() => {dispatch(cleanupPostsNumberAction)}, []);
Once your component unloaded (when you move from one page to another), you can use cleanup function to set loaded to 0
const dispatch = useDispatch();
useEffect(() => () => {dispatch(cleanupPostsNumberAction)}, []);
So if this answer is not complete, you can provide a little bit more context on the problem you have

How to reduce the number of times useEffect is called?

Google's lighthouse tool gave my app an appalling performance score so I've been doing some investigating. I have a component called Home
inside Home I have useEffect (only one) that looks like this
useEffect(() => {
console.log('rendering in here?') // called 14 times...what?!
console.log(user.data, 'uvv') // called 13 times...again, What the heck?
}, [user.data])
I know that you put the second argument of , [] to make sure useEffect is only called once the data changes but this is the main part I don't get. when I console log user.data the first 4 console logs are empty arrays. the next 9 are arrays of length 9. so in my head, it should only have called it twice? once for [] and once for [].length(9) so what on earth is going on?
I seriously need to reduce it as it must be killing my performance. let me know if there's anything else I can do to dramatically reduce these calls
this is how I get user.data
const Home = ({ ui, user }) => { // I pass it in here as a prop
const mapState = ({ user }) => ({
user,
})
and then my component is connected so I just pass it in here
To overcome this scenario, React Hooks also provides functionality called useMemo.
You can use useMemo instead useEffect because useMemo cache the instance it renders and whenever it hit for render, it first check into cache to whether any related instance has been available for given deps.. If so, then rather than run entire function it will simply return it from cache.
This is not an answer but there is too much code to fit in a comment. First you can log all actions that change user.data by replacing original root reducer temporarlily:
let lastData = {};
const logRootReducer = (state, action) => {
const newState = rootReducer(state, action);
if (newState.user.data !== lastData) {
console.log(
'action changed data:',
action,
newState.user.data,
lastData
);
lastData = newState.user.data;
}
return newState;
};
Another thing causing user.data to keep changing is when you do something like this in the reducer:
if (action.type === SOME_TYPE) {
return {
...state,
user: {
...state.user,
//here data is set to a new array every time
data: [],
},
};
}
Instead you can do something like this:
const EMPTY_DATA = [];
//... other code
data: EMPTY_DATA,
Your selector is getting user out of state and creating a new object that would cause the component to re render but the dependency of the effect is user.data so the effect will only run if data actually changed.
Redux devtools also show differences in the wrong way, if you mutate something in state the devtools will show them as changes but React won't see them as changes. When you assign a new object to something data:[] then redux won't show them as changes but React will see it as a change.

How to access current state of component from useEffect without making it a dependency?

I've got the following component (simplified) which, given a note ID, would load and display it. It would load the note in useEffect and, when a different note is loaded or when the component gets unmounted, it saves the note.
const NoteViewer = (props) => {
const [note, setNote] = useState({ title: '', hasChanged: false });
useEffect(() => {
const note = loadNote(props.noteId);
setNote(note);
return () => {
if (note.hasChanged) saveNote(note); // bug!!
}
}, [props.noteId]);
const onNoteChange = (event) => {
setNote({ ...note, title: event.target.value, hasChanged: true });
}
return (
<input value={note.title} onChange={onNoteChange}/>
);
}
The issue is that within the useEffect I use note, which is not part of the dependencies so it means I always get stale data.
However, if I put the note in the dependencies then the loading and saving code will be executed whenever the note is modified, which is not what I need.
So I'm wondering how can I access the current note, without making it a dependency? I've tried to replace the note with a ref, but it means the component no longer updates when the note is changed, and I'd rather not use references.
Any idea what would be the best way to achieve this? Maybe some special React Hooks pattern?
You can't get the current state because this component does not render on the app render that removes it. Which means your effect never runs that last time.
Using an effect cleanup function is not a good place for this sort of thing. That should really be reserved for cleaning up that effect and nothing else.
Instead, whatever logic you have in the app that changes the state to close the NoteViewer should also save the note. So in some parent component (perhaps a NoteList or something) you'd save and close like:
function NoteList() {
const [viewingNoteId, setViewingNoteId] = useState(null)
// other stuff...
function closeNote() {
if (note.hasChanged) saveNote(note)
setViewingNoteId(null)
}
return <>{/* ... */}</>
}

Filter redux state based on specific url to display one product and it's variations

I'm currently setting up a drupal based e-commerce react site. I already have it set up to make api calls and store data in my react app. When I console log I'm getting my current 3 products, and I'm getting another array of all the product variations. It's all there in the state.
Also, I have a page that displays product details. It shows description, color options, size options, etc. Right now I have it set up where when I click the link from the product selector in my app, it moves to the product page with a url containing the name of the product I clicked and displays the correct product description. However, I need to filter it so that the redux state only includes that product and it's relevant variations. Hopefully that makes sense...
Not sure what you guys need to see from the app in order to understand my issue so please let me know and I will add them to clarify. Thanks!
Since you have not provided any part of your code, I have assumed some of it.
I hope this helps:
class ProductPage extends Component {
constructor(props) {
super(props);
this.state = { productDetails: {}, relatedProducts: [] };
}
componentDidMount() {
// considering your product URL looks like domain.com/product/product-name
const productName = window.location.pathname.split('/')[2];
const productDetails = this.props.productList.find(product => product.name === productName);
const relatedProducts = this.props.productList.filter(product => // determine how a product can be considered as related to the current product);
this.setState({ productDetails, relatedProducts });
}
render() {
// render codes...
}
}
export default connect(
state => ({
productList: state.products // or whatever the name of your reducer for your product list in redux store
})
)(ProductPage);

Categories