React routing - react-router-dom pushState() call to history happens too many times - react-redux

I am using react-router-dom (v6) and noticed that I cannot use the browsers back button as expected. It seems like my application (using react/redux) pushes the location multiple times to the browser history when I am routing to a different location in my app.
This results in an application crash in Safari too:
SecurityError: Attempt to use history.pushState() more than 100 times per 30 seconds
The code looks something like this:
App.tsx
...
<BrowserRouter>
<Routes>
<Route path={ROUTES.auth.login} element={<PublicRoute component={Login} />} />
<Route path={ROUTES.auth.register} element={<PublicRoute component={Register} />} />
<Route path={ROUTES.dashboard} element={<PrivateRoute component={Dashboard} />} />
</Routes>
</BrowserRouter>
...
PublicRoute.tsx
...
export default function PublicRoute({ propsForComponent = {}, ...props }: PublicRouteProps): ReactElement {
const { showLoadingScreen } = useSelector((state: RootState) => ({
showLoadingScreen: state.loading.showLoadingScreen,
}));
const { component: Component } = props;
if (showLoadingScreen) return <LoadingOverlay />;
return <Component {...propsForComponent} />;
}
...
I found out that the PublicRoute component gets called multiple times due to the connection to the redux store with the useSelector hook.
showLoadingScreen gets updated when the application is trying to fetch the user, during this time this variable changes.
So my question is now: how do I prevent multiple re-renders, which causes the history.pushState() calls?
I know I could move the LoadingOverlay component to a different entry point. But In PrivateRoute I am also connected to the redux state because I need to access the user object. (I used the PublicRoute component to show a minimal example of whats going on...)
Any help would be highly appreciated,
cheers!

Related

Remix.run - common shared components

I’m just getting started learning remix.run and whilst I’ve gone through the tutorials there’s one bit I’m stuck on how I should implement it in remix.
If I wanted to display a common header that might toggle a sign in/sign out button based on the users logged in state where would this live?
My nextjs thinking would be to create the components and reference them in the common document. I know I can do this in the remix.server and remix.client files, but as my “login” component is and isn’t a route (I.e I might want to POST to the route when a user submits the login form but GET /login isn’t really a route) how would you structure something like this and would doing this even allow me to have loader and action functions in the shared component?
Do I just need to adjust my thinking about how to achieve this in remix or am I overthinking it and the above is perfectly valid?
I tried the following and it works. But then I end up just creating an empty "logout" route to process the form data with an action and loader that process the form in the case of the action or just redirect if a GET request via the loader. Is this the best approach?
export const SignIn = ({user}) => {
return (
<>
<form method="POST"action="/logout">
<input type="hidden" id="some" value="foo" />
{user ?
(
<button>sign out</button>
)
: (
<button>sign in</button>
)
}
</form>
</>
)
}
Thanks
based on https://remix.run/docs/en/v1/tutorials/jokes#build-the-login-form
it does indeed look like an empty route for logout:
import type { ActionFunction, LoaderFunction } from "#remix-run/node"
import { redirect } from "#remix-run/node"
import { logout } from "~/utils/session.server"
export const action: ActionFunction = async ({request}) => {
return logout(request);
};
export const loader: LoaderFunction = async () => {
return redirect("/");
};

React Router Switch Redux Dispatch

I have the below switch statement that routes the user to correct component based on the link they are on.
const Router = (props) => {
switch(props.page) {
case 'Equities' :
this.props.pageName('Equities');
this.props.pageURL('/Equities');
return <Equities />;
case 'Daily' :
return <Daily />;
default :
return ( <Redirect to="/Equities" /> )
}
}
const content = ({ match }) => {
return (
<div className="content">
<Router page={match.params.type} />
</div>
);
}
const mapDispatchToProps = {
pageURL,
pageName
};
export default connect(mapDispatchToProps)(content);
On the 4th line above, I am trying to dispatch an action to Redux to update page name and URL in the redux store that the user is on. I get the below error:
How can I dispatch actions based on the page user is on so I update name and URL to whichever page user is visiting?
So, for anyone experiencing this problem, it seems to be caused by my error on adding redux to the page crashing with the compose module.
My component structure for the app is like this:
App -> Skeleton -> TopBar, Sidebar, Content
So inside Content component I have a switch that displays the correct content for user. I was trying to add this functionality to Content. Now I added to Skeleton, and it works fine and is much better because I don't need to add now 8 different statements inside switch saying if match this do this do that. Now all I have is this.props.pageName(match.url); this.props.pageURL(match.params.type); so I record in redux the user is on and that's all.

