I am learning rematch/redux. I can't get the state to show with the API.
I have the model imported in index.js along with the store and the provider. These are my reducers/effects:
import { getItems } from './service'
const products = {
state: {
products: [],
},
reducers: {
setProducts(state, products) {
return {
...state,
products,
};
},
},
effects: {
async loadProducts() {
const products = await getItems() // <-- This is the api working normally
this.setProducts(products)
},
}
}
export default products
And this is my component:
import './App.css';
import { connect } from 'react-redux';
import React, { useEffect } from 'react';
const mapStateToProps = ({ products }) => {
return {
...products
}
}
const mapDispatchToProps = ({ products }) => {
return {
...products
}
}
const App = ({ products }) => {
useEffect(() => {
console.log(products)
})
return (
<div className="App">
{console.log(products)}
</div>
)
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
I am not sure what I am missing.
Thank you.
I'm Rematch maintainer, you should review our documentation or consider buying the official Redux made easy with Rematch book where you'll learn all this questions.
I highly recommend using React-Redux hooks instead of connect method.
import './App.css';
import { useSelector, useDispatch } from 'react-redux';
import React, { useEffect } from 'react';
const App = () => {
const dispatch = useDispatch();
const { products } = useSelector(rootState => rootState.products)
useEffect(() => {
dispatch.products.loadProducts();
}, []);
return (
<div className="App">
{console.log(products)}
</div>
)
}
export default App;
Be careful with hooks, you're forgetting to add the deps array to useEffect, any code you add there will run infinitely
Your Rematch models looks fine, you just need to work more on React essentials :)
Redux rematch
Create music listing app using youtube api
How to create folder structure and setup store for react redux
Working code sandbox link
https://codesandbox.io/s/rematch-yf77l0?file=/src/pages/musics/index.jsx
Related
I am making api call in my redux action creator storing those data into my redux state, but when trying to access that using useSelector getting undefined.
Not able to understand where i am doing wrong. Any help here would be great. I am using redux toolkit.
Attaching snipped for reducers, action creator, component, redux state snapshot from redux dev tools and debugging logs
Note: appID will come from path params but for now i am hardcoding it
In my SuggestedAudience snipped if i comment out {app.name} which is causing the problem then my redux states are loaded correctly, snapshot attached
Observation: If i am making any changes to Suggested audience and saving then i am getting value but when reloading it 2-3 times again the error comes.
//apps slice to store apps
import { createSlice } from "#reduxjs/toolkit";
import _ from "lodash";
const appsSlice = createSlice({
name: "apps",
initialState: {},
reducers: {
appSuccess(state, action) {
const apps = _.keyBy(action.payload.data, "uuid");
return apps;
},
},
});
export const appsAction = appsSlice.actions;
export default appsSlice.reducer;
//ui-slice to store app data
import { createSlice } from "#reduxjs/toolkit";
const uiSlice = createSlice({
name: "ui",
initialState: { appId: null, orgId: null },
reducers: {
setAppId(state, action) {
state.appId = action.payload;
},
},
});
export const uiAction = uiSlice.actions;
export default uiSlice.reducer;
//App.js
import logo from "./logo.svg";
import "./App.css";
import PageHeader from "./components/PageHeader";
import SuggestedAudiences from "./components/SuggestedAudiences";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { uiAction } from "./components/store/ui-slice";
import { appsAction } from "./components/store/apps-slice";
import { fetchApps } from "./components/store/apps-action";
function App() {
const dispatch = useDispatch();
const appID = "52f14657d6c765401a2d7d4-41dfb38a-4262-11e3-9166-005cf8cbabd8";
useEffect(() => {
dispatch(uiAction.setAppId(appID));
dispatch(fetchApps());
}, []);
return (
<>
<PageHeader />
<SuggestedAudiences />
</>
);
}
export default App;
//Suggested Audiences
import React, { useEffect } from "react";
import { fetchApps } from "./store/apps-action";
import { useDispatch, useSelector } from "react-redux";
import { uiAction } from "./store/ui-slice";
const SuggestedAudiences = (props) => {
const dispatch = useDispatch();
const appId = useSelector((state) => state.ui.appId);
const app = useSelector((state) => state.apps[appId]);
return (
<>
<div>Suggested Audiences 🚀</div>
{app.name}
</>
);
};
export default SuggestedAudiences;
Fixed it by doing {app && app.name} or {app?.name} would also work.
Keeping it open if someone can help me understand it why it is happening and is there any other way to fix this so that undefined doesn't come in first place itself.
Try to change this line
const app = useSelector((state) => state.apps[appId]);
into this
const app = useSelector((state) => state?.apps[appId]);
I'm a new Next user and have been using Redux with React for a long time
I had a lot of trouble in using Redux with Next
I'm done with this solution
store.js
import { configureStore } from '#reduxjs/toolkit';
import reducers from './rootReducer';
export function makeStore() {
return configureStore({
reducer: reducers,
});
}
const store = makeStore();
export default store;
rootReducer.js
import { combineReducers } from '#reduxjs/toolkit';
import tes from './test/tes';
const reducers = combineReducers({
test: tes,
});
export default reducers;
_app.js
import React from 'react';
import { Provider } from 'react-redux';
import store from '../redux/store';
import { createWrapper } from 'next-redux-wrapper';
const MyApp = ({ Component, ...rest }) => {
return (
<Provider store={store}>
<Component {...rest} />
</Provider>
);
};
const makestore = () => store;
const wrapper = createWrapper(makestore);
export default wrapper.withRedux(MyApp);
But I discovered that any use of the useDispatch
Inside any page, the search engine does not recognize the content of the page after fetching the data
import React, { useEffect } from 'react';
import { Test } from '../../redux/test/tes';
import { useDispatch, useSelector } from 'react-redux';
import Link from 'next/link';
function TestPage() {
const dispatch = useDispatch();
const { data } = useSelector((state) => state.test);
useEffect(() => {
dispatch(Test('hi'));
}, []);
return (
<div>
<Link href="/">
<a>home</a>
</Link>{' '}
{data.map((name) => (
<h1>{name.title}</h1>
))}
</div>
);
}
export default TestPage;
One of the next pre-render methods must be used
I wonder if this is normal with next
or there Is a better way for doing that?
#1 Update
Now after moving data fetching to getStaticProps
TestPage.js
import React from 'react';
import { Test } from '../../redux/test/tes';
import { useSelector } from 'react-redux';
import Link from 'next/link';
import { wrapper } from '../../redux/store';
function TestPage({ pageProps }) {
const { data } = useSelector((state) => state.test);
console.log(data);
return (
<div>
<Link href="/">
<a>home</a>
</Link>{' '}
{data && data.map((name) => (
<h1>{name.name}</h1>
))}
</div>
);
}
export const getStaticProps = wrapper.getStaticProps(
(store) => async (context) => {
const loading = store.getState().test.loading;
if (loading === 'idle') {
await store.dispatch(Test('hi'));
}
return {
props: { },
};
}
);
export default TestPage;
The problem now is that the store is not updating
useSelector return []
Although console.log (data) from getStaticProps the data is present
__NEXT_REDUX_WRAPPER_HYDRATE__
i'm stuck
#2 Update
It was really hard to get here and after that, there are still problems getting Redux with Next js
Now everything works until navigating to any page have getStaticProps or getServerProps
state getting reset automatically
store.js
import reducers from './rootReducer';
import { configureStore } from '#reduxjs/toolkit';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
const reducer = (state, action) => {
if (action.type === HYDRATE) {
let nextState = {
...state,
...action.payload,
};
return nextState;
} else {
return reducers(state, action);
}
};
const isDev = process.env.NODE_ENV === 'development';
const makeStore = (context) => {
let middleware = [];
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(middleware),
devTools: isDev,
preloadedState: undefined,
});
return store;
};
export const wrapper = createWrapper(makeStore, { debug: isDev });
In the end, this way only worked. Even the server and Client state separation did not work.
I used this jsondiffpatch.
rootReducer.js
const rootReducer = createReducer(
combinedReducers(undefined, { type: '' }),
(builder) => {
builder
.addCase(HYDRATE, (state, action) => {
const stateDiff = diff(state, action.payload);
const isdiff = stateDiff?.test?.data?.[0];
const isdiff1 =
stateDiff?.test1?.data?.[0]
return {
...state,
...action.payload,
test: isdiff ? action.payload.test : state.test,
test1: isdiff1 ? action.payload.test1 : state.test1,
};
})
.addDefaultCase(combinedReducers);
}
);
The only problem here is that you have to test every change in every piece inside the state
Update
Because a global hydrate reducer can be overkill, here is an example to handle hydration in each slice:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import { diff } from 'jsondiffpatch';
import { HYDRATE } from 'next-redux-wrapper';
const initialState = {
data: [],
};
export const TestFetch = createAsyncThunk(
'TestFetch',
async (data, { rejectWithValue, dispatch }) => {
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
const d = await response.json();
return d;
} catch (error) {
return rejectWithValue(error.response.data.error);
}
}
);
const test = createSlice({
name: 'test',
initialState,
reducers: {
update: {
reducer: (state, { payload }) => {
return { ...state, data: payload };
},
},
},
extraReducers: {
[HYDRATE]: (state, action) => {
const stateDiff = diff(state, action.payload);
const isdiff1 = stateDiff?.server?.[0]?.test?.data?.[0];
// return {
// ...state,
// data: isdiff1 ? action.payload.server.test.data : state.data,
// };
state.data = isdiff1 ? action.payload.server.test.data : state.data;
},
[TestFetch.fulfilled]: (state, action) => {
state.data = action.payload;
},
},
});
export const { update } = test.actions;
export default test.reducer;
1.) Does using Redux with Nextjs eliminate the SEO advantage?
No, using Redux with NextJs does not hinder the SEO advantage. Redux goes well with NextJS.
The problem lies with your implementation of the data fetching. NextJS does not see the fetched content, because you need to fetch it in either getInitialProps, getServerSideProps, or getStaticProps depending on the way you want your app to work.
See the Data Fetching documentation from NextJS.
Note that getServerSideProps and getStaticProps are the recommended ways of dealing with data fetching.
If you go for getStaticProps, you will need getStaticPaths. Check this answer to see use cases and the difference between the getStaticPaths and getStaticProps as it can be confusing.
TLDR; Instead of putting the data fetching in a useEffect hook, move it inside a getServerSideProps or a getStaticProps function.
I am trying to implement Redux in a Next.js app and have problems getting the dispatch function to work in getInitialProps. The store is returned as undefined for some reason that I cannot figure out. I am using next-redux-wrapper. I have followed the documentation on next-redux-wrapper GitHub page but somewhere on the way it goes wrong. I know the code is working - I used axios to directly fetch the artPieces and then it worked just fine but I want to use Redux instead. I am changing an react/express.js app to a Next.js app where I will use the API for the basic server operations needed. This is just a small blog app.
Here is my store.js:
import { createStore } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
// create your reducer
const reducer = (state = { tick: 'init' }, action) => {
switch (action.type) {
case HYDRATE:
return { ...state, ...action.payload };
case 'TICK':
return { ...state, tick: action.payload };
default:
return state;
}
};
// create a makeStore function
const makeStore = (context) => createStore(reducer);
// export an assembled wrapper
export const wrapper = createWrapper(makeStore, { debug: true });
And here is the _app.js:
import './styles/globals.css';
import { wrapper } from '../store';
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default wrapper.withRedux(MyApp);
And finally here is where it does not work. Trying to call dispatch on the context to a sub component to _app.js:
import React from 'react';
import { ArtPiecesContainer } from './../components/ArtPiecesContainer';
import { useDispatch } from 'react-redux';
import axios from 'axios';
import { getArtPieces } from '../reducers';
const Art = ({ data, error }) => {
return (
<>
<ArtPiecesContainer artPieces={data} />
</>
);
};
export default Art;
Art.getInitialProps = async ({ ctx }) => {
await ctx.dispatch(getArtPieces());
console.log('DATA FROM GETARTPIECES', data);
return { data: ctx.getState() };
};
This should probably work with "next-redux-wrapper": "^7.0.5"
_app.js
import { wrapper } from '../store'
import React from 'react';
import App from 'next/app';
class MyApp extends App {
static getInitialProps = wrapper.getInitialAppProps(store => async ({Component, ctx}) => {
return {
pageProps: {
// Call page-level getInitialProps
// DON'T FORGET TO PROVIDE STORE TO PAGE
...(Component.getInitialProps ? await Component.getInitialProps({...ctx, store}) : {}),
// Some custom thing for all pages
pathname: ctx.pathname,
},
};
});
render() {
const {Component, pageProps} = this.props;
return (
<Component {...pageProps} />
);
}
}
export default wrapper.withRedux(MyApp);
and Index.js
import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { END } from 'redux-saga'
import { wrapper } from '../store'
import { loadData, startClock, tickClock } from '../actions'
import Page from '../components/page'
const Index = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(startClock())
}, [dispatch])
return <Page title="Index Page" linkTo="/other" NavigateTo="Other Page" />
}
Index.getInitialProps = wrapper.getInitialPageProps(store => async (props) => {
store.dispatch(tickClock(false))
if (!store.getState().placeholderData) {
store.dispatch(loadData())
store.dispatch(END)
}
await store.sagaTask.toPromise()
});
export default Index
For the rest of the code you can refer to nextjs/examples/with-redux-saga, but now that I'm posting this answer they're using the older version on next-redux-wrapper ( version 6 ).
Considering the following project setup on a react-redux application that uses context API to avoid prop drilling. The example given is simplified.
Project Setup
React project uses React Redux
Uses context API to avoid prop drilling in certain cases.
Redux store has a prop posts which contains list of posts
An action creator deletePost(), which deletes a certain post by post id.
To avoid prop drilling, both posts and deletePosts() is added to a context AppContext and returned by a hook funciton useApp().
posts array is passed via contexts so it is not used by connect() function. Important
Problem:
When action is dispatched store is updated however Component is not re-rendered (because the prop is not connected?). Of course, if I pass the prop with connect function and drill it down to child rendering works fine.
What is the solution?
Example Project
The example project can be found in codesandbox. Open up the console and try to click the delete button. You will see no change in the UI while you can see the state is updated in the console.
Codes
App.js
import Home from "./routes/Home";
import "./styles.css";
import { AppProvider } from "./context";
export default function App() {
return (
<AppProvider>
<div className="App">
<Home />
</div>
</AppProvider>
);
}
context.js
import { useDispatch, useStore } from "react-redux";
import { useContext, createContext } from "react";
import { deletePost } from "./redux/actions/posts";
export const AppContext = createContext();
export const useApp = () => {
return useContext(AppContext);
};
export const AppProvider = ({ children }) => {
const dispatch = useDispatch();
const {
posts: { items: posts }
} = useStore().getState();
const value = {
// props
posts,
// actions
deletePost,
dispatch
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
Home.js
import { connect } from "react-redux";
import Post from "../components/Post";
import { useApp } from "../context";
const Home = () => {
const { posts } = useApp();
return (
<section>
{posts.map((p) => (
<Post key={p.id} {...p} />
))}
</section>
);
};
/*
const mapProps = ({ posts: { items: posts } }) => {
return {
posts
};
};
*/
export default connect()(Home);
Post.js
import { useApp } from "../context";
const Post = ({ title, content, id }) => {
const { deletePost, dispatch } = useApp();
const onDeleteClick = () => {
console.log("delete it", id);
dispatch(deletePost(id));
};
return (
<article>
<h1>{title}</h1>
<p>{content}</p>
<div className="toolbar">
<button onClick={onDeleteClick}>Delete</button>
</div>
</article>
);
};
export default Post;
You're not using the connect higher order component method properly . Try using it like this so your component will get the states and the function of your redux store :
import React from 'react';
import { connect } from 'react-redux';
import { callAction } from '../redux/actions.js';
const Home = (props) => {
return (
<div> {JSON.stringify(props)} </div>
)
}
const mapState = (state) => {
name : state.name // name is in intialState
}
const mapDispatch = (dispatch) => {
callAction : () => dispatch(callAction()) // callAction is a redux action
//and should be imported in the component also
}
export default connect(mapState , mapDispatch)(Home);
You can access the states and the actions from your redux store via component props.
Use useSelector() instead of useState(). Example codepen is fixed.
Change from:
const { posts: { items: posts } } = useStore().getState();
Change to:
const posts = useSelector(state => state.posts.items);
useStore() value is only received when component is first mounted. While useSlector() will get value when value is changed.
I am pretty new to Next.JS and I was trying to set up Redux with my Next.JS application. Now my page is supposed to display a list of posts that I am calling in from an API. The page renders perfectly when I'm dispatching from useEffect() to populate the data on to my page, but getStaticProps() or getServerSideProps() are not working whatsoever!
Here is a bit of code that will give you a hint of what I've done so far:
store.js
import { useMemo } from 'react'
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import rootReducer from './reducers/rootReducer'
const initialState = {}
const middlewares = [thunk]
let store
function initStore(preloadedState = initialState) {
return createStore(
rootReducer,
preloadedState,
composeWithDevTools(applyMiddleware(...middlewares))
)
}
export const initializeStore = (preloadedState) => {
let _store = store ?? initStore(preloadedState)
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
})
store = undefined
}
if (typeof window === 'undefined') return _store
if (!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
action.js
export const fetchPosts = () => async dispatch => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts')
dispatch({
type: FETCH_POSTS,
payload: res.data
})
}
_app.js
import { Provider } from 'react-redux'
import { createWrapper } from 'next-redux-wrapper'
import { useStore } from '../redux/store'
export default function MyApp({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState)
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
These are the files that I needed for the basic redux setup. Once my store was set up and I wrapped my app around the Provider, I initially though of using useEffect() hook to populate data on a component that was rendering inside my index.js file.
component.js
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts } from '../redux/actions/postsAction'
const Posts = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.posts)
useEffect(() => {
dispatch(fetchPosts())
}, [])
return (
<div className="">
<h1>Posts</h1>
{items.map(post => {
return (<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>)
})}
</div>
)
}
export default Posts
This worked perfectly! All my posts were showing up inside the component. The problem occurred when I was trying to achieve the same behaviour with server side rendering (or even SSG). I wanted to populate the data during the pre-render phase but for some reason the items array which is supposed to hold all the data is empty, basically meaning that the disptacher was never called! Here is the piece of code that is bothering me (exactly same as previous code, but this time I'm using getStaticProps() instead of useEffect()):
component.js
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts } from '../redux/actions/postsAction'
const Posts = ({ items }) => {
return (
<div className="">
<h1>Posts</h1>
{items.map(post => {
return (<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>)
})}
</div>
)
}
export async function getStaticProps() {
console.log('Props called')
const dispatch = useDispatch()
const { items } = useSelector(state => state.posts)
dispatch(fetchPosts())
console.log(items)
return { props: { items } }
}
export default Posts
By running this, I'm getting an error that items is empty! Please help me, I have no clue what's going wrong here.
Well I fixed this issue myself but I forgot to post an answer for it, my bad!
The problem here really is very simple, hooks don't work outside of a functional component!
I think, inside of getStaticProps just call API or get datas from DB and returns it as props to pages/index.js (any component you want) and inside of this component we can get datas from getStaticProps as props.
Also we can set it as global state using useDispatch of react-redux. After that any component we can call those states using redux mapStateToProps. This is my solution.
This maybe a solution if anyone faced this problem,
import React from 'react';
import {useSelector} from 'react-redux';
import {wrapper} from '../store';
export const getStaticProps = wrapper.getStaticProps(store => ({preview})
=> {
console.log('2. Page.getStaticProps uses the store to dispatch things');
store.dispatch({
type: 'TICK',
payload: 'was set in other page ' + preview,
});
});
// you can also use `connect()` instead of hooks
const Page = () => {
const {tick} = useSelector(state => state);
return <div>{tick}</div>;
};
export default Page;
Got it from here: https://github.com/kirill-konshin/next-redux-wrapper