how to call useSelector Hook only when the data is available - javascript

I am making a API call in useEffect Hook and then saving the data in redux store. From redux store I am storing data in my browser local storage by making a useSelector call to get data from redux store.
How to make useSelector to call only once the data is ready from API.
import React, { useEffect, useState } from "react";
import Navbar from "./Navbar";
import Footer from "./Footer";
import HomeMenu from './HomeMenu';
import { fetchingInPending, fetchingSuccess, fetchingFailed } from './HomeSlice';
import { useSelector, useDispatch } from 'react-redux';
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
(async () => {
dispatch(fetchingInPending());
const response = await fetch("https://localhost:44316/api/auth/getuser", {
headers: {
"Content-Type": "application/json"
},
credentials: "include",
});
if(response.status === 200){
const content = await response.json();
dispatch(fetchingSuccess({name: content.name, role: content.role}));
}
else{
dispatch(fetchingFailed());
}
})();
},[]);
localStorage.setItem('user', JSON.stringify(useSelector(state => state.userDetails)));
const user = localStorage.getItem('user');
console.log(user);
return (
<React.Fragment>
<h1>Home</h1>
</React.Fragment>
)};
HomeSlice
import { createSlice } from "#reduxjs/toolkit";
export const homeSlice = createSlice({
name: "userDetails",
initialState: {
name: "",
role: "",
isLoading: false
},
reducers: {
fetchingInPending: (state) => {
state.isLoading = true;
},
fetchingSuccess: (state, action) => {
state.name = action.payload.name;
state.role = action.payload.role;
state.isLoading = false;
state.error = "";
},
fetchingFailed: (state, action) => {
state.isLoading = false;
state.error = action.payload.error;
},
},
});
export const { fetchingInPending, fetchingSuccess, fetchingFailed } = homeSlice.actions;
export default homeSlice.reducer;
On my browser console I am getting data after three calls.
Index.js contain:
Index.js code:
import React from 'react';
import ReactDOM from 'react-dom';
import './style/index.css';
import App from './App';
import { BrowserRouter} from 'react-router-dom';
import store from './store';
import { Provider } from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);

I think #slideshowp2 is correct, but for incorrect reasoning. There are no asynchronous actions being dispatched (i.e. thunks or similar), so there are no returned Promises to be fulfilled.
Your code
Logs the initial state
Logs the state after dispatch(fetchingInPending());
Logs the third state update after dispatch(fetchingSuccess({ name: content.name, role: content.role }));
You can't conditionally call React hooks, so your code is correctly persisting each state update to localStorage.
If you want to conditionally persist data to localStorage then place that logic in another useEffect hook with a dependency on the redux state. The condition is ensuring that the values from your redux store have been populated.
const userDetails = useSelector(state => state.userDetails);
...
useEffect(() => {
const { name, role } = userDetails;
if (name && role) {
localStorage.setItem('user', JSON.stringify({ name, role }));
}
}, [userDetails]);
Alternatively you can just apply the localStorage persistence right in the existing useEffect. Here you persist to localStorage only when also dispatching the success action
useEffect(() => {
(async () => {
dispatch(fetchingInPending());
const response = await fetch("https://localhost:44316/api/auth/getuser", {
headers: {
"Content-Type": "application/json"
},
credentials: "include",
});
if(response.status === 200){
const { name, role } = await response.json();
const userDetails = { name, role };
dispatch(fetchingSuccess(userDetails)); // <-- dispatch user details
localStorage.setItem( // <-- persist user details
'user',
JSON.stringify(userDetails)
);
}
else{
dispatch(fetchingFailed());
}
})();
},[]);

Related

Data is not being stored in redux store

