Redux reducer not Updating State in Next.js server-side-rendering - reactjs

Problem: Action is dispatching and it's checked by reducer action.type. but after checking the reducer not updating the state. reducer returning default state.
Action is dispatching from [id].js
const RoomDetailsPage = () => {
const router = useRouter();
const { id } = router.query;
return <div>
<RoomDetails/>
</div>;
};
export const getServerSideProps= wrapper.getServerSideProps(
(store)=>(
async ({ req, res, query })=>{
store.dispatch(getRoomDetails(query?.id))
}
)
)
export default RoomDetails;
roomAction.js
export const getRoomDetails = (id) => async (dispatch) => {
try {
const { data } = await getRoomDetailsApi(id);
dispatch({
type: ROOM_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ROOM_DETAILS_FAIL,
payload: error?.response?.data?.message,
});
}
};
roomReducer.js
type case is matching in switch block, also payload coming with action. but reducer not updating the state.
export const roomDetailsReducer = (state = { room: null, error:null}, action) => {
switch (action.type) {
case ROOM_DETAILS_SUCCESS:
return {room:{hi:'hello'}};
case ROOM_DETAILS_FAIL:
return {error: action.payload};
case CLEAR_ERRORS:
return {error: null};
default:
return state;
}
};
combineReducers code in reducer.js
import { combineReducers } from "redux";
import { allRoomsReducers, roomDetailsReducer } from "./roomReducers";
const reducers = combineReducers({
allRooms: allRoomsReducers,
roomDetails: roomDetailsReducer,
})
export default reducers
Note: allRoomsReducers code working fine.

Related

Why doesn't useEffect hook work on page refresh?

I'm working on a react project. I have my own API to fetch information. I'm using the useEffect hook to fetch profile information from API. My problem is when page mounts for the first time i can fetch the data with no problem but if i refresh the page it doesn't work at all. I know i have to give a second parameter to useEffect. I tried to put profile as the second argument even dispatched the getCurrentProfile function but when i do that it constantly fires off fetch request. I would be glad if anyone can help me with that. Thanks.
Here is my Profile component:
export const Profile = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getCurrentProfile());
}, [])
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
const { user } = authReducer;
console.log("loading", loading)
console.log("profile", profile)
return loading && profile === null ? (
<div >
<Spinner />
</div>
) :
Here is my Profile action:
export const getCurrentProfile = () => async dispatch => {
try {
const res = await axios.get("/api/profile/me");
console.log(res);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
})
} catch (err) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: err.response.statusText, status: err.response.status }
})
}
}
Here is my profile reducer:
export default (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case "GET_PROFILE":
return {
...state,
profile: payload,
loading: false
}
case "PROFILE_ERROR":
return {
...state,
error: payload,
profile: null
}
case "CLEAR_PROFILE":
return {
...state,
profile: null,
loading: false
}
default:
return state;
}
}
You might want to try adding conditional logic within the useEffect so you only trigger the dispatch if you don't already have a profile.
import "./styles.css";
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useCallback } from "react";
import { getCurrentProfile } from "./action";
export const Profile = () => {
const dispatch = useDispatch();
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
// read more about this here: https://stackoverflow.com/questions/58624200/react-hook-useeffect-has-a-missing-dependency-dispatch
const stableDispatch = useCallback(dispatch, []);
useEffect(() => {
if (!profile) {
stableDispatch(getCurrentProfile());
}
}, [profile, stableDispatch]);
const { user } = authReducer;
console.log("loading", loading);
console.log("profile", profile);
return loading && profile === null ? <div>Spinner</div> : "Actual Profile";
};
export default Profile;
Also, it doesn't seem like you're currently doing anything with the loading piece of state–at least from what you've shared here. You might want to dispatch an action indicating that you're loading before you start the fetch and then it will be set to false when you get the response.
Check out this codesandbox for reference: https://codesandbox.io/s/focused-kilby-gd2nr?file=/src/App.js
Reducers:
const initialState = {
profile: null,
loading: false
};
export const profile = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case "LOADING_PROFILE":
return {
...state,
loading: true
};
case "GET_PROFILE":
return {
...state,
profile: payload,
loading: false
};
case "PROFILE_ERROR":
return {
...state,
error: payload,
profile: null
};
case "CLEAR_PROFILE":
return {
...state,
profile: null,
loading: false
};
default:
return state;
}
};
export const auth = (state = {}, action) => {
return state;
};
Action Creator:
import axios from "axios";
export const getCurrentProfile = () => async (dispatch) => {
try {
dispatch({ type: "LOADING_PROFILE" });
const res = await axios.get("https://jsonplaceholder.typicode.com/users/1");
console.log(res);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
});
} catch (err) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { profile, auth } from "./reducers";
import App from "./App";
import thunk from "redux-thunk";
const store = createStore(
combineReducers({
profile,
auth
}),
applyMiddleware(thunk)
);
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
Well i solved it by dispatching 'getCurrentProfile' not 'getCurrentProfile()' turns out using it like a function causes continuously firing off.
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
const dispatch = useDispatch();
useEffect(() => {
if (!profile) {
console.log("It worked")
dispatch(getCurrentProfile());
}
}, [dispatch(getCurrentProfile)])

