Using Redux Provider vs Redux Wrapper with NextJs | State Persists Without Wrapper - reactjs

According to the docs for the next-redux-wrapper:
When Next.js static site generator or server side rendering is involved, however, things start to get complicated as another store instance is needed on the server to render Redux-connected components.
I don't understand exactly where the complication arises.
SSR to SSG Redux State Persists During Page Navigation
I built a simple NextJs app that has one page that renders on the server, and another that is statically rendered. In production mode or in development mode, I can navigate between these two pages, and my redux state remains in-tact.
My understanding is that this isn't supposed to be the case, since a separate redux store must be created on the server and on the client; hence the creation of the next redux wrapper (also for injecting redux state into NextJs lifecycle methods, which I haven't needed).
I'm confused as to what mechanism is being used to persist the redux state between SSR and SSG pages. I'm using a simple redux provider, with a simple bit of state for testing.
Note: State persists between these two page components even without using redux-persist.
Redux Persist Also Works...
I have confirmed that redux persist also works without the next redux wrapper, saving to the local storage without issue, persisting successfully on page refresh.
Here's example code of my configuration:
page1.js (Statically rendered)
import Layout from './layout';
import { useSelector, useDispatch } from 'react-redux';
import { getTest, addBar } from '../redux/slices/topten';
export default function Page1() {
const dispatch = useDispatch();
const myState = useSelector(getTest);
return (
<Layout>
<h2>Statically Rendered</h2>
<div>
<span>
{ myState }
</span>
<button onClick={() => dispatch(addBar())}>
Add Bar to Foo
</button>
</div>
</Layout>
)
}
page2.js (Server-side rendered)
import Layout from './layout';
import { useSelector, useDispatch } from 'react-redux';
import { getTest, addBar } from '../redux/slices/topten';
export default function Page2() {
const dispatch = useDispatch();
const myState = useSelector(getTest);
return (
<Layout>
<h2>Server Side Rendered</h2>
<div>
<span>
{ myState }
</span>
<button onClick={() => dispatch(addBar())}>
Add Bar to Foo
</button>
</div>
</Layout>
)
}
export async function getServerSideProps() {
// Code for fetching data from external api.
return {
props: {
// Returned props
}
};
};
App.js
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from '../redux/store';
export default function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Component {...pageProps} />
</PersistGate>
</Provider>
)
}
topten.js (This is the redux slice with a simple reducer)
const initialState = {
topTen: {
test: "foo"
}
}
const topTenSlice = createSlice({
name: 'topten',
initialState,
reducers: {
addBar: (state) => {
state.topTen.test = "foobar";
}
},
});
// Export action.
export const { addBar } = topTenSlice.actions;
// Export slice reducer.
export default topTenSlice.reducer;
// Export state.
export const getTest = state => state.topten.topTen.test;
I left out the code for Layout and store, because it's redundant. It works without the wrapper... That's what I'm trying to understand.
I have verified here that state persists between the two components simply using the Provider HOC. According to the docs:
store is being replaced on navigation
I have also verified that state is persisting on page refresh using redux-persist.
I don't understand why I would need the wrapper unless I needed access to redux state within the NextJs lifecycle methods... Which can easily be avoided using redux useSelector hooks.
Any clarity on this would be appreciated.

Related

React Redux: state undefined

Playing around with react-redux and my state isCartVisible is showing undefined, I used simple functional components and I'm storing my stores in different files.
//main index.js file
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import store from './redux-store/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}><App /></Provider>);
and
//App.js
import { useSelector } from "react-redux";
import Layout from "./components/Layout/Layout";
import Cart from "./components/Cart/Cart";
function App() {
const cartVisible = useSelector((state) => state.isCartVisible);
return (
<Layout>
{cartVisible && <Cart />}
</Layout>
);
}
and a component deep somewhere inside the app, by clicking the button I wanna toggle my <Cart> component
//CartButton.js
import { useDispatch } from "react-redux";
const CartButton = () => {
const dispatch = useDispatch();
const cartShowHandler = () => {
dispatch({ type: "cartToggle" });
};
return (
<button onClick={cartShowHandler}>
Click
</button>
);
};
and that's my store file, where I've created my store with reducer
import { createStore } from "redux";
const uiReducer = (state = { isCartVisible: true }, action) => {
if (action.type === "cartToggle") {
state.isCartVisible = !state.isCartVisible;
}
return state;
};
const uiStore = createStore(uiReducer);
export default uiStore;
You should never mutate the state. Your condition in reducer should look like this and it will work.
if (action.type === "cartToggle") {
return { ...state, isCartVisible: !state.isCartVisible};
}
As you have only one key in store in your example at the moment. You can do it this way also.
return { isCartVisible: !state.isCartVisible};
But it's always a good practice to return the whole state in your reducer's conditions.
Remember that redux do shallow comparison. Which means it checks if reference of an object is changed. In your case it wasnt changed.
Once, I wrote something about this topic in a blog post https://dev.to/machy44/shallow-comparison-in-redux-3a6
In Redux, you can only have one store. So it is very likely that your useSelector call actually tries to select data from another store than you are expecting it here.
You could validate that by using something like
const fullState = useSelector(state => state)
console.log(fullState)
That said, you are also writing a style of Redux here that is outdated by many years - in modern Redux you are not writing switch..case reducers or string action types. Also, createStore is deprecated at this point in favor of configureStore.
I would highly recommend you read about modern Redux and then follow the official Redux tutorial.
Whatever resources you have been following right now might have given you a very skewed view of how to use Redux.

