I am working on project where I am stuck in this problem. The thing is, I am calling an axios API and after its success I want to update my redux state i.e. in the .then() chain of axios. How can I achieve that? As what I have tried by applying what I know is -> I have created a react-redux dispatch in my component. I know how to do this in normal onClick but in then method I don't know how to trigger that.
I have tried doing this:
let submitForm = (e) => {
e.preventDefault();
// Axios request
const url = 'http://localhost:5000/api/v1/users/login'
axios({
//Api details
})
.then(res => {
// Store API data in LocalStorage
})
.then(() => {
LogIN(); // Here I want to change redux state //
history.push('/dashboard')
})
}
--Component
function Signin({LogIN}) {
return (
)
}
const mapDispatchToProps = dispatch => {
return {
LogIN: () => dispatch(login_action())
}
}
export default connect(null , mapDispatchToProps)(Signin)
After doing this, I see same state with no difference
Here is redux:
const login_action = () => {
return {
type : 'LOG-IN'
}
}
const loginLogOutReducer = (state = false, action) => {
switch (action.type) {
case 'LOG_IN':
return !state
default:
return state
}
}
const AllReducers = combineReducers({
isLoggedIn : loginLogOutReducer
})
You can use redux-thunk and function component in react hook
App.js
import {Provider} from 'react-redux'
import store from './store'
<Provider store={store()}>
<AppComponent />
</Provider>
store.js
import {applyMiddleware, compose, createStore} from 'redux'
import thunk from 'redux-thunk'
import {initialState, rootReducer} from './reducers'
const store = () => {
return createStore(rootReducer, initialState, compose(applyMiddleware(thunk)))
}
export default store
reducer.js
import {actionTypes} from './actionTypes'
const initialState = {}
const rootReducer = (state = initialState, action) => {
if (action.type === actionTypes.STH) {
return {
...state,
sth: action.payload,
}
}
}
export {initialState, rootReducer}
actionTypes.js
export const actionTypes = {
STH: 'STH'
}
Component
...
const onChange = => {
dispatch(actionFunc()).then(res => {
// DO Something
})
...
action.js
const actionFunc = () => {
return (dispatch, getState) => {
return axios({
//Api details
}).then(res => res).catch(err => err)
}
}
Related
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.
For some reason, even though the reducer runs and console.log shows that the correct data was passed to it, the redux store was not updated.
Relevant files:
App.jsx
import {Provider} from 'react-redux';
import store from './store';
const Stack = createStackNavigator();
export default class App extends Component {
render() {
return (
<Provider store={store()}>
Store.js
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
const store = (initialState = {}) =>{
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
)
}
export default store;
Register.tsx
...
<Pressable
style={styles.button}
onPress={() => this.props.submitRegistration(this.state)}
>
...
const mapDispatchToProps = (dispatch: any) => {
return {
submitRegistration: (data: any) => {
dispatch(UserActions.submitRegister(data))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Register);
UserActions
import { signUp } from '../../services/backend';
import { setUser } from '../../actions';
export function submitRegister(data: any) {
return async (dispatch: any) => {
const response = await signUp(data);
const responseData = await response.json();
if(responseData.token) {
console.log('here', responseData);
dispatch(setUser(responseData.user));
}
};
}
Action creator
export const setUser = (user: any) => ({
type: 'SET_USER',
user
});
User Reducer
import { SET_USER } from "../actions/actionTypes"
const initialState = {
user: {}
}
const User = (state = initialState, action: any) => {
switch(action.type) {
case SET_USER:
console.log('here action', action.user);
return { user: action.user}
default:
return state
}
}
export default User;
I would really appreciate any help possible. Seems like I misconfigured in someway because even when I set initial state :
const initialState = {
user: {firstName: "John"}
}
it's not reflected in the redux store.
In your action creator:
export const setUser = (user: any) => (
return {
type: 'SET_USER',
user
});
I'm using React-Laravel for my project.
The problem is when I tried to use redux-thunk for the asynchronous dispatch function.
My dispatch function won't get executed.
Please do help me figure out this problem.
I have already tried to use promise or redux-devtools-extension library
https://codeburst.io/reactjs-app-with-laravel-restful-api-endpoint-part-2-aef12fe6db02
app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import Layout from './jsx/Layout/Layout';
import marketplaceReducer from './store/reducers/marketplace';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const appReducer = combineReducers({
marketplace: marketplaceReducer
});
const rootReducer = (state, action) => {
return appReducer(state, action);
}
const store = createStore(rootReducer, composeEnhancers(
applyMiddleware(logger, thunk)
));
const render = (
<Provider store={store}>
<BrowserRouter>
<Layout />
</BrowserRouter>
</Provider>
);
ReactDOM.render(render, document.getElementById('root'));
marketplace.js (action)
import * as actionTypes from './actionTypes';
import axios from '../../axios';
export const loadMarketplace = () => {
console.log("Load Marketplace");
return {
type: actionTypes.LOAD_MARKETPLACE
};
}
export const successMarketplace = (data) => {
console.log("Success Marketplace");
return {
type: actionTypes.SUCCESS_MARKETPLACE,
data: data
}
}
export const failedMarketplace = () => {
console.log("Failed Marketplace");
return {
type: actionTypes.FAILED_MARKETPLACE
}
}
export const showMarketplace = () => {
console.log("Show Marketplace Action")
return dispatch => {
//This is the problem
//Inside this function, I can't see any console.log, even loadMarketplace() didn't get called.
console.log("Show Marketplace in dispatch");
dispatch(loadMarketplace());
axios.get('/marketplaces')
.then(response => {
dispatch(successMarketplace(response));
})
.catch(error => {
dispatch(failedMarketplace());
});
};
}
marketplace.js (reducer)
import * as actionTypes from '../actions/actionTypes';
const initial_state = {
data: [],
loading: false
}
const loadMarketplace = (state, action) => {
console.log("Load Marketplace Reducer")
return {
...state,
loading: true
};
}
const successMarketplace = (state, action) => {
console.log("Success Marketplace Reducer", action.data)
return {
...state,
loading: false,
data: action.data
};
}
const failedMarketplace = (state, action) => {
return {
...state,
loading: false
};
}
const reducer = (state = initial_state, action) => {
//This is called when the first init, never got it through showMarketplace() function.
console.log("Marketplace Reducer", action);
switch (action.type) {
case actionTypes.LOAD_MARKETPLACE: return loadMarketplace(state, action);
case actionTypes.SUCCESS_MARKETPLACE: return successMarketplace(state, action);
case actionTypes.FAILED_MARKETPLACE: return failedMarketplace(state, action);
default: return state;
}
}
export default reducer;
Marketplace.js (jsx view)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../../store/actions';
class Marketplace extends Component {
componentDidMount() {
console.log('[ComponentDidMount] Marketplace')
this.props.showMarketplace();
}
render() {
return (
<React.Fragment>
Marketplace
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
showMarketplace: () => dispatch(actions.showMarketplace)
};
}
export default connect(null, mapDispatchToProps)(Marketplace);
This is the result of my console.log (when loading the first time for Marketplace.js)
Please do help, I've been struggling for 2 hours or more, only because of this problem. (This is my first time using React-Laravel).
Thank you.
I already found the problem. It is not redux-thunk problem.
It is actually a normal Redux problem we found anywhere.
Marketplace.js (jsx view)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../../store/actions';
class Marketplace extends Component {
componentDidMount() {
console.log('[ComponentDidMount] Marketplace')
this.props.showMarketplace();
}
render() {
return (
<React.Fragment>
Marketplace
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
showMarketplace: () => dispatch(actions.showMarketplace) //THIS IS THE PROBLEM, IT IS NOT EXECUTING PROPERLY. THIS ONE SHOULD BE
showMarketplace: () => dispatch(actions.showMarketplace()) //SHOULD BE LIKE THIS.
};
}
export default connect(null, mapDispatchToProps)(Marketplace);
Edited: I think it is something about thunk is not added right to redux.
First of all try to add only thunk.
const store = createStore(rootReducer, composeEnhancers(
applyMiddleware(thunk)
));
If it works, maybe try to change the order of them.
I'm a bit new to using redux and react. I'm trying to make a simple API call with redux and having it render in react. I can see the API call working as it's in the payload in redux dev tools, but I can't seem to get it to update the state possibly in the `connect?.
actions/index
import FilmAPI from '../api/api';
export const FETCH_FILMS = 'FETCH_FILMS';
export const RECEIVE_FILMS = 'RECEIVE_FILMS';
export const receiveFilms = (films) => {
return {
type: RECEIVE_FILMS,
films
};
}
export const fetchFilmsRequest = () => {
return dispatch => {
return axios.get('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => {
dispatch(receiveFilms(response.data))
})
}
}
export default fetchFilmsRequest;
reducers/FilmReducer
import RECEIVE_FILMS from '../actions/index';
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return [...state, action.films];
default:
return state;
}
}
reducers/index
import { combineReducers } from 'redux';
import { films } from './FilmsReducer';
export default combineReducers({
films,
});
containers/FilmListContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchFilmsRequest } from '../actions';
import FilmList from '../components/FilmList'
class FilmListContainer extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchFilmsRequest();
}
render() {
return (
<div>
<FilmList films={this.props.films}/>
</div>
);
}
}
const mapStateToProps = state => ({
films: state.films
})
export default connect(mapStateToProps, {fetchFilmsRequest: fetchFilmsRequest})(FilmListContainer);
configureStore.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// options like actionSanitizer, stateSanitizer
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk)
);
return createStore(
rootReducer,
initialState,
enhancer
);
}
As mentioned, Redux DevTools show the films in the payload, but films still remain 0 in its state. Could anyone please point me in the right direction?
You can get updated state by subscribing store and use store.getState()
Steps:
Write subscribe function in constructor of component class.
Set state of class by store.getState().
import store from '../js/store/index';
class MyClass extends Component {
constructor(props, context) {
super(props, context);
this.state = {
classVariable: ''
}
store.subscribe(() => {
this.setState({
classVariable: store.getState().MyStoreState.storeVariable
});
});
}
}
You are close, your action needs to send the data to the store by dispatching an event which your reducer can then catch. This is done using the type attribute on the dispatch object.
https://redux.js.org/basics/actions
return fetch('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => {
dispatch({
type: RECEIVE_FILMS,
payload: response,
})
})
You then need to grab the data in your reducer and put it in the store
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return {
...state,
films: action.payload.films
};
default:
return state;
}
}
It looks like you just need to import your action type constant into your reducer using a named import instead of a default export.
i.e. import {RECEIVE_FILMS} from '../actions' rather than import RECEIVE_FILMS from '../actions'
Just dispatch result of resolved fetch promise like so:
if the payload is json, then:
export const fetchFilmsRequest = () => {
return dispatch => {
return fetch('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => response.json())
.then(response => {
dispatch({
type: RECEIVE_FILMS,
payload: response
})
})
}
Your reducer would need modifying slightly to:
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return [...action.payload]; // assuming response is jus array of films
default:
return state;
}
}
I'm new with Redux Thunk and I'm having problems with dispatch an action after fetching async call by click on button component.
actions.js
import fetch from 'isomorphic-fetch'
export const getPosts = (json) => {
return {
type: constant.GET_POSTS,
payload: {
data: json
}
}
}
export const loadPosts () => {
return (dispatch) => {
return fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => {
res.json()
}).then(json => {
dispatch(getPosts(json))
})
}
}
button.js
class Button extends React.Component {
clicked(){
console.log(this.props.loadJsonPosts()) // got undefined here
}
render() {
return(
<button onClick={this.clicked.bind(this)}>click</button>
)
}
}
buttonContainer.js
import connect from 'react-redux/lib/components/connect'
import { loadPosts } from '../actions/actions.js'
import Button from '../components/Button'
function mapDispatchToProps(dispatch) {
return {
loadJsonPosts: () => { dispatch(loadPosts()) }
}
}
export default connect(null, mapDispatchToProps)(Button)
reducer.js
import * as constant from '../constants/index'
let initialState = { postList: [] }
const reducer = (state = initialState, action) => {
switch (action.type) {
case constant.GET_POSTS: //here i call my loadPosts action
state = Object.assign({}, { postList: [{ post: action.data }] })
break;
default:
break;
}
return state
}
export default reducer
App.jsx
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import Main from './components/Main'
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import reducer from './reducers/reducer'
const store = createStore(
reducer,
applyMiddleware(thunk)
)
class App extends Component {
render() {
return(
<Provider store={store}>
<Main />
</Provider>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
I can't figure out why i get undefined, maybe I've missed something or I've wrong the approach
You forgot to return res.json() in actions.js for the next then block.
it should be
export const loadPosts () => {
return (dispatch) => {
return fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => {
return res.json();
}).then(json => {
dispatch(getPosts(json))
})
}}
or you can skip the return statement by removing the blocks by writing .then(res => res.json())
I the same issue and found that ensuring the thunk middleware was first in my chain when creating the redux store allowed me to access the promise I was after rather than getting undefined,
store = createStore(
rootReducer,
initialState,
applyMiddleware(thunk, otherMiddleware1, otherMiddleware2)
);
mapDispatchToProps should be like this:
function mapDispatchToProps(dispatch) {
return {
// loadPosts instead of loadPosts()
loadJsonPosts: () => { dispatch(loadPosts) }
} }