action payload is not dispatched to reducer

Using react-redux to get items from my database. My reducer is receiving action.type but not action.payload from action. As shown in the redux developer tool here: The response from my database api is working and I have already applied my redux-thunk into my store using applyMiddleware.
Home.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { getAvailableItems } from '../Redux/Actions/ItemAction'
componentDidMount() {
this.props.getAvailableItems()
this.props.fetchPosts()
}
render() {
console.log(this.props)
return (<div></div>)
}
const mapStateToProps = state => {
return {
item : state.item.items
}
}
const mapActionsToProps = {
getAvailableItems,
fetchPosts
}
export default connect(mapStateToProps, mapActionsToProps)(Home)
ItemAction.js
export const getAvailableItems = () => dispatch => {
console.log("running itemAction")
fetch('https://us-central1-firebaselink.cloudfunctions.net/api/items')
.then(
(res) => {
//console.log(res.json())
res.json()
})
.then(data => dispatch(
{
type : 'GET_ALL_AVAILABLE_ITEMS',
payload : data
//console.log(data)
}
))
.catch((err) => console.log(err));
}
itemReducer.jsx
const initState = {
items : []
}
const itemReducers = (state = initState, action) => {
//return state;
console.log(action.type)
switch(action.type){
case 'GET_ALL_AVAILABLE_ITEMS':
console.log("this is the payload : "+action.payload)
return{
...state,
items: action.payload
}
default:
return state;
}
}
export default itemReducers;

how to render updated state in react-redux?

I am trying to update state and render the updated value in my component but unable to achieve it.
<--Here is my action -->
export const fetchProducts = () => async (dispatch) => {
const res = await fakeApi.get("/products");
console.log(res.data);
dispatch({
type: FETCH_PRODUCTS,
payload: res.data,
});
};
<--Here is the reducer -->
const initialState = {
products: [],
};
export default (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case FETCH_PRODUCTS:
return { ...state, products: payload };
default:
return state;
}
};
<--Here is the component -->
const Products = ({ products, fetchProducts }) => {
const [productsList,setProductsList]=useState([])
useEffect(() => {
fetchProducts();
console.log(products);
setProductsList({productsList:products})
}, []);
return <ProductList products={productsList} />;
};
const mapStateToProps = (state) => ({
products: state.products,
});
export default connect(mapStateToProps, { fetchProducts })(Products);
<-- This is the first time I am using redux. I don't know where I am going wrong.All i am getting is undefined in the state. -->
you are using react hooks for a function-based component, using useSelector() hook instead of mapStateToProps might be easier in this specific scenario to get data from redux store, as i can't see what you've named your reducer in the root reducer, i'm assuming it's named productReducer, try this in your component:
import {useSelector} from 'react-redux'
...
let products = useSelector(state=>state.productReducer.Products)

Redux Thunk action with axios returning multiple values

I have a React app that uses redux-thunk and axios to fetch an API. The action fires successfully, but returns multiple payloads which means it is firing multiple times.
How can I make it fire only one time?
Code
Actions
import Axios from "axios";
import { fetchEnglishLeagueTable } from "./ActionTypes";
export function fetchEnglishTable() {
var url = "https://api.football-data.org/v2/competitions/PL/matches";
var token = "52c8d88969d84ac9b17edb956eea33af";
var obj = {
headers: { "X-Auth-Token": token }
};
return dispatch => {
return Axios.get(url, obj)
.then(res => {
dispatch({
type: fetchEnglishLeagueTable,
payload: res.data
});
})
.catch(e => {
console.log("Cant fetch ", e);
});
};
}
Reducers
import { fetchEnglishLeagueTable } from "../actions/ActionTypes";
const initialState = {
EnglishTable: {}
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case fetchEnglishLeagueTable:
return {
...state,
EnglishTable: action.payload
};
default:
return state;
}
};
export default rootReducer;
Page
const League = props => {
useEffect(() => {
props.getLeagueTable();
}, [props.leagueTable]);
console.log(props.leagueTable);
return <p>ihi</p>;
};
const mapStateToProps = state => ({
leagueTable: state.EnglishTable
});
const mapDispatchToProps = dispatch => {
return { getLeagueTable: () => dispatch(fetchEnglishTable()) };
};
export default connect(mapStateToProps, mapDispatchToProps)(League);
Store
import rootReducer from "./Reducer";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Here is what it returns
Just remove leagueTable from useEffect's dependency array, so it'll fetch them only once component is mounted. Because now you have a loop:
Get leagues -> leagueTable updates -> useEffect sees that leagueTable changed in dependency array and calls to get leagues again and we've got a loop.
const League = props => {
useEffect(() => {
props.getLeagueTable();
}, []); // <~ no props.leagueTable here
console.log(props.leagueTable);
return <p>ihi</p>;
};
Hope it helps :)

Dispatch not changing redux state

I am fairly new to redux, and I am running into a problem.
I am trying to implement flash messages to my login page, but redux's dispatch is not changing the UI State.
I want a flash message to appear on the login page after user successfully register.
//login.js
class Login extends Component{
renderMessage() {
if (this.props.flashMessageType== "registrationComplete"){
return (
<Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
);
} else {
return (null);
}
}
render() {
return ({
this.renderMessage()
});
}
}
function mapStateToProps(state) {
return {
flashMessageType:state.flashMessage.flashType,
};
}
export default connect(mapStateToProps, actions)(Login);
Here is the reducer
const initialState = {
flashType: "",
};
export default function(state = {initialState}, action){
switch(action.type){
case USER_REGISTER:
return [
...state,
{
flashType:"registrationComplete"
}
];
default:
return initialState;
}
}
and here is the actions
export const submitForm = (values,history) => async dispatch => {
const res = await axios.post('/api/signup', values);
history.push('/');
dispatch({type: FETCH_USER, payload: res.data});
dispatch({type: USER_REGISTER});
};
I appreciate your help.
Thanks,
Vincent
As Amr Aly mentioned (and now soroush), you're essentially mutating the state when you do:
return[ ...state, { flashType:"registrationComplete" }]
What you really want is:
return { ...state, flashMessage: "registrationComplete" }
Also, some of your code is a bit redundant and/or missing some important instructions (like try/catch blocks).
What your code should look like:
FlashMessage.js
import React, { PureComponent } from 'react';
import Message from '../some/other/directory';
import actions from '../some/oter/directory':
class Login extends PureComponent {
render = () => (
this.props.flashMessage == "registrationComplete"
? <Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
: null
)
}
export default connect(state => ({ flashMessage: state.auth.flashMessage }), actions)(Login)
reducers.js
import { routerReducer as routing } from 'react-router-redux';
import { combineReducers } from 'redux';
import { FETCH_USER, USER_REGISTER } from '../actions/types';
const authReducer = (state={}, ({ type, payload }) => {
switch(type){
case FETCH_USER: return { ...state, loggedinUser: payload };
case USER_REGISTER: return { ...state, flashMessage: "registrationComplete" }
default: return state;
}
}
export default = combineReducers({
auth: authReducer,
routing
});
actions.js
import { FETCH_USER, USER_REGISTER } from './types';
export const submitForm = (values,history) => async dispatch => {
try {
const {data} = await axios.post('/api/signup',values);
dispatch({ type:FETCH_USER, payload: data });
dispatch({ type:USER_REGISTER });
history.push('/');
catch (err) {
console.error("Error: ", err.toString());
}
};
Your reducer should be:
const initialState = {
flashType: "",
};
export default function(state = initialState, action){
switch(action.type){
case USER_REGISTER:
return {
...state,
flashType: "registrationComplete",
};
default:
return state;
}
}

Resources