How to get stored data from store.getState()

I have using react with redux for the first time.
In my app's render, if I console log store.getState() I can see the stored data {username: 'foo', password: 'bar'}
but when I want to get the data like store.getState().password
I get undefined error. I am trying to pass this data to
my components for private route as:
inside PrivateRoute I then try to check if the user is logged in or not to send to dashboard
so, how do I get data to pass to my props?
even const { isLoggedIn, username } = store.getState() doesn't work, it shows
Property isLoggedIn does not exist on type {}
btw I know this might be bad but it's my first react app so I am trying to learn redux
if you are calling the store from the react application you have to use provider and pass the store to the react app, and then bind state, actions and/or methods of the store to the react props as shown in this link
connect https://react-redux.js.org/5.x/api/connect
but if you are using redux in normal javascript then it will work fine.
example in react
first
import { Provider } from 'react-redux'
import { store } from "store";
ReactDOM.render(
<Provider store={store}>
<YourReactApp/> // e.g <Container />
</Provider>, document.getElementById('root')
);
then in you can bind anything from your store to react component like this
import { connect } from "react-redux";
const mapStateToProps = (state) => {
const { isLoggedIn, username }= state
return {
isLoggedIn,
username
};
};
const mapDispatchToProps = (dispatch :any) => {
return {
login: ()=> {
return dispatch(your action creator)
}
}
}
const Containter = connect(mapStateToProps,mapDispatchToProps)(AnyReactComponentYouWantToPassThisStore);
export default Containter;
the you can use it in your page like this
function AnyReactComponentYouWantToPassThisStore (props){
return(
<div> {props.username} </div>
)
}
then instead of calling <AnyReactComponentYouWantToPassThisStore />
now use <Container />

Class component with Redux

I am new to React and Redux and as we know, it is best to use class component for those components that have state and the question I would like to ask is that Is it recommended to use functional component for components that have connection and interaction with Redux store since those components that interact with store do not have state locally.
As of version 7.x react-redux now has hooks for functional components
const store = useSelector(store => store)
So that we can use functional component with redux store like class component.
please check below link to get more idea about hooks
https://react-redux.js.org/next/api/hooks
It's perfectly fine to connect functional components to redux store.
Functional components don't have a state is not completely correct with hooks. You can add state to functional component with hooks.
Answering your question, you can connect functional component with redux store like below.
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider, connect } from "react-redux";
const reducers = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
};
const store = createStore(reducers, 0);
const App = ({ count, handleIncrement, handleDecrement }) => {
return (
<div>
<button onClick={handleIncrement}>+</button>
<h4>{count}</h4>
<button onClick={handleDecrement}>-</button>
</div>
);
};
const mapStateToProps = state => {
return { count: state };
};
const mapDispatchToProps = dispatch => {
return {
handleIncrement: () => {
dispatch({ type: "INCREMENT" });
},
handleDecrement: () => {
dispatch({ type: "DECREMENT" });
}
};
};
const ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
document.getElementById("root")
);
Is it recommended to use functional components for components that have connection and interaction with the Redux store since those components that interact with the store do not have state locally.
Yes, it is recommended to use functional components with redux, and there is a way to have a local state in a functional component.
Why functional components are recommended?
The react ecosystem moves toward the use of hooks which means standardize the functional components.
As stated in docs about uses of hooks or classes:
In the longer term, we expect Hooks to be the primary way people write React components.
How to have a local state in functional components with redux?
Redux introduced redux-hooks API which gives functional components the ability to use local component state and allows to subscribe to the Redux store without wrapping your components with connect().
useSelector
useDispatch
useStore
// Creating a store
const store = createStore(rootReducer)
ReactDOM.render(
<Provider store={store}>
<CounterComponent />
</Provider>,
document.getElementById('root')
)
// CounterComponent.jsx Selector example
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
// Using the store localy with a selector.
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
// CounterComponent.jsx Dispatch Example
import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
// Dispatching an action
const dispatch = useDispatch()
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}
// CounterComponent.jsx Referencing the store example
import React from 'react'
import { useStore } from 'react-redux'
export const CounterComponent = ({ value }) => {
const store = useStore()
// EXAMPLE ONLY! Do not do this in a real app.
// The component will not automatically update if the store state changes
return <div>{store.getState()}</div>
}

