React component not reloading on history.push - javascript

I have an app with routes defined as follows:
<Route exact path="/:category/:product" component={ProductComponent} />
On category page, "/:category I have two buttons for each product, "Quick View" and "Full view", on click of which I want to move to product page, but also set the state for view like setView('quick') or setView('full').
I tried solving this by using onclick on buttons with the following code:
() => {
setView('quick')
history.push(`/${category.slug}/${product.slug}`)
}
here both are called, history api changes the url but component does not load for that url. I cannot use the react-router's <Link> component because I need to set the view before going to product page.
How can I use the history from react-router to perform page change like <Link> does?

Try this
Remove component and use render
<Route
exact
path="/:category/:product"
render={({ match }) => <ProductComponent key={match.params.product || 'empty'} />}
/>

Related

React infinite render loop

I have created a component which needs to be individually displayed in multiple parts of the application - (inside of my navigation, on the home page and on a separate route). Therefore, I made all my action dispatches (api calls for reading data) inside a useEffect in this component:
useEffect(() => {
dispatch(time.read())
dispatch(pause.read())
dispatch(travel.read())
dispatch(distances.read())
}, [])
As you can see, this effect should only run once (when the component mounts).
Nowhere else do I dispatch these specific actions inside or outside of this component.
This component is rendered in 3 places (inside of the nav, in its own /time route and inside of the home component). I can render it inside of the navigation and on /time at the same time without a problem. It acts as it should (when I make changes from the navigation, they are mirrored on /time since it is all tied together with redux). Finally there is the Home component.
When I first tried to render on the Home component, it took a while to load, then I tried opening it up from my mobile device through my v4 address (same network), it took too long to load. At this point my laptop started sounding like a helicopter, so I opened htop, memory usage and CPU usage was pretty much maxed out, I opened up my server logs and saw hundreds of requests per second. Here is how I render this component inside my Home component:
import React from "react"
import { useSelector } from "react-redux"
import { NavLink } from "react-router-dom"
import "./Home.css"
import {
FaCarCrash,
CgProfile,
AiOutlineShop,
TiVideo,
} from 'react-icons/all'
import Tracking from "../TimeTracking/Tracking" /*This is the component which causes all the toruble*/
export default function Home(props) {
const current = useSelector(state => state.user.currentUser)
return (
<div className="Home container">
<div id="links">
<NavLink to="/create_job/">
<FaCarCrash />
<p> CREATE JOB </p>
</NavLink>
<NavLink to={"/profile/" + current._id}>
<CgProfile />
<p> PROFILE </p>
</NavLink>
<NavLink to="/shop/view/">
<AiOutlineShop />
<p> SHOP </p>
</NavLink>
<NavLink to="/tutorials/">
<TiVideo />
<p> TRAINING </p>
</NavLink>
</div>
<Tracking /> /*This is the component which causes all the toruble*/
</div>
)
}
As you can see, nothing out of the ordinary - a simple functional component which doesn't even dispatch any actions. Only communication with redux here is the useSelector.
Here is where I render the Home component:
import React, { Fragment } from 'react'
import { Route } from 'react-router-dom'
/*...other imports...*/
import Home from "../components/Mobile/Home"
/*...other imports...*/
export default props => (
<Fragment>
/*...other routes...*/
<Route path="/home" component={Home} />
/*...other routes...*/
</Fragment> /* just a fragment wrapper around the routes for the user. This component is later rendered inside of my <Router> </Router>. This is done because the app has a lot of different routes. */
)
For some reason, this component keeps re-rendering over and over again, even if I do not interact with the application. I've checked to see if I am ever dispatching the same action from 2 places which could cause this issue, but I am not. I know I haven't given a lot to go on, but there is really not that much to show in this issue, I'm just hoping someone has encountered it before. I've dealt with infinite render loops before, they are usually obvious and easy to solve but this one is a bit odd.
I have solved my problem. I was actually using the old version of my RootRouter.js which had the Home component declared like so:
<Route path="/home" component={<Home user={this.props.user} />} />
And the Home component I was rendering was also an older version (it was a class component, not the function component I wrote in the question).
So here is what caused the issue:
the user prop is passed down when declaring the Home route, and it holds the value of state.user
Then in the Home component, I am using mapStateToProps and mapping the user to the props, and setting it to state.user.currentUser.
This causes the user prop to be declared once when rendering the component, then changing once the component is rendered, and then it will change again on the next render and so on. The reason why react did not respond to the render loop is because the user prop holds a different value for each render.

Create a custom Show Layout component

I'm trying to create a custom layout component, so I can design my Show page better.
I'm not sure why some things work and some don't. My plan is to use the Material-UI <Grid> component. I know that the Grid component doesn't pass the props, then I'm extending to my own:
export default function OrderShow(props) {
return (
<Show {...props}>
<CustomGrid>
<TextField source="shortId" label="Short ID" />
</CustomGrid>
</Show>
);
}
My CustomGrid component, which is just a draft yet, is cloning its children to pass the props:
function CustomGrid(props) {
return React.Children.map(props.children, (child) => React.cloneElement(child, props));
}
When I use it, the TextField receives the source, and renders it correctly. However the label is gone. I'm not sure why this happens.
Another problem is that when I try to use the CustomGrid with a ReferenceField, it doesn't work at all:
<Show {...props}>
<CustomGrid>
<ReferenceField source="user" reference="users" label="Name" link="show">
<FunctionField render={(record) => `${record.firstName} ${record.lastName}`} />
</ReferenceField>
</CustomGrid>
</Show>
I can see that the props are passed until the ReferenceField, but it's lost before reaching the FunctionField:
Is there a better way to customize the SimpleShowLayout and continue using the TextField and other components from react-admin?
Yes, there is a way. Simply put react-admin's SimpleShowLayout is a little bit more coplex then just passing the props to the children fields. That's why you have to recursively iterate until you reach a field and perform the stuff the react-admin does.
Finally I have decided to share some privately developed components in a new package I am building. I have added layouts which work both with Box and Grid material-ui's components. You can have a look here: ra-compact-ui

