bjoluc / next-redux-cookie-wrapper

Sync a subset of your Redux state with cookies in Next.js :cookie: :sparkles:
MIT License
114 stars 4 forks source link

Add `defaultState` option to remove a cookie for a specified default state #17

Closed zhangwei900808 closed 2 years ago

zhangwei900808 commented 3 years ago

when I login is success save cookie but how can I clear cookie when I logout?

zhangwei900808 commented 3 years ago
import Cookies from 'js-cookie'

// Create axios instance.
const axiosInstance = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_API_HOST}`,
  withCredentials: false,
});

axiosInstance.interceptors.response.use(response => {
  return response;
}, async error => {
  if (error.response.status === 401) {
    Cookies.remove('auth.me') // fail!
    Cookies.remove('auth.isLogin') // fail!
    Cookies.remove('auth.accessToken') // fail!
  }
  return error;
});
zhangwei900808 commented 3 years ago

who can help me?

trangchongcheng commented 3 years ago

me too, i need to remove the cookie if authentication fail

bjoluc commented 3 years ago

Hi both, I'm afraid deleting cookies is currently not supported (and if we deleted them directly on the client/server, they would be recreated again on the next state change / request), but I'd like to know your opinion on a feature idea I had: What about having a per-subtree defaultState (or the like) option that, if provided, removes the cookie whenever a state subtree equals the provided default state? When you log out, @zhangwei900808, you could set your state back to the default then and the cookie would disappear. Similarly, @trangchongcheng, you could reset the auth-related state when the authentication fails.

What do you think @zhangwei900808 @trangchongcheng?

zhangwei900808 commented 3 years ago

@bjoluc I know you said , but this a new problem , is when my token is expired axios status is 401 and axios file is not in reducer So I cant reset reducer when status = 401, do you have any methods to resolve it? eg: axios.js

// Create axios instance.
const axiosInstance = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_API_HOST}`,
  withCredentials: false,
});

axiosInstance.interceptors.response.use(response => {
  return response;
}, async error => {
  if (error.response.status === 401) {
    //todo:how to call reducer reset initstate
  }
  return error;
});
bjoluc commented 3 years ago

@zhangwei900808 I think you should be using redux-thunk to invoke axios in a context where you can dispatch Redux actions. next-redux-cookie-wrapper is designed to mirror Redux state changes to cookies and use them to share state between client and server, not to mirror cookie state changes to Redux (this only ever happens when the server updates cookies on page transitions). Hence you'll have to dispatch a Redux action when axios gets the response, and thunk actions are a good place to do so. You may find https://github.com/reduxjs/redux-thunk#why-do-i-need-this helpful.

zhangwei900808 commented 2 years ago

@bjoluc I had resolve this problem and I read this article helped me React + Redux: Refresh Token with Axios and JWT example

store/index.js

import setupInterceptors from "./setupInterceptors";

const combinedReducers = combineReducers({
  [authSlice.name]: authSlice.reducer,
  [layoutSlice.name]: layoutSlice.reducer,
  [systemSlice.name]: systemSlice.reducer,
  [spaceSlice.name]: spaceSlice.reducer,
  [settingSlice.name]: settingSlice.reducer,
  [userSlice.name]: userSlice.reducer,
  [homeSlice.name]: homeSlice.reducer
});
export const store = wrapMakeStore(() => {
  const initStore = configureStore({
    reducer: combinedReducers,
    middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(
      nextReduxCookieMiddleware({
        subtrees: ["auth.accessToken", "auth.refreshToken", "auth.isLogin", "auth.me"],
      })
    ).concat(logger)
  });
  setupInterceptors(initStore);
  return initStore;
});

const makeStore = () => store;

export const wrapper = createWrapper(store, {storeKey: 'key', debug: true});

axios.js

import axios from 'axios';

const axiosInstance = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_API_HOST}`,
  withCredentials: false,
});

export default axiosInstance;

setupInterceptors.js

import axiosInstance from "./axios";
import {logout, refresh} from '@/store/slices/authSlice'

const setup = (store) => {
  const {dispatch} = store;
  axiosInstance.interceptors.response.use(
    (res) => {
      return res;
    },
    async (error) => {
      console.log('error=', error)
      const originalConfig = error.config;
      // originalConfig._retry使用:避免死循环请求
      if (error.response.status === 401  && !originalConfig._retry) {
        originalConfig._retry = true;
        dispatch(refresh())
        return axiosInstance(originalConfig);
      }
      return Promise.reject(error);
    }
  );
};

export default setup;

authSlices.js

// 使用refresh token 获取 access token
export const refresh = createAsyncThunk('auth/refreshToken', async (params, thunkAPI) => {
  try {
    const {refreshToken} = thunkAPI.getState().auth;
    if (refreshToken) {
      console.log('==== refreshToken ====', refreshToken)
      const response = await axios.post('/auth/oauth/token', qs.stringify({
        grant_type: 'refresh_token',
        refresh_token: refreshToken
      }));
      const resdata = response.data;
      console.log('==== resdata ====', resdata)
      if (resdata.access_token) {
        const refetch = await axios.get('/account/me', {
          headers: {Authorization: `Bearer ${resdata.access_token}`},
        });
        return {
          accessToken: resdata.access_token,
          refreshToken: resdata.refresh_token,
          isLogin: true,
          me: {
            name: refetch.data.name,
            avatar: refetch.data.avatar
          }
        };
      } else {
        return thunkAPI.rejectWithValue({errorMsg: response.data.message});
      }
    } else {
      console.log('==== no refreshToken ====')
      return null
    }

  } catch (error) {
    return thunkAPI.rejectWithValue({errorMsg: error.message});
  }
});

[refresh.fulfilled]: (state, action) => {
      if (action.payload) {
        state.accessToken = action.payload.accessToken;
        state.refreshToken = action.payload.refreshToken;
        state.isLogin = action.payload.isLogin;
        state.me = action.payload.me;
      }
    },
bjoluc commented 2 years ago

Great, let's re-open this as a feature request anyway – I think it may be useful :)

bryanltobing commented 2 years ago

is there any update on this? I also need to clear cookies when doing logout. Or atleast could we have some workaround to implement logout ?

bjoluc commented 2 years ago

Hi @bryantobing12, I didn't pay attention to this lately, but hearing that you're interested, I'm happy to look into it soon :slightly_smiling_face: How important is this to you, or better: When do you need it (feel free to say ASAP, if that's the case)?

bryanltobing commented 2 years ago

Well, for now it's working fine at my case. And i can clear the cookies manually from the client.

bryanltobing commented 2 years ago

@bjoluc I was thinking that it would be nice if we can expose decompressFromEncodedURIComponent so that we don't need to install lz-string on our project to decode the cookie data. Is this possible ?

github-actions[bot] commented 2 years ago

:tada: This issue has been resolved in version 2.2.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket: