Redux state don't change after dispatch on next js - redux

I am new on next js and i use redux-next-wrapper !
The problem is that i want to dispatch a token access but when i am doing it in getInitialProps, the store does not update after the render of the page !
I try to use componentDidMount, it work, but the state is update only after the render of the page, which make visible the button login one second before to be replace by logout !
componentDidMount () {
const token_access = Cookies.get('xxxxxxxxxx');
const token_refresh = Cookies.get('xxxxxxxxxx');
console.log(token_access);
if (token_access && token_refresh) {
const decode = jwt_decode(token_refresh);
if (decode.exp >= new Date()) {
this.props.store.dispatch(logout(token_refresh))
}
else {
const decode_access = jwt_decode(token_access);
if (decode_access.exp >= new Date()) {
refresh(token_refresh)
}
this.props.store.dispatch(userLoggedIn(token_access));
}
}
}
static async getInitialProps ({Component, ctx}) {
const token = '12345';
ctx.store.dispatch(userLoggedIn(token));
return {
pageProps: (Component.getInitialProps ? await Component.getInitialProps(ctx) : {})
}
}
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/index'
export default initialState => createStore(
rootReducer,
composeWithDevTools(
applyMiddleware(thunk)
)
);
is there a way to dispatch and load the store before the render of the page ?
Thank you for your answer

I fix this issue by changing the store ! Hope this help someone :)
export const initStore = (initialState = {} ) => {
return createStore(rootReducer,
initialState, composeWithDevTools(applyMiddleware(thunk)))
};

Related

Next.js: How to use useState and AuthContext without invalidating SSG html

I have an AuthContext to manage the authentication in my Next.js app. The _app.js file looks like this:
import '../styles/global.css'
import 'tailwindcss/tailwind.css'
import { AuthProvider } from '../components/AuthContext'
function MyApp({ Component, pageProps }) {
return (
<>
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
</>
)
}
export default MyApp
And the AuthContext file is something like this:
import React, { useContext, useState, useEffect } from "react"
import { auth, db } from "./Firebase";
import { doc, getDoc } from "firebase/firestore";
const AuthContext = React.createContext()
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState([])
const [loading, setLoading] = useState(true)
const [userData, setUserData] = useState({})
const [companyData, setCompanyData] = useState({})
useEffect(() => {
const unsubscribe =
auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
useEffect(() => {
const fetchData = async () => {
const qu = doc(db, "users", currentUser.uid)
const datau = await getDoc(qu)
setUserData(datau.data());
const qc = doc(db, "companies", currentUser.uid)
const datac = await getDoc(qc)
setCompanyData(datac.data());
}
currentUser?.uid && fetchData()
}, [currentUser])
const value = {
currentUser,
userData,
companyData
}
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
)
}
export function useAuth() {
return useContext(AuthContext)
}
Then I have a dynamic page [id].js that is something like this:
import { useAuth } from '../../components/AuthContext';
import Head from 'next/head';
export async function getStaticProps(context) {
// fetch and return data
}
export async function getStaticPaths() {
// fetch paths
return { paths, fallback: 'blocking' }
}
export default function Job(props) {
const { userData, currentUser } = useAuth();
const data = props.data;
return (
<>
<Head>
{data.title} | SiteName
</Head>
<h1>data.title</h1>
{currentUser ? userData.name : null}
</>
)
}
Problem: The website works perfectly, BUT when I check the page [id].js source code, Next.js doesn't build the HTML structure with head and body optimized for SEO (which is the main reason why I'm migrating to Next.js).
If I remove the "!loading &&" (see below) in the AuthContext file, the Next's SSG HTML generation works BUT the whole app starts to give me errors everywhere.
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
What exactly is the problem? And any idea on how to solve it?

I have successfully implemented the redux-persist with next-redux-wrapper in next js

Im getting data from the external api and storing it in the reducer.And im using redux-persist to persist the state while navigating from one page to another.But i have made left the whiteList as an empty array but all the state are being persisted?Need help
import "../assets/css/style.scss";
import "owl.carousel/dist/assets/owl.carousel.css";
import "owl.carousel/dist/assets/owl.theme.default.css";
import Layout from "../component/Layout/Layout";
import { wrapper } from "../redux/store";
import { useEffect } from "react";
import { useStore } from "react-redux";
function MyApp({ Component, pageProps }) {
const store = useStore((store) => store);
useEffect(() => {
{
typeof document !== undefined
? require("bootstrap/dist/js/bootstrap.bundle")
: null;
}
}, []);
return (
<Layout>
<Component {...pageProps} />;
</Layout>
);
}
export default wrapper.withRedux(MyApp);
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import rootReducer from "./index";
import { createWrapper, HYDRATE } from "next-redux-wrapper";
const middleware = [thunk];
let initialState={}
// BINDING MIDDLEWARE
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const makeStore = ({ isServer }) => {
if (isServer) {
//If it's on server side, create a store
return createStore(rootReducer,initialState, bindMiddleware(middleware));
} else {
//If it's on client side, create a store which will persis
const persistConfig = {
key: "root",
storage: storage,
whiteList: [],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer,initialState, bindMiddleware(middleware));
store.__persisitor = persistStore(store); // This creates a persistor object & push that
persisted object to .__persistor, so that we can avail the persistability feature
return store;
}
};
// export an assembled wrapper
export const wrapper = createWrapper(makeStore);
If you keep the whitelist an empty array then nothing will be persisted.
You have to put inside that string array the redux reducers values you want to be persisted.
Example:
const persistConfig = {
key: 'root',
storage: storage,
whiteList: ['cart', 'form', 'user'],
};

Redux Thunk not dispatching

I have installed Redux Thunk on my application and it's been working fine so far, all of the previous actions I've created are pulling out data from APIs successfully, however the following action is not even dispatching actions to my reducer, any idea what am I missing?
// my action
export const fetchClub = id => {
debugger
return (dispatch) => {
if (id){
dispatch ({type: 'START_PULLING_NIGHTCLUB'});
let targetUrl = `http://localhost:3001/nightclub`
fetch(targetUrl)
.then(res => {
debugger
return res.json()
})
.then(nightclub => dispatch({type: 'CURRENT_NIGHTCLUB', nightclubs: nightclub.result}))
.catch(error => {
console.log(error)
})
}}}
//my reducer
import {combineReducers} from "redux"
const rootReducer = combineReducers({
nightclubs: nightClubsReducer,
user: userReducer
})
export default rootReducer
function nightClubsReducer(state = {}, action) {
debugger
switch (action.type){
case 'ADD_NIGHTCLUBS':
debugger
let nightclubs = action.nightclubs
// filering the results just to show nightclubs rather than hotels
nightclubs = nightclubs.filter( function (nightclub){
return !nightclub.types.includes("lodging")
})
return {...state.nightclubs, nightclubs}
case 'CURRENT_NIGHTCLUB':
debugger
let nightclub = action.nightclub
return {...state.nightclubs, nightclub}
default:
return state
}}
function userReducer(state = {user: {logged_in: false}}, action){
let current_user = {}
switch (action.type){
case 'ADD_USER_LOCATION':
let coords = action.location.coords
return {...state.user, coords}
case 'CREATE_USER':
current_user = action.user
state.logged_in = true
return {...state.user, current_user}
case 'ADD_LOGGED_IN_USER':
current_user = action.user
if(state.user){
state.user.logged_in = action.user.logged_in}
return {...state.user, current_user}
default:
return state
}
}
I should be hitting the debugger on the first line of my nightClubsReducer however nothing happens.
My Nightclub component is connected properly as far as I'm aware:
import React, { Component } from 'react';
import Maya from '../assets/Mayaclubbio.jpg'
import '../NightClubPage.css'
import { connect } from 'react-redux';
import { fetchClub } from '../actions/NightClubs';
class NightClub extends Component {
constructor(props) {
super(props);
this.id = props.match.params.id
}
componentDidMount() {
fetchClub(this.id)
}
render() {
debugger
return (
<React.Fragment>
//HTML code
</React.Fragment>
)
}
}
const mapStateToProps = (state) => {
debugger
return {
nightclub: state.nightclubs.nightclub,
user: state.user
}
}
export default connect(mapStateToProps, { fetchClub })(NightClub);
I have no clue what could be failing as I'm using the same logic for the rest of my actions and they are working just fine.
I think calling the action from props should fix your issue
componentDidMount() {
this.props.fetchClub(this.id);
}

"Cannot read property of undefined" being thrown by react-apollo used with React-NextJS