Grab redux snapshot of redux state without connecting

I have a background saga that is watching the location and submits an action with the new location every time it changes and updates the location state. However, I have a component that I just want to grab the current state.user.location on a user interaction, but I don't want to use mapStateToProps because the component keeps re-rendering and I only want to grab the state when the user requests it and avoid constantly re-rendering.
I need the state in the background for other parts of the app.
reducer:
export const updateLocation = (state, { location }) => state.merge({ location })
selector:
getLocation : state => state.user.location && state.user.location.coords || null
Component:
class SearchScreen extends PureComponent {
render(){
const {location} = this.props
return(
<Button onPress={()=>searchWithCurrentLocation(location)} />
)}
}
const mapStateToProps = (state) => {
return {
location: UserSelectors.getLocation(state),
}
}
this is my current setup, but I'd like to not pass in the location and keep re-rendering as it's not used to display the component.
You can make the store accessible from anywhere like this:
file: storeProvider.js
var store = undefined;
export default {
init(configureStore){
store = configureStore();
},
getStore(){
return store;
}
};
file: App.js
import { createStore } from 'redux';
import rootReducer from './rootReducer';
import storeProvider from './storeProvider';
const configureStore = () => createStore(rootReducer);
storeProvider.init(configureStore);
const store = storeProvider.getStore();
const App = () =>
<Provider store={store} >
<Stuff/>
</Provider>
file: Component.js
import storeProvider from './storeProvider';
class SearchScreen extends PureComponent {
render(){
return(
<Button onPress={()=> searchWithCurrentLocation(UserSelectors.getLocation(storeProvider.getStore().getState())} />
)}
}
I don't think you need to be troubled about re-rendering if the data that has changed isn't directly affecting the components inside your render method. Let us remember that ReactDOM watches only those changed state and only update the DOM based on what's different. The render method maybe called but if nothing has really changed, it won't affect render performance at all.
After all that is what react is selling: reactive elements that updates changes if data changes in a smart way that it is optimized by updating only updated elements by using a virtual DOM which is ReactDOM.
Changes are first compared between the virtual DOM and the real DOM before updates are committed to the real DOM. You can read how ReactDOM does this here.

Integrating Dispatch Actions in Container Component Pattern

So I'm completely confused on how to integrate the Container and Component Pattern. I've been reviewing examples all morning and nothing seems to be clicking. How I have been worked with React previously on my first project was fetch the data within my view components and then pass that data down as props using the #connect which works, but in an "automagically" way to me at this time.
import React;
...
import {action} from 'path/to/action.js';
#connect((store) => {return{ key: store.property}});
export class Component{
componentWillMount(){
this.props.dispatch(action());
}
}
As I'm working more with React I want to learn the more "correct" way of building out with Redux and understand on a deeper level what is happening.
What I have setup is
index.jsx (This renders all of my HOCs)
|
App.jsx (Container)
|
Auth.jsx (Component)
|
Layout.jsx (Component) - Contains app content
--or--
AuthError.jsx (Component) - 401 unauthenticated error page
Authentication is handled through an outside resource so this app will not control anything with Logging in or out. There will be no log in/out states simply receiving an object from an API that identifies the User Role & Authenticated Boolean.
What I would like to happen is when the App loads, it will fetch data from a mock API, JSON Server. From there it will render the Auth component. The Auth component will take in props from App.jsx and either render the Layout.jsx or AuthError.jsx.
Where I'm running into issues is how this should be integrated. I'm going to omit lines of code I don't think absolutely pertain to the question.
store.js
import { applyMiddleware, combineReducers, createStore } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import promise from 'redux-promise-middleware';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducer from './reducers';
const middleware = applyMiddleware(promise(), thunk, createLogger());
export default createStore(reducer, composeWithDevTools(middleware));
index.jsx
import React from 'react';
import store from './store.js';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './containers/App.jsx';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
App.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { authenticateUser } from '../actions/authActions.js';
import Auth from '../components/Auth.jsx';
class App extends Component {
constructor(props) {
super(props);
this.state = {
authenticated: false // this needs to be set
};
}
componentWillMount() {
console.log('APP PROPS', this.props);
// this.props.actions.authenticateUser();
authenticateUser(); // this runs but doesn't run the dispatch function
// What I think needs to happen here Dispatch an Action and then setState referring back to how I would previous build with React Redux.
}
render() {
return (
<Auth app_name={ApplicationName} authenticated={this.state.authenticated} {...this.props} />
);
}
}
const mapStateToProps = state => {
console.log('redux store auth state', state);
return {
auth: state.auth
};
};
const mapDispatchToProps = dispatch => {
return { actions: bindActionCreators(authenticateUser, dispatch) };
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Auth.jsx
import React from 'react';
import { Route } from 'react-router-dom';
import AuthError from './AuthError.jsx';
import Layout from './Layout.jsx';
export default function Auth(props) {
console.log('AUTH PROPS', props);
const renderLayout = () => {
if (props.authenticated == true) {
return <Layout app_name={props.app_name} />;
} else {
return <AuthError />;
}
};
return <Route path="/" render={renderLayout} />;
}
authReducer.js
export default function reducer(
state = {
authenticated: null
},
action
) {
switch (action.type) {
case 'AUTH_SUCCESSFUL': {
return {
...state,
authenticated: action.payload.authenticated
};
break;
}
case 'AUTH_REJECTED': {
return {
...state,
authenticated: false
};
}
}
return state;
}
authActions.js
import axios from 'axios';
export function authenticateUser() {
console.log('authenticate user action has been called');
return function(dispatch) {
// nothing runs within this block so it's leading me to believe nothing is being `dispatch`ed
console.log('dispatch', dispatch);
axios
.get('localhost:3004/auth')
.then(response => {
dispatch({ type: 'AUTH_SUCCESSFUL', payload: response.data });
console.log('response', response);
})
.catch(err => {
dispatch({ type: 'AUTH_REJECTED', payload: err });
console.log('error', err);
});
};
}
Right now inside of App.jsx I can console the state of the authReducer and I can call authenticateUser() in my actions. But when I call authenticateUser() the return dispatch function doesn't run. Should I be dispatching the auth action in App.jsx? Or should I be dispatching the auth in Auth.jsx as a prop to then have App.jsx fetch the data? Just a bit lost on breaking this apart and what piece should be doing what work.
I'll do a brief explanation about it to help you to understand those patterns and don't get in confusion anymore (I hope).
So, let's forget reducers for a moment to focus on container, action creator and component pattern.
Component
A lot of people implement components by wrong way when using it with redux application.
A better component approach for redux is, implement it with stateless pattern (see Functional Components). Let's see in practice:
// components/Subscribe.js
import React from 'react'
import PropTypes from 'prop-types'
const Subscribe = ({text, confirmSubscription}) =>
<div>
<p>{text}</p>
<button onClick={confirmSubscription}>Confirm</button>
</div>
Subscribe.propTypes = {
subtitle: PropTypes.string.isRequired
}
Subscribe.defaultProps = {
subtitle: ''
}
export default Subtitle
This allows you to optimize component footprint because they have less features than stateful components (or class components), so you will win some performance and keep focused on component objective.
Container
In other hand, Container is a kind of component with some logical implementation. Container is a pattern created to bind React and Redux, because both should't interact directly. This means, a Container render the component, handle some component events (for example, form onSubmit) and feed components with application state. So, the Container is the best place to interact with Redux. (react-redux)[https://github.com/reactjs/react-redux] and Redux make this task a bit easier. So a simple Container to feed and capture interactions on Subscribe component could be like this:
// containers/SubscribeContainer.js
import React from 'react'
import PropTypes from 'prop-types'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { confirmSubscription } from 'actions/subscription'
import Subscribe from 'components/Subscribe'
const mapStateToProps = state => ({
text: state.subscription.text
})
const mapDispatchToProps = dispatch =>
bindActionCreators({
confirmSubscription
}, dispatch)
const Container = connect(mapStateToProps, mapDispatchToProps)
export default Container(Subscribe)
Action Creator
An action creator (or action creators), is just a collection of or a function where return an action. Simple like that:
// actions/subscription
export const CONFIRM_SUBSCRIPTION = 'actions.confirmSubscription'
export function confirmSubscription() {
return {
type: CONFIRM_SUBSCRIPTION
}
}
For now, we have the triad pattern, Component, Container and Action Creator implemented, from here, you just need two more things to make this working with Redux.
Create a subscription store.
Handle CONFIRM_SUBSCRIPTION (in case to update app's state)
Return a new state
The magic will happen when you return a new state from any reducer, the mapStateToProps will be called and you will receive the new state as argument and from there, React will update your components when necessary, in case of those components are stateless, PureComponent (works only with single level states and props) or custom shouldComponentUpdate.
Another thing to keep on mind is to not do fetch or async execution inside Components, Containers and Action Creators, instead, you can use middleware like redux-thunk to compose a custom middeware to capture actions and handle that before be sent to reducers.
your authenticateUser returns a function, you need to literally run the function. The right way to do that is to add a property in your mapDispatchToProps
const mapDispatchToProps = dispatch => {
return { authenticateUser: () => dispatch(authenticateUser()) };
};
Then, in your componentWillMount function, call
this.props.authenticateUer()
Check this

Resources