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!
I am a totally noob at laravel and npm and vuejs things.
I made a new Laravel Project and instead of playing around with jquery I want to learn how to use vuejs.
I ran against a wall today :( trying 2 days to get this Multiselect (https://vue-multiselect.js.org/#sub-select-with-search) running on my project.
I think I am missing some basics ...
What I've done:
ran on terminal npm install vue-multiselect
created in resources/js/comonents/Multiselect.vue
pasted this code in /Multiselect.vue:
<template>
<div>
<multiselect
v-model="selected"
:options="options">
</multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
components: { Multiselect },
data () {
return {
selected: null,
options: ['list', 'of', 'options']
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
added to my app.js in resources folder:
- import Multiselect from "vue-multiselect";
- Vue.component('v-multiselect', require('./components/Multiselect.vue'));
- const app = new Vue({
- el: "#app",
- data: {
- users: '',
- firmas: '',
}});
and in my blade file I used:
<v-multiselect></v-multiselect>
So far ... so good
npm run dev and refreshed the page.
Error:
index.js:133 [Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <VMultiselect>
<Root>
so I have two questions is this the correct way to implement external vuejs components inte Laravel ?
and what If it is the right way am I doing wrong - at which points???
Thank you all out there to help me to learn ...
I'm glad you got your code working! To answer your question, it looks like you're using a mix of the external component you're importing and your own custom component which uses that component which may be what is confusing you a little bit.
When you do the following:
import Multiselect from "vue-multiselect";
inside your app.js file, you are importing an external component globally. When you place that import inside of a component, you are importing the external component for use within that component only. In your current code you've posted, you are importing it both globally and within your component.
If you are registering a component globally (within the element id assigned to the vue instance), you can register it like this within your app.js file:
import Multiselect from "vue-multiselect";
Vue.component('multiselect', Multiselect);
Then in your components, you will not have to import it again, but simply use it like this:
<template>
<div>
<multiselect v-model="selected" :options="options" placeholder="Select one" label="name" track-by="name"></multiselect>
</div>
</template>
<script>
export default {
data() {
return {
selected: null,
options: ['one','two','three'],
}
},
}
</script>
You would also be able to use this component in your blade since it is defined within your app.js file.
However with the setup you're using now, your fix of:
Vue.component('v-multiselect', require('./components/Multiselect.vue').default);
is not actually registering the external component. You are registering YOUR component.
So to answer your question, yes, you've taken an external component where you can make your custom component and easily add reusable content around it which is perfectly valid use, but you could either remove the extra import of Multiselect in your app.js file, or import the Multiselect external component globally, like I mentioned above.
Update:
Found the solution for my problem:
in my app js there was the error!
Vue.component('v-multiselect', require('./components/Multiselect.vue').default);
I registered the component wrong :(
So second question is answered :)
But do you guys do it the same way? or I am completly wrong implementing external commponets into laravel?
I don't want to use vueex or vuerouter for now ... I need to learn laravel itself ... afterwards I know how Laravel works I will use vuerouter ... for my projects ...
So sorry for the long text - but needed to explain a little bit more about the situation - thnaks for reading guys!
Thank you very much Sawyer,
I got it, I thought :(
I want to use this multiselect component muptliple times in my page.
So I removed the extra import in my app.js - saw it in phpstorm that it was unused but didn't know why :) - now I know.
What I have so far:
hit me if I am wrong :)
in app.js: (registering my own component)
Vue.component('v-multiselect', require('./components/Multiselect.vue').default);
added Multiselect.vue to my components folder in laravel with this src:
<template>
<div>
<multiselect v-model="value" :options="options"></multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
// register globally
Vue.component('multiselect', Multiselect)
export default {
// OR register locally
components: { Multiselect },
data () {
return {
value: null,
options: ['option1','option2','option3']
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
and in my blade file:
<v-multiselect :options="['one','two','three']" ></v-multiselect>
I get no errors at all from vuejs butit isn't working as it should:
How do I overwrite the options array from my blade file ? As I saw on the documentation "options" is a prop of the component so why am I getting as select the predefined option array ['option1','option2','option3'] and not the array from the blade file:['one','two','three'] am I missing a shortcut or something else?? Thanks a lot for your patience ...
If you can tell me where to read about it - except the docs of vuejs - I think this will help me a lot!
when running the test script an error occurs
Invariant Violation: Could not find "store" in the context of "Connect (DragD
ropContext (App)) ". Either wrap the root component in a,
or pass a custom React context provider to and the
corresponding React context Consumer Connect (DragDropContext (App)) in connect options.
although everything works in an unassembled project. As I understand it, there are some problems due to the fact that the root component is wrapped in two hocs - one redox, the other dnd.
ReactDOM.render(<DragDropContextProvider backend={HTML5Backend}>
<Provider store={store}>
<App />
</Provider>
</DragDropContextProvider>, document.getElementById('root'));
I tried to wrap first in dnd, then in redaks - it did not help, everything is the same.
It seems you mixed up the legacy decorator API with the top-level API
Legacy Decorator API
With legacy decorator you can wrap as following:
import HTML5Backend from 'react-dnd-html5-backend'
import { DragDropContext } from 'react-dnd'
class YourApp {
/* ... */
}
export default DragDropContext(HTML5Backend)(App)
Check the documentation about Legacy Decorator API here...
Top-Level API
For the Top-Level API you use:
import { DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
....
...
<div className="App">
<DndProvider backend={HTML5Backend}>
<App />
</DndProvider>
</div>
Check the documentation about Top-level API here...
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.
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.