React Native - Global screen outside React-Navigation

In my React Native application, I am using Redux to manage the global state of the app. This is what the render of my App.js looks like:
render() {
return (
<Provider store={store}>
<MainNavigator />
</Provider>
);
}
where <MainNavigator /> exports createAppContainer(createBottomTabNavigator({ ...my panel names })) and <Provider ... /> is imported from react-redux.
Everything works just fine. However, I would like to have a "global" screen which is independent of my navigator. I would like to have it mounted all the time immediately after the app has been launched and display it over the main navigator or hide it using Redux.
As an example, I am aiming to achieve something like the music screen on Spotify and SoundCloud which is always mounted while browsing through the whole app. Or as another example, the video screen on YouTube which serves the same purpose as the previous examples.
I have tried the following in my App.js:
render() {
return (
<Provider store={store}>
<GlobalScreen />
<MainNavigator />
</Provider>
);
}
However, this would split my application into two halves where the upper half of the screen displays the <GlobalScreen /> component and the lower half displays the <MainNavigator /> component.
How can I achieve a "global" screen, independent of React Navigation, which can overlay it or hide itself using Redux?
P.S. I am not sure if this can be achieved by having the screen outside React Navigation. But I am open to any suggestions which could help me achieve my desired goal.
You can use a conditional to render either the GlobalScreen or the MainNavigator.
{someConditionBasedOnDataFromStore ? (
<GlobalScreen/>
):(
<MainNavigator/>
)
You probably want this logic inside a component which connects to the store to get the data for your conditional.

React and navigating to a selected item

I have a table of user I am displaying to a user. They click a row, and I want to navigate to a component to show the details.
<tbody>
{this.state.clients.map(client => (
<tr className="tableRow" onClick={() => this.handleSelection(client.id)}>
<td>{client.name}</td>
My Route is setup like this:
<Route path="/client/:clientId" component={Client} />
So in the row, I have an onClick event, which calls a function, passing the parameter selected. Here's the function:
handleSelection(clientId) {
this.props.history.push(`/client/${clientId}`);
}
Then, in the constructor of the target component, I am just logging the selected id, which will be passed to the api to load the data:
console.log('Loading clientId...', this.props.match.params.clientId);
But this seem wrong. I get the right ID and all, but .. am I doing this right? Is there a better/safer way to do what I am doing?
Another option would just be to use individual Link tags inside each TD:
<td><Link to={`/client/${client.id}`}>{client.name}</Link></td>
That would also be more accessible, since vision impaired users might have trouble triggering that onclick event.
If you're using react-router-dom it has a Link component
{this.state.clients.map(client => (
<Link to={`/client/${clientId}`} >
<tr className="tableRow">
<td>{client.name}</td>
</Link>
It's better to use a link rather than an onClick on on a table row for accessibility.
If you're not using react-router-dom you can roll your own version from looking at the source - https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js
By working in this way it'll behave correctly when people command click on the link to open it in a new tab.
For the routes you grab the params as you have, if you don't want the component to deal with match in the properties you can do something like this:
<Route path="/client/:clientId" children={({ match }) => (<Client {...match.params} />)} />

React 2 components of same Class in a switch, moving from 1 to another, doesn't render again

So this is my Switch:
<Switch>
<Redirect exact from={`${match.url}`} to={`${match.url}/faq`} />
<Route exact path={`${match.url}/faq`} component={FAQPage} />
<Route path={`${match.url}/troubleshooting`} component={FAQPage} />
<Redirect from={`${match.url}/*`} to={`${match.url}/faq`} />
</Switch>
My FAQPage is a component that has a list, which shows data from REDUX, based on the type of the list:
let listType = 'troubleshooting';
if (this.props.match.url.indexOf('faq') !== -1) {
listType = 'faq';
}
Where I have a ListContainer which contains the react-semantic-UI List which will populate the data.
My problem is that, if I load a page, I will get back data, and show it, but when I move to the other page. It will not Re-render the listContainer, which means it will not fetch the data, so it will show empty.
If I force reload, then my second page will load, but when I move to the first one, I get the empty data.
IF I copy paste the code of my component, and create another component to put in the second Route of the Switch. Something like:
<Switch>
<Redirect exact from={`${match.url}`} to={`${match.url}/faq`} />
<Route exact path={`${match.url}/faq`} component={FAQPage} />
<Route path={`${match.url}/troubleshooting`} component={TroubleShootingPage} />
<Redirect from={`${match.url}/*`} to={`${match.url}/faq`} />
</Switch>
This will work, assuming because the component is a different one, so it's not reused, so it has to render completely.
My question is: Is it possible to force my FAQPage to reload, from the SWITCH?
So that it will always show the correct data? (I could just leave the code with 2 exactly the same .js files, but I do not want to have redundancy like that in the code.
I would like to help. Your scenario is little hard to understand, would you consider adding minimal example on codesandbox.io

Categories

Resources