Redux DevTools could not render

I am using redux-devtools with redux-devtools-dock-monitor and redux-devtools-log-monitor.
I followed documentation. However, in the console I receive this error:
index.js:1452 Redux DevTools could not render.
You must pass the Redux store to <DevTools> either as a "store" prop
or by wrapping it in a <Provider store={store}>.
Here is my Root component:
const Root = () => {
return (
<Provider store={store}>
<div>
<h1>Hello !!!</h1>
<DevTools/>
</div>
</Provider>
);
};
export default Root;
Additionally, I am getting this warning too:
redux-devtools#3.4.2" has incorrect peer dependency "react-redux#^4.0.0 || ^5.0.0".
I have used redux-devtools-log-monitor and redux-devtools-dock-monitor as monitors
I've tried the suggestion and now it gives another error (I'm using the DockMonitor). Ie this is my setup.
<DockMonitor toggleVisibilityKey='ctrl-h'
changePositionKey='ctrl-q'
changeMonitorKey='ctrl-m' >
<LogMonitor />
</DockMonitor>
<Provider store={store}>
<App />
<DevTools store={store} />
</Provider>
Produces the following error:
Uncaught Error: Passing redux store in props has been removed and does not do anything. To use a custom Redux store for specific components, create a custom React context with React.createContext(), and pass the context object to React-Redux's Provider and specific components like: . You may also pass a {context : MyContext} option to connect
In react-redux#6, the store is not passed via legacy React context and you should specify it explecitely via props like so:
<DevTools store={store}/>
Update 1: It was working for me because I had react-redux#6 in main project, but react-redux#5 for redux-devtools dependency. So if someone needs a quick workaround (you shouldn't include DevTools component in production anyway) till it will be supported, just do: cd ./node_modules/redux-devtools && npm i react-redux#5.
Update 2: redux-devtools#3.5.0 was just published, which adds support for react-redux#6.

How to access record's internals in Show/Edit/Create

Similar to admin-on-rest: Access row's column data within a Datagrid component although I think it doesn't apply to my cases:
export const PlantShow = (props) => {
return (<Show {...props}>
<TabbedShowLayout>
<Tab label="Analytics">
{ record.oneId && <MetricsCharts {...props} manufacturer="one" /> }
{ record.otherId && <MetricsCharts {...props} manufacturer="other" /> }
{ record.anotherId && <MetricsCharts {...props} manufacturer="another" /> }
</Tab>
</TabbedShowLayout>
</Show>)
}
There should be a way to access current record's internals so I can introduce conditional logic .. I didn't find anything useful in props.. I even tried to inject {record} in PlantShow function but that didn't work either. Also DependentInput definitely doesn't help here
Thanks!
You'll have to use an intermediate component as a child of Show. It will receive the record prop.
See https://codesandbox.io/s/wyln51r907 (in posts.js, around the PostShow component.

React-router transition always creates handler

I just started, so I don't know if this is desired behavior or if I have missed something. I'm using the Flux architecture (specifically Reflux but that should not be relevant). In my super-simple test app:
var App = React.createClass({
render: function () {
return (
<div className="container">
<Header />
<RouteHandler />
<div>
);
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="employees" handler={Employees}/>
<Route name="company" handler={Company}/>
<Route name="timesheets" handler={Timesheets}/>
<DefaultRoute handler={Company} />
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.getElementById('main'));
});
The handlers are React components, the simplest possible setup. When the app starts I land on the "company" page as expected. Then when I transition to "employees" all is well, but when I transition back to "company", I observe that an entirely new {Company} component is created, thus blowing away my state. This is going to result in many unecessary API calls, unless I'm doing something wrong or not understanding.
Is there a way to tell the Route to use an existing handler if one exists and just re-render it? Instead of creating a new class?
Keeping the state in a flux store
One solution for you would be to keep the state in a flux store. When the component is mounted, request data from the store in the getInitialState() function.
Keeping the state in a parent component
If you do not want to use a flux store (because it increases the complexity of your simple example), I recommend keeping the state in a parent component, and passing it to your as a prop.
Of course, this would only work if your parent component does not become unmounted. I believe that the root component <Handler /> in your example should stay mounted between state transitions in your example.
I recommend that you look at this tutorial that goes over how to pass props to a child component.
This tutorial goes over how to communicate to the parent from the child.

Resources