My NextJS project has been giving me grief over Graph QL these days. I've been trying to implement an Apollo client solution to retrieve data from a remote GraphQL server into a custom component. But no matter which solution I try, I always end up with this error. Here's my current react-apollo implementation:
// /lib/with-apollo-client.js
import React from "react";
import Head from "next/head";
import { getDataFromTree } from "react-apollo";
import initApollo from "./init-apollo";
export default App => {
return class WithData extends React.Component {
static displayName = `WithData(${App.displayName})`;
static async getInitialProps(ctx) {
const { Component, router } = ctx;
const apollo = initApollo({});
ctx.ctx.apolloClient = apollo;
let appProps = {};
if (App.getInitialProps) {
appProps = await App.getInitialProps(ctx);
}
// Run all GraphQL queries in the component tree
// and extract the resulting data
if (!process.browser) {
try {
// Run all GraphQL queries
await getDataFromTree(
<App
{...appProps}
Component={Component}
router={router}
apolloClient={apollo}
/>
);
} catch (error) {
console.error("Error while running `getDataFromTree`", error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
// Extract query data from the Apollo store
const apolloState = apollo.cache.extract();
return {
...appProps,
apolloState
};
}
constructor(props) {
super(props);
this.apolloClient = initApollo(props.apolloState);
}
render() {
return <App {...this.props} apolloClient={this.apolloClient} />;
}
};
};
// /lib/init-apollo.js
import { ApolloClient, InMemoryCache, HttpLink } from 'apollo-boost'
import fetch from 'isomorphic-unfetch'
let apolloClient = null
// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
global.fetch = fetch
}
function create (initialState) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
connectToDevTools: process.browser,
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
}),
cache: new InMemoryCache().restore(initialState || {})
})
}
export default function initApollo (initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (!process.browser) {
return create(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState)
}
return apolloClient
}
The component I'm retrieving data into looks like this:
// /components/PostsList2.jsx
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
export const allUsersQuery = gql`
query allUsers($first: Int!, $skip: Int!) {
allUsers(orderBy: createdAt_DESC, first: $first, skip: $skip) {
id
firstName
createdAt
}
_allUsersMeta {
count
}
}
`
export const allUsersQueryVars = {
skip: 0,
first: 10
}
export default function PostsList2 () {
return (
<Query query={allUsersQuery} variables={allUsersQueryVars}>
{({ loading, error, data: { allUsers, _allUsersMeta }, fetchMore }) => {
if (error) return <aside>Error loading users!</aside>
if (loading) return <div>Loading</div>
const areMorePosts = allUsers.length < _allUsersMeta.count
return (
<section>
<ul>
{allUsers.map((user, index) => (
<li key={user.id}>
<div>
<span>{index + 1}. </span>
<div>{user.firstName}</div>
</div>
</li>
))}
</ul>
{areMorePosts ? (
<button onClick={() => loadMorePosts(allUsers, fetchMore)}>
{' '}
{loading ? 'Loading...' : 'Show More'}{' '}
</button>
) : (
''
)}
</section>
)
}}
</Query>
)
}
function loadMorePosts (allUsers, fetchMore) {
fetchMore({
variables: {
skip: allUsers.length
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return previousResult
}
return Object.assign({}, previousResult, {
// Append the new users results to the old one
allUsers: [...previousResult.allUsers, ...fetchMoreResult.allUsers]
})
}
})
}
Since this is a NextJS project, there's also an _app.jsx that I've wrapped in a special provider component:
// /pages._app.jsx
/* eslint-disable max-len */
import '../static/styles/fonts.scss';
import '../static/styles/style.scss';
import '../static/styles/some.css';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/styles';
import jwt from 'jsonwebtoken';
import withRedux from 'next-redux-wrapper';
import App, {
Container,
} from 'next/app';
import Head from 'next/head';
import React from 'react';
import { Provider } from 'react-redux';
import makeStore from '../reducers';
import mainTheme from '../themes/main-theme';
import getSessIDFromCookies from '../utils/get-sessid-from-cookies';
import getLanguageFromCookies from '../utils/get-language-from-cookies';
import getUserTokenFromCookies from '../utils/get-user-token-from-cookies';
import removeFbHash from '../utils/remove-fb-hash';
import withApolloClient from '../lib/with-apollo-client'
import { ApolloProvider } from 'react-apollo'
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let userToken;
let sessID;
let language;
if (ctx.isServer) {
ctx.store.dispatch({ type: 'UPDATEIP', payload: ctx.req.headers['x-real-ip'] });
userToken = getUserTokenFromCookies(ctx.req);
sessID = getSessIDFromCookies(ctx.req);
language = getLanguageFromCookies(ctx.req);
const dictionary = require(`../dictionaries/${language}`);
ctx.store.dispatch({ type: 'SETLANGUAGE', payload: dictionary });
if(ctx.res) {
if(ctx.res.locals) {
if(!ctx.res.locals.authenticated) {
userToken = null;
sessID = null;
}
}
}
if (userToken && sessID) { // TBD: validate integrity of sessID
const userInfo = jwt.verify(userToken, process.env.JWT_SECRET);
ctx.store.dispatch({ type: 'ADDUSERINFO', payload: userInfo });
}
ctx.store.dispatch({ type: 'ADDSESSION', payload: sessID }); // component will be able to read from store's state when rendered
}
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
return { pageProps };
}
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
// Register serviceWorker
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceWorker.js'); }
// Handle FB's ugly redirect URL hash
removeFbHash(window, document);
}
render() {
const { Component, pageProps, store, apolloClient } = this.props;
return (
<Container>
<Head>
// redacted for brevity
</Head>
<ThemeProvider theme={mainTheme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<ApolloProvider client={apolloClient}>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</ApolloProvider>
</ThemeProvider>
</Container>
);
}
}
export default withApolloClient(withRedux(makeStore)(MyApp));
So with this setup, when I compile and run my app, it throws the following:
TypeError: Cannot read property 'allUsers' of undefined
I'm really lost! The repo is up at https://github.com/amitschandillia/proost/tree/master/web.