store imageI am going to store the data into the react-redux-store but it is not getting stored. I don't understand what I am missing...I have given my code below.
i am trying to store the data from the api but it is not working...
INDEX.JS
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./features/store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
STORE.JS
import { configureStore } from "#reduxjs/toolkit";
import moviesReducer from "./movies/movieSlice";
export const store = configureStore({
reducer: moviesReducer,
});
MOVIE SLICE.JS
import { createSlice } from "#reduxjs/toolkit";
const initialstate = {
movies: [],
};
const movieSlice = createSlice({
name: "movies",
initialstate,
reducers: {
addMovies: (state, { payload }) => {
state.movies = payload;
},
},
});
export const { addMovies } = movieSlice.actions;
// export const getAllMovies = (state) => state.movies.movies;
export default movieSlice.reducer;
COMPONENT
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import MovieAPI from "../config/MovieAPI";
import { addMovies } from "../features/movies/movieSlice";
const Home = () => {
const dispatch = useDispatch();
const fetchMovies = async () => {
const response = await MovieAPI.get(`?apiKey=1234&s=harry&type=movie`);
console.log(response.data);
dispatch(addMovies(response.data));
};
useEffect(() => {
fetchMovies();
}, []);
For the very first: createSlice expecting to recieve object with property named initialState instead initialstate, notice camelCase naming.
The next one: acording to location and slice name "movies" I may suspect you should define it as: const initialState = [];, due to it is "movies slice" initial state definition itself, otherwise you will have state with something like
state = {movies: {movies: []}}.
Also, you may wish to rewrite addMovies reducer in something like:
addMovies: (moview_slice_state, { payload }) => {
console.log("add movies", payload);
moview_slice_state.push(...payload);
}
where moview_slice_state - state of movies slice of whole state, e.g. state.movies.
By the way, due to #reduxjs/toolkit use immer under the hood you may "modify" state OR return new state, as Andrej Kirejeŭ propose. But NOT the both of them.
P.S. For the future, feel free to create minimal demo for your question or answer, some thing like live demo based on your code
return new state:
addMovies: (state, { payload }) => ({
...state,
movies: payload
}),
by the way, how do you know it is not stored. Please, show the code where you use state data to render some component.

Redux toolkit store reset automatically in navigating between pages in next js

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.

Delay in Authentication using React

I use React for frontend and Node.js in backend and Postgre for database.
I have create my own API for authenticating user and using useState and useContext hook to store the login status of the user.
I also setup a Redirect function after successful login but the useState is taking a while to update the login status of user and because of that the page is not being redirect.
I tried using async and await while fetching the data from the server but still there is delay in authenticating the user.
I also tried to follow some blogs like this
This context state handle the login functionality and update the login status within the component.
import React, { createContext, useContext, useState } from "react";
import { GlobalContext } from "./GlobalState";
export const LoginAuth = createContext();
export const ProvideAuth = ({ children }) => {
const auth = useProvideAuth();
return <LoginAuth.Provider value={auth}>{children}</LoginAuth.Provider>;
};
export const useAuth = () => {
return useContext(LoginAuth);
};
const useProvideAuth = () => {
const [user, setUser] = useState(null);
const { setIsLogin } = useContext(GlobalContext);
const login = async (userDetails) => {
const response = await fetch("http://localhost:4000/api/v1/login/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userDetails),
}).catch((err) => console.log(err.message));
const data = await response.json();
if (data?.error) {
setUser(false);
} else {
setUser(data);
setIsLogin(true);
}
};
return { user, login };
};
This state used to update the login state throughout the app
import React, { createContext, useState } from "react";
export const GlobalContext = createContext();
export const GlobalProvider = ({ children }) => {
const [isLogin, setIsLogin] = useState(false);
return (
<GlobalContext.Provider value={{ isLogin, setIsLogin }}>
{children}
</GlobalContext.Provider>
);
};
Private Route Code
import React, { useContext } from "react";
import { Redirect, Route } from "react-router";
import { GlobalContext } from "../State/GlobalState";
const PrivateRouteLogin = ({ children, ...rest }) => {
const { isLogin } = useContext(GlobalContext);
return (
<Route
{...rest}
render={({ location }) => {
return isLogin ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location },
}}
></Redirect>
);
}}
/>
);
};
export default PrivateRouteLogin;
You can have an another value for your loginStatus
false = not logged in
true = logged in
pending = for collect all data
in this situation you can have a backdrop(loading) that show over your website till detect loginStatus

React js, Cannot read property "0". When calling an API

