I am trying to write a simple todoList using vue.js and I want to save those todos into cookies before the vue instance is destroyed. But I find it weird that though I wrote callback in beforeDestory hook, the hook is never called.
I checked Vue documents and could not find any hint.
when I tried to
save those todos into cookies by adding callback to window.onbeforeunload and window.onunload, it works.
my code is like
computed: {
todos() {
return this.$store.getters.todos
},
...
},
beforeDestroy() {
myStorage.setTodos(this.todos)
}
todos is a array defined in store.js, which has been imported in main.js, like
import myStorage from '#/utils/storage'
...
const store = new Vuex.Store({
state: {
todos: myStorage.getTodos()
...
},
getters: {
todos: state => state.todos
}
and myStorage is defined as:
import Cookies from 'js-cookie'
const todoKey = 'todo'
const setTodos = (todos) => {
Cookies.set(todoKey, JSON.stringify(todos))
}
const getTodos = () => {
const todoString = Cookies.get(todoKey)
let result = []
if (todoString) {
const todoParsed = JSON.parse(todoString)
if (todoParsed instanceof Array) {
result = todoParsed
}
}
return result
}
export default {
setTodos: setTodos,
getTodos: getTodos
}
I am using vue 2.6.10, and my project is constructed by vue-cli3.
I develop this todolist using Chrome on Window 10.
I expect that after I close the window or after I refresh the window, the todolist can still fetch todo written previously from cookies. But the fact is that the beforeDestory hook is never called.
When you refresh the window, the component's beforeDestroy() is not called, because you are not programmatically destroying the component, but ending the entire browser session.
A better solution would simply to call myStorage.setTodos whenever the todos object in the component is mutated. You can do that by setting up a watcher for the computed property:
computed: {
todos() {
return this.$store.getters.todos
},
},
watch: {
todos() {
myStorage.setTodos(this.todos)
}
}
Altertively, you let the VueX store handle the storage. It is unclear from your question if you are mutating the todos state: if you are mutating it, you can also do myStorage.setTodos in the store. The actual component can be dumb in that sense, so that all it needs to do is to update the store.
Related
In my store module /store/template.js I have:
const templateConfig = {
branding: {
button: {
secondary: {
background_color: '#603314',
background_image: ''
}
}
}
}
export const state = () => ({
branding: {},
...
})
export const actions = {
initializeStore (state) {
state.branding = templateConfig.branding
}
}
(initializeStore() is called when app initially loads)
I want to retrieve the branding the branding object in my component:
computed: {
...mapState({
branding: state => state.template.branding
})
}
But when trying to console.log() branding I see this:
Why don't I simply see the branding object? (and what on earth is this?)
You need to always use a mutation to change state. You can call one from your action:
export const mutations = {
SET_BRANDING(state, payload) {
state.branding = payload;
}
}
export const actions = {
initializeStore ({ commit }) {
commit('SET_BRANDING', templateConfig.branding);
}
}
What you're seeing with the observer is normal, and indicates that the branding object has been successfully mapped and accessed.
What you see is Vue's observable object, which is how Vue implements reactivity. Without this, there would be no reactivity, and you will see such a wrapper on all top-level reactive objects. You can pretend it's not there.
Vue in fact applies this same "wrapper" to the data object internally to make it observable:
Internally, Vue uses this on the object returned by the data function.
You won't see it on other reactive properties, but if they're reactive, they belong to some parent observable object.
You need to import { mapState, mapActions } from 'vuex' (already done I guess).
And then, you can write this
...mapState(['branding']) // or ...mapState('#namespacedModule', ['branding'])
Still, why do you not simply put the state directly (with your background_color) rather than going through a Vuex action ?
If you want to keep it this way, do not forget to await this.initializeStore() in your component before trying to access the state.
According to this and that question the spread operator seems to be used for updating an object managed in a useState hook.
I created a super simple example and found out, that even when the content of the object does not change, a re-render is triggered (which is clear, because the object changed):
import React from "react";
function useFriendStatus() {
const [person, setPersonProps] = React.useState({name:'Mark',age:23});
React.useEffect(() => {
console.log("rerender");
const interval = setInterval(() => {
setPersonProps({...person}); //simply set the object again -> no content changed
console.log('update');
}, 1000);
return () => clearInterval(interval);
}, [person]);
return person;
}
export default function App() {
const person = useFriendStatus();
return <div className="App">Hello World: {"" + person.name}</div>;
}
Here you see a screenshot from my profiler which shows that a re-rendering seems to be fired (even if the displayed name did not change):
I am wondering if this is a "good practice" as EVERYTHING seems to be re-rendered. Sometimes you get deeply nested objects from an API and breaking them down to super-simple non-object userState hooks is not possible.
Wouldn't it be better to Stringify everything?
import React from "react";
function useFriendStatus() {
const [person, setPersonProps] = React.useState(JSON.stringify({name:'Mark',age:23}));
React.useEffect(() => {
console.log("rerender");
const interval = setInterval(() => {
const personCopy=JSON.parse(person);
setPersonProps(JSON.stringify({...personCopy}));
console.log('update');
}, 1000);
return () => clearInterval(interval);
}, [person]);
return person;
}
export default function App() {
const person = JSON.parse(useFriendStatus());
return <div className="App">Hello World: {"" + person.name}</div>;
}
How do you handle that in practice?
I created a super simple example and found out, that even when the content of the object does not change, a re-render is triggered (which is clear, because the object changed)
It has nothing to do with the "content" of the object, your component re-renderers because you create a shallow copy {...person} of an object (changing its reference).
On render phase React makes a shallow comparison with the previous state in order to decide if render will occur, and in javascript, {} === {} is always false.
the spread operator seems to be used for updating an object managed in a useState hook.
As the state should be treated as immutable it is common to use the spread operator to make a shallow copy.
"It should only re-render if the name property of the object changes"
It is common to just add a condition before calling setState:
React.useEffect(() => {
const newPerson = { ...person }; // fetch from some source
// or check if person.name !== newPerson.name
if (!isEqual(person, newPerson)) {
setPerson(newPerson);
}
}, [person]);
I'm building a Movie website to practice on VueJS. During app initialization, I get a list of movie genres from 3rd-party API. Since this list is needed in several components of the app, I manage and store it via Vuex, like so:
main.js:
new Vue({
router,
store,
vuetify,
render: h => h(App),
created () {
this.$store.dispatch('getGenreList')
}
}).$mount('#app')
Vuex's index.js:
export default new Vuex.Store({
state: {
genres: []
},
mutations: {
setGenreList (state, payload) {
state.genres = payload
}
},
actions: {
async getGenreList ({ commit }) {
try {
const response = await api.getGenreList() // axios call defined in api.js
commit('setGenreList', response)
} catch (error) {
console.log(error)
}
}
}
})
Now, in my Home view, I want to retrieve a list of movies for each genres, something like this:
Home.vue:
<script>
import { mapState } from 'vuex'
import api from '../api/api'
export default {
name: 'home',
data () {
return {
movies: null
}
},
computed: {
...mapState({
sections: state => state.genres
})
},
async mounted () {
const moviesArray = await Promise.all(
this.sections.map(section => {
return api.getMoviesByGenre(section.id)
})
)
this.movies = moviesArray
}
}
</script>
The issue here is that, on initial load, sections===[] since genres list hasn't been loaded yet. If I navigate to another view and come back, sections holds an array of genres objects as expected.
Question: How can I properly wait on sections to be loaded with genres? (since the getGenreList action isn't called from that component, I can't use this method)
I was thinking in implementing the movie list retrieval in a Watcher on sections instead of in mounted() but not sure if it's the right approach.
Yep, it is right approach, that's what watchers are for.
But if you only can... try to do actions like this one inside one component family. (parent passing props to children, controlling it);
You can read this article, about vuex - https://markus.oberlehner.net/blog/should-i-store-this-data-in-vuex/.
It will maybe clarify this idea. Just simply don't store in vuex everything, cause sometimes it' does not make sense
https://v2.vuejs.org/v2/api/#watch - for this one preferably you should use immedaite flag on watcher and delete mounted. Watcher with immedaite flag is kinda, watcher + created at once
The title is wordy, however a short / simple example will go a long ways in explaining my question. I have the following start to a component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGames } from '../../path-to-action';
class TeamsApp extends Component {
constructor(props) {
super(props);
this.state = {
oldGames: [],
newGames: []
};
}
componentDidMount() {
this.props.dispatch(fetchGames('1617'));
this.setState({ oldGames: this.props.teamGameData });
this.props.dispatch(fetchGames('1718'));
this.setState({ newGames: this.props.teamGameData });
}
...
...
}
function mapStateToProps(reduxState) {
return {
teamGameData: reduxState.GamesReducer.sportsData
};
}
export default connect(mapStateToProps)(TeamsApp);
I would like the action / reducer that corresponds with fetchGames() and gamesReducer to be called twice when the component mounts. This action / reducer grabs some sports data, and I am trying to grab data for two separate seasons (the '1617' season and the '1718' season). The fetchGames() is built correctly to handle the season parameter.
With the current setup, the states aren't being set, and my linter is throwing an error Do not use setState in componentDidMount.
Can I pass a callback to this.props.dispatch that takes the results of the fetchGames() (the teamGameData prop), and sets the oldGames / newGames states equal to this object?
Any help with this is appreciated!
Edit: if i simply remove the this.setState()'s, then my teamGameData prop simply gets overridden with the second this.props.dispatch() call...
Edit 2: I'm not 100% sure at all if having the 2 state variables (oldGames, newGames) is the best approach. I just need to call this.props.dispatch(fetchGames('seasonid')) twice when the component loads, and have the results as two separate objects that the rest of the component can use.
Edit 3: I have the following part of my action:
export const fetchSportsDataSuccess = (sportsData, season) => ({
type: FETCH_NBA_TEAM_GAME_SUCCESS,
payload: { sportsData, season }
});
and the following case in my reducer:
case FETCH_NBA_TEAM_GAME_SUCCESS:
console.log('payload', action.payload);
return {
...state,
loading: false,
sportsData: action.payload.sportsData
};
and the console.log() looks like this now:
payload
{ sportsData: Array(2624), season: "1718" }
but i am not sure how to use the season ID to create a key in the return with this season's data....
Edit 4: found solution to edit 3 - Use a variable as an object key in reducer - thanks all for help on this, should be able to take it from here!
Copying data from the redux store to one's component state is an anti-pattern
Instead, you should modify your redux store, for example using an object to store data, so you'll be able to store datas for multiples seasons :
sportsData: {
'1617': { ... },
'1718': { ... },
}
This way you'll be able to fetch both seasons in the same time :
componentDidMount() {
const seasons = ['1718', '1617'];
const promises = seasons.map(fetchGames);
Promise.all(promises).catch(…);
}
And connect them both :
// you can use props here too
const mapStateToProps = (reduxState, props) => ({
// hardcoded like you did
oldGames: reduxState.GamesReducer.sportsData['1617'],
// or using some props value, why not
newGames: reduxState.GamesReducer.sportsData[props.newSeason],
};
Or connect the store as usual and go for the keys:
const mapStateToProps = (reduxState, props) => ({
games: reduxState.GamesReducer.sportsData,
};
…
render() {
const oldGame = this.props.games[1718];
const newGame = this.props.games[1718];
…
}
Redux is you single source of truth, always find a way to put everything you need in Redux instead of copying data in components
I searched a bit about this question but found very vague answers. In redux, we know that the state is stored as an object. But where is this state stored actually? Is it somehow saved as a file which can be accessed by us later on? What I know is that it does not store it in a cookie format or in the browser's local storage.
The state in Redux is stored in memory, in the Redux store.
This means that, if you refresh the page, that state gets wiped out.
You can imagine that store looking something like this:
function createStore(reducer, initialState) {
let state = initialState // <-- state is just stored in a variable that lives in memory
function getState() {
return state
}
function dispatch(action) {
state = reducer(state, action) // <-- state gets updated using the returned value from the reducer
return action
}
return {
getState,
dispatch
}
}
The state in redux is just a variable that persists in memory because it is referenced (via closure) by all redux functions.
Here's a simplified example of what is going on:
function example() {
let variableAvailableViaClosure = 0
function incrementTheClosureVariable() {
variableAvailableViaClosure += 1
}
function getTheClosureVariable() {
return variableAvailableViaClosure
}
return {
incrementTheClosureVariable,
getTheClosureVariable
}
}
let data = example()
// at this point example is finished
// but the functions it returned
// still have access to the (internal) variable via closure
console.log(
data.getTheClosureVariable() // 0
)
data.incrementTheClosureVariable()
console.log(
data.getTheClosureVariable() // 1
)
Furthermore, the statement
In redux, we know that the state is stored as an object.
isn't correct. State in redux can be any valid javascript value, not just an object. It just usually makes the most sense for it to be an object (or a special object like an array) because that allows for a more flexible data structure (but you could make the state just be a number for example, if you wanted to).
Check out the actual Redux implementation for more details.
If you want the state to persist in a cookie or localStorage, you would enhance the store such that, on top of updating the state in memory, it will save to your desired storage as well (and load from that storage when the store is initialized)
States are stored in redux-store. Redux Store is a global store which can be accessed anywhere/any components.
Let consider an example of getting Index of data using third party API. The following snippet uses componentWillMount which will trigger a fetch call using redux action.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchDataFromUrl } from '../actions/index.js';
class Indexdata extends Component {
constructor(props){
super(props);
this.state = {
text: ''
}
}
componentWillMount(){
let thisVal = this;
thisVal.props.fetchIndexofData()
}
componentWillReceiveProps(nextProps){
this.setstate({
text: nextProps.indexData.text
})
}
render(){
return(
<div>
<Navbar />
<h2 className="prescription-index-title">Index of Data</h2>
</div>
)
}
}
function mapStateToProps(state){
return{
indexData: state.fetchedData
}
}
function mapDisptachToProps(dispatch){
return {
fetchIndexofData: () => dispatch(fetchDataFromUrl(access_token))
};
};
export default connect(mapStateToProps, mapDisptachToProps)(IndexData);
The above snippet will fetch index of data using a redux action. The below code is a redux action,
export function fetchDataFromUrl(){
return(dispatch) => {
const base_url = "https://api_serving_url.com"
fetch(base_url, {
method: 'GET'
})
.then(response => response.json())
.then(data => {
dispatch({
type: "INDEX_DATA",
data: data
})
})
}
}
Redux action will dispatch data to reducer, where state will be initialized in redux store. The following code snippet is redux-reducer
export function fetchedData(state = [], action) {
switch(action.type) {
case "INDEX_DATA":
return action.data;
default:
return state;
}
}
State stored in redux store will be mapped using function mapStateToProps, implemented in the above component. Now you can access the state using props in the respective component. Lifecyclehook componentWillReceiveProps will be able to fetch the state stored redux store.
You can access the State by means of using store.getState() in any component.The only drawback of using reducer state, is that it will reset the state when you refresh the component/application. Go through Reducer Store , for more information.