how to reset redux store when log out?

I am trying to reset my storage when I log out but it doesn't seem to work at all.
As you can see I am using AsyncStorage for my store and I try to follow the answer from this post.
Here is my index.js from store folder
import thunk from 'redux-thunk';
import { createStore, compose, applyMiddleware } from 'redux';
import { AsyncStorage } from 'react-native';
import { persistStore, autoRehydrate } from 'redux-persist';
import rootReducer from '../reducers';
var defaultState = {};
export function configureStore(initialState = defaultState) {
var store = createStore(rootReducer, initialState, compose(
applyMiddleware(thunk),
autoRehydrate(),
));
persistStore(store, { storage: AsyncStorage });
return store;
}
and here is my index.js from reducers folder
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import { AsyncStorage } from 'react-native';
import authReducer from './authReducer';
import alertsReducer from './alertsReducer';
import jobsReducer from './jobsReducer';
import userDataReducer from './userDataReducer';
const appReducer = combineReducers({
form: formReducer,
auth: authReducer,
alerts: alertsReducer,
jobs: jobsReducer,
userData: userDataReducer
})
const rootReducer = ( state, action ) => {
if(action.type === 'UNAUTH_USER') {
Object.keys(state).forEach(key => {
AsyncStorage.removeItem(`persist:${key}`);
console.log(state)
});
}
return appReducer(state, action)
}
export default rootReducer
On Initial load of our application the reducer state is fresh.
We Can copy this initial default state and use it to assign to our reducer again on logging out, the way we can achieve this could be as follows.
Step 1: call an action on the application load that will copy reducer's initial state as the defaultState
Step 2: While logging out of the application we can simply reAssign the default state and it should work as new.
App root Component
componentDidMount() {
dispatch(ON_APP_LOAD)
}
App Reducer
const appReducer = combineReducers({
user: userStatusReducer,
analysis: analysisReducer,
incentives: incentivesReducer
});
let defaultState = null;
export default (state, action) => {
switch (action.type) {
case **ON_APP_LOAD**:
// will be assigned or called only once
defaultState = defaultState || state;
break;
case **RESET_STATE**:
// detaching the reference on reset
state = _.deepClone(defaultState);
return state;
default:
break;
}
return appReducer(state, action);
};
On Logout calling the action for resetting state
function* logoutUser(action) {
// on logout success
dispatch("RESET_STATE")
}
I assume you have the js file where all reducers are combined in one and thus you have:
const allReducers = combineReducers({
reducer: nameOfReducer
});
const rootReducer = (state, action) => {
switch (action.type) {
case CLEAR_ALL_REDUCERS_DATA:
return state = undefined;
default:
return allReducers(state, action)
}
};
export default rootReducer;
and in index file where you create a store you need to define
const store = createStore(rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
and then simply call it on logout:
dispatch(clearAllStoreData());
where clearAllStoreData is an action defined in your action file:
export const clearAllStoreData = () => {
return {
type: CLEAR_ALL_REDUCERS_DATA
}
};

Resources