created a state using redux-toolkit but right after i refresh page or go to another one the state resets. i've been broking my head for 4 hours, can't stand it anymore.
there is code on github gists
Also here it is:
index.ts file: configuration of redux store
import { configureStore } from "#reduxjs/toolkit";
import authStateSlice from "redux/authStateSlice";
import deviceTypeReducer from "redux/deviceTypeSlice";
import userDataSlice from "redux/userDataSlice";
const store = configureStore({
reducer: {
isMobile: deviceTypeReducer,
isLogin: authStateSlice,
userData: userDataSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
signin.tsx: there i fetch data from a server and put it in my redux state:
import React, { useEffect, useState } from "react";
import { useAppSelector } from "lib";
import { useAppDispatch } from "../../lib/useAppDispatch";
import { userActionState } from "redux/userDataSlice";
export const SignIn: React.FC = () => {
const { isUser } = useAppSelector((state) => state.userData);
console.log("IS USER: ", isUser);
const dispatch = useAppDispatch();
const loginHandler = async () => {
try {
const response = await fetch(
"...link to api",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
}
);
if (!response.ok) {
const err = await response.json();
throw new Error(err)
}
const data = await response.json();
dispatch(userActionState({ isUser: true }));
} catch (error) {
throw new Error(err)
}
};
return (
<div>
...
</div>
);
};
userDataSlice.tsx: my user's data slice:
import { createSlice } from "#reduxjs/toolkit";
import { ResponseLoginDataType } from "#types";
const initialState: {
isUser: boolean | undefined;
} = {
isUser: undefined,
};
const userDataSlice = createSlice({
name: "isUser",
initialState,
reducers: {
userActionState(
state,
action: {
payload: {
isUser: boolean | undefined;
};
}
) {
state.isUser = action.payload.isUser;
},
},
});
export default userDataSlice.reducer;
export const { userActionState } = userDataSlice.actions;
userInfo.tsx: there i tried to get that state, but it gives my default one:
import React, { useEffect, useState } from "react";
import { useAppSelector } from "lib";
export const UserInfo: React.FC<UserInfo> = ({
name,
nickname,
picture,
activity,
active,
}) => {
const { isUser } = useAppSelector((state) => state.userData);
console.log("USER:: ", isUser);
return (
<>
.....
</>
);
};
Your Redux state doesn't persist to any non-volatile storage, such as local storage or cookies. redux-persist could be an option for you to keep your application's state.
This blog post might help: https://blog.logrocket.com/persist-state-redux-persist-redux-toolkit-react/
When you refresh or leave page, you are restarting the redux store so it will always go back to the default. If you want to persist the store you will need to use a tool like redux-persist. Here is how you could setup your store with redux-persist.
One thing that is annoying is making sure your redux store persists nested objects, secondly since you are in typescript don't try to persist the root reducer.
import storage from "redux-persist/lib/storage";
import autoMergeLevel2 from "redux-persist/lib/stateReconciler/autoMergeLevel2";
import persistReducer from "redux-persist/es/persistReducer";
import persistStore from "redux-persist/es/persistStore";
const persistConfigOne = {
key: "ex1",
storage,
stateReconciler: autoMergeLevel1, // Shallow level persisted objects
};
const persistConfigTwo = {
key: "ex2",
storage,
stateReconciler: autoMergeLevel2, // This will persist deeper nested objects if needed.
};
//Can't get state typings if persisting root reducer. Persisted by reducer works.
const reducers = combineReducers({
ex1: persistReducer<Ex1State, any>(persistConfigOne, ex1Reducer),
ex2: persistReducer<Ex2State, any>(persistConfigTwo, ex2Reducer),
});
export const store = configureStore({
reducer: reducers,
devTools: process.env.NODE_ENV !== "production", // May want to add this for Redux Dev Tools
middleware: [thunk], // Probably will need to use thunk at some point
});
Related
I tried saving my cart items to local storage but only few of the item gets saved. If i add 2 items, 1 gets saved, if i add 3 items, 2 gets saved, if i add 4, 3 gets saved, and so on.
Here is my code to save them.
const [state, dispatch] = useReducer(StoreReducer, {
cart:
JSON.parse(localStorage.getItem("cart"))
|| [],
});
const addToCart = (product) => {
dispatch({
type: "ADD_TO_CART",
payload: product,
});
localStorage.setItem("cart", JSON.stringify(state.cart));
};
Here is an image describing what is looks like
Here is an image that describes what the application looks like and the local storage:
This function has a major flaw. React doesn't work synchronously.
const addToCart = (product) => {
// this change will be made before next render
dispatch({
type: "ADD_TO_CART",
payload: product,
});
// that's why this `state.cart` is not updated yet
localStorage.setItem("cart", JSON.stringify(state.cart));
};
Try running
localStorage.setItem("cart", JSON.stringify(state.cart));
Inside the reducer
I'd recommend using redux-persist
I'll give a simple example. Please tweak it according to your needs.
We basically have a localstorage.js file to save and read state from local storage.
File: localStorage.js
export const loadState = () => {
try {
const serialState = localStorage.getItem('cart');
if (serialState === null) {
return undefined;
}
return JSON.parse(serialState);
} catch (err) {
return undefined;
}
};
export const saveState = (state) => {
try {
const serialState = JSON.stringify(state);
localStorage.setItem('cart', serialState);
} catch(err) {
console.log(err);
}
};
File: reducer.js
import {
addToCart
} from './actions';
function reducer(state, action) {
if (action.type === addToCart) {
// example
return {
...state,
cart: [...state.cart, action.payload]
}
}
return state;
}
export default reducer;
We read data from localstorage.js by loadState method and put its value in the persistedState constant.
File: store.js
import reducer from './reducer';
import { createStore } from 'redux';
import { loadState } from './localStorage';
const persistedState = loadState();
const initialStore={
/* state of your app */
cart: [],
persistedState
}
export const store=createStore(reducer, persistedState);
In our App.js, we subscribe state changes to store it to web browser's local storage.
File: App.js
import React from "react";
import { Provider } from 'react-redux';
import { store } from './store';
import { saveState } from './localStorage';
store.subscribe(() => {
saveState({
cart:store.getState().cart
});
});
function App() {
return (
<Provider store={store}>
<YourComponent />
</Provider>
);
}
export default App;
That's it. With this approach, there's no need to manually modify your local storage item.
This is a commercial project, so I can't show everything. The login is made and when I log in, I get a token and user data, but as soon as I refresh the page or go back to the profile, the data disappears, this is due to the refresh of the token, which works fine on the main page in the console, but it does not work in the profile , the data must be overwritten via redux. I will show parts of the code associated with the token.
action accessToken
import actionTypes from "./actionTypes";
export const setToken = (token) => {
return (dispatch) => {
dispatch({ type: actionTypes.setToken, payload: token });
};
};
export const resetToken = () => {
return (dispatch) => {
dispatch({ type: actionTypes.resetToken});
};
};
export default { setToken, resetToken };
reducersAccessToken
import actionTypes from "../actions/actionTypes";
const initialState = null;
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.setToken:
return action.payload;
case actionTypes.resetToken:
return action.payload;
default:
return state;
}
};
export default reducer;
mainPage when going on get refreshToken
const dispatch = useDispatch();
const { setToken } = bindActionCreators(accessTokenActions, dispatch);
const { setCurrentUser } = bindActionCreators(currentUserActions, dispatch);
const axiosPrivate = useAxiosPrivate();
useEffect(() => {
const checkAuth = async () => {
const response = await axiosPrivate.post(`${baseUrl}/api/auth/refresh`);
if (response.data.status === 200) {
setToken(response.data.body.token);
setCurrentUser(response.data.body.user);
}
console.log(response)
};
checkAuth();
}, []);
I leave you some code example for persisting data:
in /store/reducers.js
import autoMergeLevel2 from 'redux persist/lib/stateReconciler/autoMergeLevel2'
import { persistReducer } from 'redux-persist'
//here import your main root reducer for example->
import app from '../containers/App/reducer'
const appPersistConfig = {
key: 'app',
storage,
whitelist: ['accessToken'],
stateReconciler: autoMergeLevel2
}
export default combineReducers({
app: persistReducer(appPersistConfig, app)
})
in /store/index.js
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import { persistStore, persistReducer } from 'redux-persist'
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'
import reducers from './reducers';
const persistConfig = {
key: 'root',
storage,
whitelist: ['none'],
stateReconciler: autoMergeLevel2
}
const sagaMiddleware = createSagaMiddleware();
const middleware = [
...getDefaultMiddleware({
thunk: false,
serializableCheck: false,
immutableCheck: false
}),
sagaMiddleware,
];
const persistedReducer = persistReducer(persistConfig, reducers)
const store = configureStore({
reducer: persistedReducer,
middleware
});
export const persistor = persistStore(store)
export default store
This is just a default example of persist store, customize it with everything you need.
Check https://github.com/rt2zz/redux-persist here and be careful using persist
Remember that you need to install those libraries imported at the top of the file to make it works
This is the first time I am working with redux saga. I have a backend route called https://localhost:5000/developers/signup. I have a signup form:
import { FormEvent, useState } from 'react';
import HttpService from 'services/Http';
export default function Signup() {
const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', password: '' });
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
const httpService = new HttpService('api/developers/signup');
const res = await httpService.create(formData);
// I receive the user data + JWT token
console.log(res);
} catch (err) {
console.log(err);
}
};
return (
<main>
<h1>Signup</h1>
<form onSubmit={handleSubmit}>
// Some JSX to show the form
</form>
</main>
);
}
Store:
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import authReducer from './ducks/auth';
import rootSaga from './sagas/root';
const reducers = combineReducers({
auth: authReducer,
});
const sagas = createSagaMiddleware();
const composeSetup =
/*#ts-ignore eslint-disable */
process.env.NODE_ENV !== 'production' && typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? /*#ts-ignore eslint-disable */
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
/*eslint-enable */
const middleWare = [sagas];
const store = createStore(reducers, composeSetup(applyMiddleware(...middleWare)));
sagas.run(rootSaga);
export default store;
I am unable to understand what redux saga does, It would be great if someone could explain this. I've seen a lot of posts and youtube video. I looked at the docs but then it did not have a basic AJAX example. I would like to have a redux state structure like this:
{
auth: {
// Some auth data like the token & user details
}
}
Also, I am using functional components, so It would be great if your solution is compatible with that. Looking forward to talking, thanks in advance!
you should create action to send data to the server
and use the action in your function
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
dispatch(sendDataForSignup(e))
};
On submit fire an action with form data
import { signUp } from "actions";
const handleSubmit = async (e) => {
e.preventDefault();
signUp(formData);
};
which will forward the type and payload to watcher
export const signUp = (payload) => ({
type: "SIGNUP_REQUEST",
payload
});
the watcher will take the latest action and pass it to worker function that will make the api call and dispatch an action with response type and data
import { put, takeLatest, all } from "redux-saga/effects";
function* signupWorker() {
const json = yield fetch("URL/signup").then((response) => response.json());
yield put({ type: "SIGNUP_SUCCESS", json: json.data });
}
function* signupWatcher() {
yield takeLatest("SIGNUP_REQUEST", signupWorker);
}
export default function* rootSaga() {
yield all([signupWatcher()]);
}
and the reducer will handle the data as you like based on the types
const reducer = (state = {}, action) => {
switch (action.type) {
case "SIGNUP_REQUEST":
return { ...state, loading: true };
case "SIGNUP_SUCCESS":
return { ...state, ...action.payload, loading: false };
default:
return state;
}
};
I have a problem when load my data in redux, reducer lost my data when call in my file, please help me.
my main src/index.tsx
import React from 'react'
import {Provider} from 'react-redux'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/stores'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
my file get data CampaingHeader
export class CampaingHeader {
private token: any
private resp_campaing_header: any
constructor(token: any){
this.token = token
}
/*
get last campaing for the current user
*/
getLastCampaingHeader = async() => {
this.resp_campaing_header = await API.get(`campaing-header-last`,{
headers: {Authorization: `Bearer ${this.token}`}
})
return this.resp_campaing_header
}
}
my files in redux
the types
/redux/types/campaing.types.tsx
export const SET_CAMPAING = 'SET_CAMPAING'
export const LOADING_CAMPAING = 'LOADING_CAMPAING'
export const SET_DEFAULT_CAMPAING = 'SET_DEFAULT_CAMPAING'
export const SET_ERRORS = 'SET_ERRORS'
the reducer
/redux/reducers/campaing.reducers.tsx
my problem is this file
import {
SET_DEFAULT_CAMPAING,
SET_CAMPAING,
SET_ERRORS
} from '../types/campaing.types'
const InitCampaingState = {
answer: false,
campaing: {},
errors: {}
}
export default function(state = InitCampaingState, action: any) {
// HERE LOAD MY DATA
console.info(action.payload)
switch (action.type) {
// BUT HERE NOT LOADED
case SET_CAMPAING:
return {
...state,
answer: true,
campaing: action.payload // ALWAYS payload is undefined
}
default:
return state // AND RETURN STATE
}
}
my file actions
/redux/actions/campaing.actions.tsx
import {
SET_CAMPAING,
SET_ERRORS,
} from '../types/campaing.types'
import {CampaingBody} from '../../userCampaings'
let token = window.sessionStorage.getItem('token')
let CampHeader = new CampaingBody(token)
export const RetrieveCampaing = (campaing_id: number) => (dispatch: any) => {
CampHeader.getRetrieveCBody(campaing_id)
.then(resp =>{
console.info(resp.data.data)// DATA IS LOADED HERE
dispatch({
type: SET_CAMPAING,
payload: resp.data.data,// HERE LOADED DATA TOO
})
}).catch(err =>{
dispatch({
type: SET_ERRORS,
errors: err
})
})
}
file when get the ID of campaing, the code below working well, pass the ID campaing to my code above in /redux/actions/campaing.actions.tsx
import React from 'react'
import {connect} from 'react-redux'
// call my actions
import {RetrieveCampaing} from '../../../../redux/actions/campaing.actions'
const UpdateCampaing: React.FC = (props: any) => {
React.useEffect(()=>{
// HERE PASS DATA CORRECTLY
let ID = GetCampID()
props.RetrieveCampaing(ID)
},[])
return(....)
}
const mapStateToProps = (state: any) => ({
campaing: state.campaing
})
const mapActionToProps = {
RetrieveCampaing
}
export default connect(mapStateToProps, mapActionToProps)(UpdateCampaing)
and my problem is here when I try to share data from actions, using connect not working.
import React from 'react'
import {connect} from 'react-redux'
type FormData = {
profile: Iuser
first_name: string
last_name: string
email: string
cinit: string
}
interface Icampaing {
campaing: FormData
}
const Personal: React.FC<Icampaing> = ({campaing})=>{
React.useEffect(()=>{
//MY PROBLEM IS HERE NOT LOAD DATA
console.info('from redux')
console.info(campaing) // RETURN DATA InitCampaingState, from my type files
},[])
}
return(<div>{campaing.cinit}</div>)
}
const mapStateToProps = (state: any) =>({
campaing: state.campaing
})
export default connect(mapStateToProps)(Personal)
my problem is in the REDUCER file, because the data is loaded, but don't return the data.
please help me, I don't know where is my error, aparentlly all working well, I means, the load data into de actions file working, but when I call the action into de PERSONAL file not working
best words.
Your UseEffect with empty array [] works like componentDidMount, it is called only once.
You need to add campaing to array and your useEffect hook will be called on every campaing prop change:
const Personal: React.FC<Icampaing> = ({campaing})=>{
React.useEffect(()=>{
//MY PROBLEM IS HERE NOT LOAD DATA
console.info('from redux')
console.info(campaing) // RETURN DATA InitCampaingState, from my type files
},[campaing])
}
I have been following a guide to setup redux-thunk so I can fetch a users geolocation and then dispatch and update state. However, every time I attempt to dispatch the action with response data, it just sets the data to null.
When I attempt to simulate an API call with a timeout and set some random values, it works without a problem.
geoLocationActions.js
export function geoLocationActions() {
return dispatch => {
const geolocation = navigator.geolocation;
geolocation.getCurrentPosition((position) => {
console.log(position.coords);
dispatch({
type: 'FETCH_USER_LOCATION_SUCCESS',
payload: position
});
});
}
};
MapContainer.js
import React from "react";
import { geoLocationActions } from '../../../actions/geoLocationActions';
import { connect } from 'react-redux';
class MapContainer extends React.Component {
componentWillMount() {
this.props.geoLocationActions();
}
render() {
return (
<div>
<p>Fetching location...</p>
</div>
);
}
}
// update current geolocation state
const mapDispatchToProps = (dispatch) => {
return {
geoLocationActions: () => dispatch(geoLocationActions())
};
}
const mapStateToProps = (state) => {
return {
state: state
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MapContainer);
reducers.js
case 'FETCH_USER_LOCATION_SUCCESS':
return {
...state,
userLocation: action.payload
}
store.js
import { createStore, combineReducers, applyMiddleware, compose } from
'redux';
import reducer from '../reducers/reducers';
import reduxThunk from "redux-thunk";
const rootReducer = combineReducers({
state: reducer
});
export const store = createStore(
rootReducer,
compose(
applyMiddleware(reduxThunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
);
It turns an HTML5 Geoposition object. You need to convert it to a regular object that can be serialized with JSON.stringify.
You can use this method:
const geopositionToObject = geoposition => ({
timestamp: geoposition.timestamp,
coords: {
accuracy: geoposition.coords.accuracy,
latitude: geoposition.coords.latitude,
longitude: geoposition.coords.longitude
}
})
Update your geoLocationActions.js like this:
export function geoLocationActions() {
return dispatch => {
const geolocation = navigator.geolocation;
geolocation.getCurrentPosition((position) => {
const positionObj = geopositionToObject(position)
console.log(positionObj);
dispatch({
type: 'FETCH_USER_LOCATION_SUCCESS',
payload: positionObj
});
});
}
};
You can have a look at my repo to see the same code.