I am having an issue accessing methods of an object retrieved from state. What would be the correct way of storing an object, along with all of its methods in state so that they may be accessed later.
Store (store.jsx)
import create from "zustand";
import { persist } from "zustand/middleware";
import { cloneDeep } from "lodash";
const initialState = {
user: undefined,
profile: undefined,
}
let store = (set) => ({
...initialState,
setUser: (user) => set((state) => ({ user: cloneDeep(user) })),
setProfile: (profile) => set((state) => ({ profile: profile })),
logout: () => {set(initialState)},
});
store = persist(store);
export const useUserStore = create(store);
Setting State (file2.jsx)
import { useUserStore } from "../../store";
const setUser = useUserStore((state) => state.setUser);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
//all properties and methods defined here
setUser(user);
return () => unsubscribe();
}, []);
Accessing stored state (file2.jsx)
import { useUserStore } from "../../../store/userStore";
const user = useUserStore((state) => state.user);
//usage, no methods defined at this point.
user.updateEmail() <--- method is not defined
Workaround / Correct Implementation??
Access currentUser in auth instance.
Related
I'm getting the ID of a user based on the ID I pass in my route params. When I first load the page and access one of the users, the getter displays the ID from the route param accordingly, however once I go back, and click on another user, the ID from the param does not match with the getter. Instead the getter shows the ID of the previously accessed user. Can anyone kindly suggest a solution for this?
setup() {
const store = vuexStore;
const adminId = router.currentRoute.params.adminId;
console.log("ID param:", adminId);
getSelectedAdmin();
const selectedAdmin = computed(() => store.getters.getSelectedAdmin);
console.log("getter Id:", selectedAdmin.value.id);
function getSelectedAdmin() {
return store.dispatch(GET_ADMIN_BY_ID, adminId)
}
return {
selectedAdmin,
}
}
You should fetch the user in the onMounted hook.
(I use the script setup, vue-router#next & vuex#next)
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from 'vuex'
const store = useStore()
const route = useRoute()
const selectedAdmin = computed(() => store.getters['getSelectedAdmin'])
onMounted(async () => { await store.dispatch('GET_ADMIN_BY_ID', route.params.adminId) })
</script>
<template>
<div v-if="selectedAdmin">
{{ selectedAdmin.username }}
</div>
<div v-else>Loading ...</div>
</template>
In your store :
Set initial value for selectedAdmin to null
In the GET_ADMIN_BY_ID action, reset selectedAdmin to null before updating
const state = {
currentAdmin: null,
}
const mutations = {
SET_ADMIN: (state, payload) => state.currentAdmin = payload
}
const getters = {
getSelectedAdmin: (state) => state.currentAdmin
}
const actions = {
GET_ADMIN_BY_ID: ({ commit }, id) => {
commit('SET_ADMIN', null)
return axios.get(`admin/${id}`)
.then((response) => { commit('SET_ADMIN', response.data) })
.catch((err) => { commit('SET_ADMIN', null) })
}
I'm working on a React project and I reuse a fetchAPIcall action since I make 3 different initial API calls, and further, I plan on using more to add and edit my Items.
So to have control over the correct order of the API call I tried using a flag at the end, being a state of the component.
And since I am using many API calls, to add some Items to favorites and be removed quickly by a like button, I'd like to know what is the best practice when using many async functions or API calls?
I can think of only 1) using flags, and 2) having the API call-actions separate for each, but in my case that would be a lot of code (get user)(get, add, delete FavList)(get Items)(add, edit remove Item).
By the way, the API is mine, made it in rails.
Here are the main code&files for my issue:
This is from my GetItemsNFavlist Component, this is where I load all the info of items and favList items. I made it into a component that I call because I thought it was a good idea so when I add an Item to the Favorites List I can just call this component to update my FavoritesList (but that 'updating' part isn't working great just yet, I'm having to go back to the User and again to the Fav List to see the update or even logout and in again to see the change).
Here I call the action "fetchAPIcall" and I check the status and response data with the "fetchCall" store object. Also here I do 2 API calls, 1) to get all the Items and 2) to get the FavoritesList for the User:
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import * as MyActions from '../actions';
const GetItemsNFavlist = props => {
const {
actions, items, fetchCall, favList, user,
} = props;
const [apiFlag, setApiFlag] = useState({ itm: false, fvl: false });
const itemsUrl = 'https://findmyitem-api.herokuapp.com/items';
const favListUrl = `https://findmyitem-api.herokuapp.com/users/${user.id}/favorites_lists`;
useEffect(() => { // #1
if (!apiFlag.itm && !apiFlag.fvl) actions.fetchAPIcall(itemsUrl, 'get', {});
}, []);
useEffect(() => {
if (!fetchCall.apiData && items[0]) {
actions.fetchAPIcall(favListUrl, 'get', {});
setApiFlag({ itm: true, fvl: false });
}
}, [items]);
useEffect(() => {
if (fetchCall.apiData && !items[0] && !favList[0]) {
actions.setItems(fetchCall.apiData);
actions.fetchAPIreset();
}
if (apiFlag.itm && fetchCall.apiData && !favList[0]) actions.setFavList(fetchCall.apiData);
});
useEffect(() => {
if (favList[0]) {
actions.fetchAPIreset();
setApiFlag({ itm: true, fvl: true });
}
}, [favList]);
return (<> </>);
};
GetItemsNFavlist.propTypes = {
user: PropTypes.objectOf(PropTypes.any).isRequired,
actions: PropTypes.objectOf(PropTypes.any).isRequired,
items: PropTypes.arrayOf(PropTypes.any).isRequired,
favList: PropTypes.arrayOf(PropTypes.any).isRequired,
fetchCall: PropTypes.objectOf(PropTypes.any).isRequired,
};
const mapStateToProps = ({
user, items, fetchCall, favList,
}) => ({
user, items, fetchCall, favList,
});
function mapActionsToProps(dispatch) {
return {
actions: bindActionCreators({ ...MyActions }, dispatch),
};
}
export default connect(mapStateToProps, mapActionsToProps)(GetItemsNFavlist);
And these are my actions (actions/index.js), where I have the API call function:
import axios from 'axios';
const addUsername = username => ({
type: 'SET_NAME',
username,
});
const setUserInfo = user => ({
type: 'SET_USER',
user,
});
const setItems = items => ({
type: 'SET_ITEMS',
items,
});
const setFavList = favList => ({
type: 'SET_FAVLIST',
favList,
});
const fetchAPIbegin = callHeader => ({
type: 'FETCH_API_BEGIN',
callHeader,
});
const fetchAPIsuccess = payload => ({
type: 'FETCH_API_SUCCESS',
payload,
});
const fetchAPIfailure = error => ({
type: 'FETCH_API_FAILURE',
payload: error,
});
const fetchAPIsuccesResp = payload => ({
type: 'FETCH_API_SUCCESS_RESP',
payload,
});
function handleErrors(response) {
if (!response.ok && response.error) { throw Error(JSON.stringify(response)); }
return response;
}
function fetchAPIcall(url, restAct, options) {
return dispatch => {
dispatch(fetchAPIbegin(url, options));
setTimeout(() => axios[restAct](url, options)
.then(handleErrors)
.then(rsp => {
dispatch(fetchAPIsuccesResp(rsp));
return rsp;
})
.then(resp => resp.data)
.then(jsonResp => dispatch(fetchAPIsuccess(jsonResp)))
.catch(err => dispatch(fetchAPIfailure(`${err}`))), 1000);
};
}
const fetchAPIreset = () => ({ type: 'FETCH_API_RESET' });
export {
addUsername,
setUserInfo,
setItems,
setFavList,
fetchAPIcall,
fetchAPIbegin,
fetchAPIsuccess,
fetchAPIfailure,
fetchAPIreset,
fetchAPIsuccesResp,
};
And Just in case, this is the link to my repo: find-my-item repo.
Thanks in advance!!
Best regards
I'm creating a new app where I want to be able to post updates to my friends. A micro-blogging site.
I want to learn how to update the app using React hooks and React's context API. I created the following provider that takes the state as the value... I want to be able to add a new post and then update the state's posts so that I don't have to fetch the database again (using firestore) I'm really trying to save myself a call to the db...
Basically, when I call createNewPost within the state, I want to be able to update the current posts section of the state: state.posts but when I update the state after the API call is successful, my entire posts array gets replaced for some reason. Not sure what I might be doing wrong...
import { createContext, useState } from 'react';
import { createDoc, getWhere } from '../utils/database/db';
export const PostDataContext = createContext();
const SetDataContextProvider = ({ children }) => {
const [state, setState] = useState({
posts: [],
timelinePosts: [],
createNewPost: async (collection, payload) => {
const doc = await createDoc(collection, payload)
payload.id = doc?.doc?.id;
updateStatePosts(payload);
return doc;
},
getPostsByUserId: async (userId) => {
const dataReceived = await getWhere('/posts', userId, 'userId')
setState({ ...state, posts: dataReceived })
}
});
const updateStatePosts = (payload) => {
console.log('why is state posts empty?!', state);
setState({ ...state, posts: [payload, ...state.posts] })
}
return <PostDataContext.Provider value={state}>
{children}
</PostDataContext.Provider>
}
export default SetDataContextProvider;
If I had to guess I would say you have a stale enclosure of your initial empty posts state within the updateStatePosts function used in your state. You can use a functional state update to access the previous state to update from. Functional state updates allow you to update from the previous state, not the state the update was enqueued/enclosed in.
const SetDataContextProvider = ({ children }) => {
const [state, setState] = useState({
posts: [],
timelinePosts: [],
createNewPost: async (collection, payload) => {
const doc = await createDoc(collection, payload)
payload.id = doc?.doc?.id;
updateStatePosts(payload);
return doc;
},
getPostsByUserId: async (userId) => {
const dataReceived = await getWhere('/posts', userId, 'userId')
setState(prevState => ({
...prevState, // <-- preserve any previous state
posts: dataReceived
}))
}
});
const updateStatePosts = (payload) => {
setState(prevState => ({ // <-- previous state to this update
...prevState,
posts: [payload, ...prevState.posts],
}));
};
return <PostDataContext.Provider value={state}>
{children}
</PostDataContext.Provider>
}
This function is wrong:
const updateStatePosts = (payload) => {
console.log('why is state posts empty?!', state);
setState({ ...state, posts: [payload, ...state.posts] })
}
You're using the spread operator correctly for your state, but posts is directly replaced. You might want to use the following setState:
setState({ ...state, posts: [...payload,...state.posts] })
Despite that, you should also refactor your state. Functions are not states so put them outside your state.
I have the following function to check the users
export const authCheckState = () => {
return dispatch => {
const token = localStorage.getItem("token");
const email = localStorage.getItem("email");
if (token === undefined) {
dispatch(logout());
} else {
const expirationDate = new Date(localStorage.getItem("expirationDate"));
if (expirationDate <= new Date()) {
dispatch(logout());
} else {
dispatch(authSuccess(email, token));
dispatch(
checkAuthTimeout(
(expirationDate.getTime() - new Date().getTime()) / 1000
)
);
}
}
};
};
And for that i did the special initialization function that will take all the needed functions and dispatch them
initialState = {
initialized: false
};
const appReducer = (state = initialState, action) => {
switch (action.type) {
case INITIALIZED_SUCCESS:
return {
...state,
initialized: true
}
default:
return state;
}
}
export const initializedSuccess = () => ({type: INITIALIZED_SUCCESS});
export const initializeApp = () => (dispatch) => {
let promise = dispatch(authCheckState());
Promise.all([promise])
.then(() => {
dispatch(initializedSuccess());
});
}
And after that i am putting initialization function to the App's componentDidMount function
componentDidMount() {
this.props.initializeApp();
}
and after i am doing that
const mapStateToProps = (state) => ({
initialized: state.app.initialized
})
but after that i am still getting the data too late and i have to refresh the page to see the data. How to implement this feature?
You should fetch data in the component itself. Use Redux for storing the data so that you can use it later or manipulate it. (If you need to do so!)
This how the process should be,
Make an API request on componentDidMount.
Store the data in redux.
Use that data in the component coming in the form of props using Redux.
In my store.js i have the following code:
import { createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk'
const reducer = (state, action) => {
console.log(action.type)
if (action.type === 'LOAD_USERS') {
return {
...state,
users: action.users['users']
}
} else if (action.type === 'LOAD_CHATROOMS') {
return {
...state,
chatRooms: action.chatRooms['chatRooms']
}
}
return state;
}
export default createStore(reducer, {users:[], chatRooms:[]}, applyMiddleware(thunk));
the code inside the action.type === 'LOAD_CHATROOMS' is never accessed for some reason, this is the action file where i set the action type for the reducer:
import axios from 'axios'
axios.defaults.withCredentials = true
const loadUsers = () => {
return dispatch => {
return axios.get('http://localhost:3000/session/new.json')
.then(response => {
dispatch({
type: 'LOAD_USERS',
users: response.data
});
});
};
};
const logIn = user => {
return axios.post('http://localhost:3000/session', {
user_id: user.id
})
.then(response => {
//TODO do something more relevant
console.log('loged in');
});
};
const loadChatRooms = () => {
return dispatch => {
return axios.get('http://localhost:3000/session/new.json')
.then(response => {
dispatch({
type: 'LOAD_CHATROOMS',
chatRooms: response.data
});
});
};
};
const enterChatRoom = chatrom => {
};
export { loadUsers, logIn, enterChatRoom, loadChatRooms};
The 'Load methods' get the data that i use to populate both components (one for users list and the other one for chatrooms list ), both components are called at the same level in the app.js file.
Basically the output that i'm getting is the first component (users) as expected with the correct list, and the chatrooms component is also rendered but the data is not loaded (since it's corresponding reducer block is not accessed).
Thanks a lot for reading :)