I cannot able to access the data from the fetch function. I want to pass the data from action to reducer. API is called using an fetch function, api is returned in the form of promise. So, API is called separately and data is returned back to the action payload.
import { INDEX_PRESCRIPTION } from '../constant.js';
function fetch_prescription(){
const base_url= "http://192.168.1.22:3000/api/v1/";
const fetch_url = `${base_url}/prescriptions`;
let datas = [];
return fetch(fetch_url, {
method: "GET"
})
.then(response => response.json())
.then(data => {
datas.push(data['prescriptions'])
return datas
})
}
export const indexPrescription = async () => {
const action = {
type: INDEX_PRESCRIPTION,
details: await fetch_prescription()
}
return action;
console.log('action details', action.details)
}
export const getIndexPrescription = (dispatch) => {
dispatch(indexPrescription());
}
On examining the console, we get:
How to access the prescription details. I tried to access it by action.details["0"]["0"] , but results in 'Cannot read property "0" of undefined '. I have gone through many questions and solution related to this problem, but cant able to study what is going wrong with my code.
Update Here is my index.jsx component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getIndexPrescription } from '../actions/index.js';
class Index extends Component {
constructor(props){
super(props);
this.state = {
prescription: null
}
}
componentWillMount(){
this.props.getIndexPrescription();
}
render(){
return(
<h2>
Prescription Index
</h2>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ getIndexPrescription }, dispatch)
}
function mapStateToProps(state){
return {
prescription: state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Index);
And My src/index.js file is
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import {Provider} from 'react-redux';
import reducer from './reducers';
import Index from './components/index.jsx';
const store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<Index />
</Provider>, document.getElementById("root")
)
Your promise is resolved only after you have answer from the server. You need to use additional layer in order to handle async behavior in redux.
For example with redux-thunk, you can make it work like this:
import { INDEX_PRESCRIPTION } from '../constant.js';
function fetch_prescription(){
const base_url= "http://192.168.1.22:3000/api/v1/";
const fetch_url = `${base_url}/prescriptions`;
let datas = [];
return fetch(fetch_url, {
method: "GET"
})
.then(response => response.json())
.then(data => data['prescriptions']);
}
export const indexPrescription = (dispatch) => {
fetch_prescription()
.then(details => {
const action = {
type: INDEX_PRESCRIPTION,
details
}
dispatch(action);
}
}
The part you are missing here is that the function fetch_prescription() is asynchronous. So the data may not be available when you are accessing the data.
You are returning the datas before resolving the asnyc function return datas
You may use it as
import { INDEX_PRESCRIPTION } from '../constant.js';
function fetch_prescription(){
const base_url= "http://192.168.1.22:3000/api/v1/";
const fetch_url = `${base_url}/prescriptions`;
let datas = [];
return fetch(fetch_url, {
method: "GET"
})
.then(response => response.json())
.then(data => {
datas.push(data['prescriptions'])
return datas
})
}
export const indexPrescription = async () => {
const action = {
type: INDEX_PRESCRIPTION,
details: await fetch_prescription()
}
return action;
}
export const getIndexPrescription = (dispatch) => {
dispatch(indexPrescription());
}
And dispatch the above action where ever you want.
Call getIndexPrescription() in componentWillMount
Find the code below to add redux-thunk to your application.
...
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
...
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
<Provider store={store}>
...
</Provider>

Redux firing undefined action while using redux thunk

This issue likely stems from a misconfiguration of redux-thunk or a misunderstanding of how to write a thunk. I've tried a lot of different ways, but from what I can tell, this should work. However, I'm still getting a console message that says its firing a redux action of undefined.
Here is my store configuration
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import App from './components/App';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('rootElement')
);
Here is my action:
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState.isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState.about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Here is me firing the action in my component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../../actions/about';
import getAboutContent from '../../reducers';
class AboutMe extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.getAboutContent();
}
render() {
return <div>{ this.props.content }</div>
}
}
const mapStateToProps = (state) => ({
content: {} || getAboutContent(state)
})
const mapDispatchToProps = (dispatch) =>
bindActionCreators({ getAboutContent }, dispatch)
export default connect(
mapStateToProps, mapDispatchToProps
)(AboutMe);
I've tried quite a few configurations for mapDispatchToProps, i.e. connect(..., { fetchData: getAboutContent })..., and more. Any help is greatly appreciated.
Edit:
Here is the git repo, if that is helpful to anybody: https://github.com/sambigelow44/portfolio-page
Check your reducer name,you export fetchAboutContent, but import getAboutContent.
Code in action file is seems to be incorrect.
getState is a function.
const state = getState();
Change below code.
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState().isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState().about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Also you need to return promise from axios call, just add return statement.
return axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));

Categories