I have some problems with my Vue app.
I'm trying to update user information that are stored in localStorage, and I'm updating it with websockets in App.vue in mounted function, like so:
window.Echo.channel("user." + this.userData.id).listen(".user-updated", (user) => {
localStorage.setItem('userData', JSON.stringify(user.user))
});
And so far, so good actually, localstorage is updating realtime, but the problem is, the user must refresh the page or change route so other information that is shown in another components could be updated.
Is there any way I can update all my components at once from App.vue?
You can emit events using event bus, or else use Vuex. For example:
window.Echo.channel("user." + this.userData.id).listen(".user-updated", (user) => {
localStorage.setItem('userData', JSON.stringify(user.user));
//emit event
EventBus.$emit('EVENT_USER_DATA_CHANGE', payLoad);
});
Then, on other components:
mounted () {
EventBus.$on(‘ EVENT_USER_DATA_CHANGE’, function (payLoad) {
...
});
}
You can use :key binding on your body or main container. (e.g. <main :key="pageTrigger">)
In your listen function update pageTrigger with new Date().getTime() so it will re-render the components inside the container.
Are you using vuex too? put the pageTrigger in some store so it will be accessible from everywhere in your app.
Related
I have a standard React/Redux app, that when loads, initially pulls localStorage values (via useEffect hook that triggers the reducers, etc.), and then stores/saves them when changed.
However, I have legacy code, that stores and saves one of these localStorage values, and saves it directly.
It's all on the same domain so the localStorage value is saved via the React app or legacy code. But I need a way for the React/Redux end to reactively update if localStorage is updated from the legacy code.
You can handle Window: storage event like so:
useEffect(() => {
const localStorageListener = (event) => {
const value = JSON.parse(window.localStorage.getItem('sampleValue'))
dispatch(updateValue(value));
};
window.addEventListener('storage', localStorageListener);
return () => {
window.removeEventListener('storage', localStorageListener);
};
}, []);
alternatively you could window.onstorage property... but IMO listener seems cleaner in React.
I'm currently trying to convert some hooks over to being inside class components in React. I'm attempting to recreate the code that allows for session storage so that the app doesn't need to fetch from the API so much. Here is the code:
useEffect(() => {
if(sessionStorage.homeState){
// console.log("Getting from session storage");
setState(JSON.parse(sessionStorage.homeState));
setLoading(false);
}else{
// console.log("Getting from API");
fetchMovies(POPULAR_BASE_URL);
}
}, [])
useEffect(() => {
if(!searchTerm){
// console.log("Writing to session storage");
sessionStorage.setItem('homeState', JSON.stringify(state));
}
}, [searchTerm, state])
These are two useEffect hooks so it makes sense to me to go with a regular componentDidMount to get from the session storage. The only thing that I can't seem to figure out is how to recreate the second useEffect that sets session storage and fires only when searchTerm or state changes. searchTerm and state are simply two properties of the state. How could I implement this since componentDidMount only fires once when the app first mounts? Thanks
The only thing that I can't seem to figure out is how to recreate the second useEffect that sets session storage and fires only when searchTerm or state changes. searchTerm and state are simply two properties of the state.
componentDidMount() is only one of methods used by the class components you can recreate the second hook with componentWillUpdate() or shouldComponentUpdate().
For example:
componentWillUpdate(nextProps, nextState) {
if (this.props.searchTerm !== prevProps.searchTerm) {
...
}
}
You can check what lifecycle methods are available in class components by Googling "class component lifecycle".
But as you can read in the comment to your questions Hooks can offer you more than class components, and recreating them is not trivial. It is easier to move from the class component to the Hooks.
I am trying to use react hooks to make a Table component that displays rows of data from an API based on a set of filters that the user can choose. I want to make a new call to fetch data whenever the user clicks an 'Apply Filters' button, not when the user makes changes to the filters.
I am using context to manage the 'filters' state and a 'lastFetched' state which tracks when the user last clicked the 'Apply Filters' button (as well as other states on the page). Updates to the context are made via the useReducer hook and its dispatch method (see here).
The data fetching occurs in a useEffect hook that reruns whenever the 'lastFetched' state changes. This appears to be working correctly; however, the effect references other values from the context (i.e. the filters) that are not included in the dependencies. I am aware of the exhaustive-deps eslint rule, and I am concerned that I am not handling the hook's dependencies correctly.
const Table = () => {
const [context, dispatch] = useTableContext(); // implemented with createContext and useReducer
const { filters, lastFetched } = context;
useEffect(() => {
if (!filters.run) {
return;
}
dispatch({ type: 'FETCH_DATA_BEGIN' });
const params = convertContextToParams(context); // this is lazy, but essentially just uses the the filters and some other state from the context
API.fetchData(params)
.then((data) => {
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data.results });
})
.catch((e) => {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: e.response.data.message });
});
return () => { ... some cleanup... };
}, [lastFetched]); // <== This is the part in question
return <...some jsx.../>
};
Again, this appears to be working, but according to the react docs, it seems I should be including all the values from the context used in the hook in the hook's dependencies in order to prevent stale references. This would cause the logic to break, since I don't want to fetch data whenever the filters change.
My question is: when the user clicks 'Apply Filters', updates context.lastFetched, and triggers the useEffect hook, will the hook be referencing stale filter state from the context? If so, why? Since the effect is rerun whenever the button is clicked, and all the state updates are done via a reducer, does the usual danger of referencing stale variables in a closure still apply?
Any guidance appreciated!
Note: I have thought about using useRef to prevent this issue, or perhaps devising some custom async middleware to fetch data on certain dispatches, but this is the solution I currently have.
I am not an expert but I would like to provide my takes. According to my understanding of how Context works, you will not get stale filter data with the current implementation. useReducer updates the state with a new object which will trigger Table to be re-render.
Also, Table component doesn't really care about filter data unless lastFetched is changed by a click event. If lastFetched is changed, all the Consumer of TableContext will be re-render again. You should not get stale filter data either.
I have a React Component with a toggle on it (on or off). The on / off state is handled by the components own state (this.state).
But I want that state to remain when the user goes from one page to the next. For instance they are on home.html and then when user clicks to another page like about.html.
Also this is not a single page app. Do I want Redux or Mobox or some other state management tool? Suggestions are welcomed.
But I want that state to remain when the user goes from one page to the next.
As has been said in comments probably the most straight-forward way is to just store the state to localstorage and retrieve it when the component mounts.
class Toggle extends Component {
componentDidMount() {
const storedValue = localStorage.getItem("my_value");
if (storedValue) {
this.setState({ value: storedValue });
}
}
handleChange = e => {
const value = e.target.value;
this.setState({ value });
localStorage.setItem("my_value", value);
}
render() {
return ...
}
}
Also this is not a single page app. Do I want Redux or Mobox or some other state management tool? Suggestions are welcomed.
No, Redux and Mobx aren't necessary, they are state containers that have ways to persist to localstorage (for example redux-localstorage and mobx-localstorage), but the key is just persisting to localstorage.
If you are not moving pages (whole page refresh) and only using different components then you can simply define a state in parent component and pass it in the child components along with a function that would toggle the state.
Function would look like this:
ToggleState = newState => this.setState({ myState : newState });
Pass this function as prop to child component.
Use it in child component as
This.props.toggle(newState);
**but if it is across multiple pages the you can go for localstorage **
Hope this resolves your issue.
I am fairly new to vue and trying to figure out the best way to structure my event bus. I have a main layout view (Main.vue) inside of which I have a router view that I am passing a child's emitted info to like so:
<template>
<layout>
<router-view :updatedlist="mainupdate" />
</layout>
</template>
import { EventBus } from './event-bus.js'
export default {
data () {
return {
mainupdate: []
}
},
mounted () {
EventBus.$on('listupdated', item => {
this.mainupdate = item
console.log(item)
})
}
}
The structure looks like: Main.vue contains Hello.vue which calls axios data that it feeds to child components Barchart.vue, Piechart.vue, and Datatable.vue
The axios call in Hello.vue populates a data property called list. I then check if updatedlist (passed as router prop from Datatable.vue to Main.vue when something changes) is empty, and if so set it to the value of list
I know that the event is being emitted and received by Main.vue because the console.log(item) shows the data. But my child components are not getting updated, even though they are using updatedlist as their data source. (If I reload the page, they will be updated btw, but why aren't they reactive?)
UPDATE: I thought I would cut out the top level parent Main.vue and just put my EventBus$on inside Hello.vue instead and then just change list when it was received. But this does two things, neither of which are great:
On every page refresh, my console logs add one more log to the list and output all of them. So for example if I have made 3 changes, there will be three logs, 4 there will be 4, etc. The list grows until I restart the app.
My child components are STILL not updated :(
UPDATE 2: UGH. I think I see why they aren't updating, the problem is with the reactivity in my chartsjs component, so I will have to resolve that there (Barchart.vue, Piechart.vue). A simple component I built myself to just read the total length DOES get updated, so that works. This still leaves the mystery of the massive number of duplicate console logs though, any ideas?
It sounds like you may have the answer to the original question? To answer your last update you likely have duplicate logs because you do not appear to be removing event handlers. When you add an event handler to a bus like this:
mounted () {
EventBus.$on('listupdated', item => {
this.mainupdate = item
console.log(item)
})
}
}
You need to remove it yourself. Otherwise you are just adding a new handler every time the component is mounted.
I suggest you move the actual handler into a method:
methods: {
onListUpdated(item){
this.mainupdate = item
console.log(item)
}
}
Move code to add the handler to created:
created() {
EventBus.$on('listupdated', this.onListUpdate)
}
}
And add a beforeDestroy handler:
beforeDestroy(){
EventBus.$off("listupdated", this.onListUpdate)
}
And do that in every component that you are adding event handlers to EventBus.
I' not aware if you require to stick to the current structure. For me the following works perfectly fine.
// whereever you bootstrap your vue.js
// e.g.
window.Vue = require('vue');
// add a central event bus
Vue.prototype.$bus = new Vue();
Now you can simply access the event bus in every component by using
this.$bus.$on( ... )
As said I'm not aware of your full event bus code but this should actually work fine