React hook useContext re-render component - javascript

I'm using global hooks with useContext to store signed up user information. I don't know why useContext render my component twice and state is undefined in the second time.
Provider is global, in the index.js
Any idea to prevent render twice the component?
store.js:
import React, { createContext, useReducer } from 'react';
const initialState = {
accessToken: '',
expirationDate: '',
fullName: ''
};
const store = createContext(initialState);
const { Provider } = store;
const StateProvider = ({ children }) => {
const [state, dispatch] = useReducer((state, action) => {
const { type, payload } = action;
switch (type) {
case 'login':
const newState = {
...state,
...payload
};
return newState;
default:
throw new Error();
}
}, initialState);
return <Provider value={{ state, dispatch }}>{children}</Provider>;
};
export { store, StateProvider };
when i use useContext(store) i get this result:
useContext result

Don't default to error
Always return state in your reducer, not undefined. This is the first issue I see.
default:
throw new Error();
}
to
default:
return state;
}

Related

REACT - cannot pass Products & Cart arrays from Context with useReducer

I am new to this so please bear with me, I am trying to pass the products & the empty cart from (Context.js) to the (cartState) in apps.js file, through useReducer as intructed in minute 20 of this guide, the only difference is that he is using a faker to generate the Api and I am pulling data with Axios in a useEffect from Api with 13 products
I tried in many ways to receive the items inside the products array but always receiving an empty array in console log of the (carState) in App.js File, all help is welcome appreciated ! Github Repo
Context.js
import { createContext, useState, useEffect, useReducer, useContext } from 'react';
import { cartReducer } from './Reducers';
import { ApiServer } from '../services/Api/index'
const Cart = createContext();
const Context = ({ children }) => {
const [products, setProducts] = useState([]);
useEffect(() => {
ApiServer.get('/products/')
.then(response => {
if (response.data != null) {
setProducts(response.data);
} else {
console.log('O Array esta vazio')
}
})
.catch(error => {
console.log(error);
});
}, []);
console.log(products)
const [state, dispatch] = useReducer(cartReducer, {
products: products,
cart: [],
});
return <Cart.Provider value={{ state, dispatch }}> {children}</Cart.Provider>;
}
export default Context
export const CartState = () => {
return useContext(Cart);
};
Reducer.js
export const cartReducer = (state, action) => {
switch (action.type) {
default:
return state;
}
};
App.js
import React from 'react';
import Router from './routes';
import GlobalStyle from './assets/styles/globalStyles';
import ScrollToTop from './components/ScrollToTop';
import { BrowserRouter } from 'react-router-dom';
import { ContentProvider } from './useContext';
import { CartState } from './contexts/Context';
function App() {
const { state } = CartState();
console.log(state);
return (
<BrowserRouter>
<ContentProvider>
<GlobalStyle />
<Router />
<ScrollToTop />
</ContentProvider>
</BrowserRouter>
);
}
export default App;
What you are trying to do here will not work. The initial state of the reducer is just that -- the INITIAL state. It will use the value of initialState from the very first render, before your items has been updated with the fetch results. Changes to the variable initialState will not effect the reducer after it has already been created. You have to dispatch an action to store the items in your reducer. source
Solution:
As you are using useReducer() for state handling you don't need to use useState()
Write your useReducer like:
const [state, dispatch] = useReducer(reducer, {
products: [],
cart: []
});
Reducer function will be like:
export const reducer = (state, action) => {
switch (action.type) {
case "SET_PRODUCTS_DATA":
return { ...state, products: action.payload };
default:
return state;
}
}
Set product data from useEffect using dispatch like:
useEffect(() => {
ApiServer.get("/products/")
.then((response) => {
if (response.data != null) {
dispatch({ type: "SET_PRODUCTS_DATA", payload: response.data });
} else {
console.log("O Array esta vazio");
}
})
.catch((error) => {
console.log(error);
});
}, []);
It should work perfectly. Demo codesandbox Link

I keep getting " Uncaught TypeError: dispatch is not a function" whenever I try to use dispatch using useContext hook

So I've been learning to use useContext and useReducer hooks with action/dispatch and whenever I try to use dipatch function from any component, it throws out "Uncaught TypeError: dispatch is not a function" error.. it's been 5 days now:/
I try to access dispatch function in the following manner inside components
// context
import { ModalContext } from "../Context/Contexts/ModalContext";
import { OPEN_IMAGE_MODAL } from "../Context/action.types";
const { dispatch } = useContext(ModalContext);
dispatch({
type: OPEN_IMAGE_MODAL,
payload: { isEnabled: true, imageDetails: { url: doc.url } },
});
Here are the ref files
App.js
import React from "react";
// components
import Nav from "./Components/Nav";
import ImageUploadForm from "./Components/ImageUploadForm";
import ImageGrid from "./Components/ImageGrid";
// context
import { ModalContextProvider } from "./Context/Contexts/ModalContext";
const App = () => {
return (
<div className="App">
<Nav />
<ImageUploadForm />
<ModalContextProvider>
<ImageGrid/>
</ModalContextProvider>
</div>
);
};
export default App;
ModalContext.js (Context & Context Provider creation)
import { createContext, useReducer } from "react";
// reducer
import { modalReducer } from "../Reducers/modalReducer";
// components
import ImageModal from "../../Components/ImageModal";
// creating and exporting context
export const ModalContext = createContext();
const initialState = { isEnabled: false, imageDetails: {} };
export const ModalContextProvider = ({ children }) => {
// for selected/clicked image
const [isEnabled, imageDetails, dispatch] = useReducer(
modalReducer,
initialState
);
return (
<ModalContext.Provider value={{ isEnabled, imageDetails, dispatch }}>
{children}
{isEnabled && <ImageModal />}
</ModalContext.Provider>
);
};
modalReducer.js (reducer fn)
import { OPEN_IMAGE_MODAL, CLOSE_IMAGE_MODAL } from "../action.types";
export const modalReducer = (state, action) => {
switch (action.type) {
case OPEN_IMAGE_MODAL:
return {
...state,
isEnabled: true,
imageDetails: action.payload.imageDetails,
};
case CLOSE_IMAGE_MODAL:
return { ...state, isEnabled: false, imageDetails: {} };
default:
return { ...state };
}
};
useReducer returns an array with exactly two values. You are assuming there is some third value dispatch, which is actually undefined. And undefined is not a function.
Depending on how many values you want to passdown using context you can fix this. I like to pass only two values, one state and one dispatch.
const [state, dispatch] = useReducer(
modalReducer,
initialState
);
return (
<ModalContext.Provider value={{ state, dispatch }}>
{children}
{state.isEnabled && <ImageModal />}
</ModalContext.Provider>
);
In children just extract how you do:
const { dispatch } = useContext(ModalContext);
I think the useReducer hook just can return 2 arrays, the satate and the dsipatch method.

React useContext and useMemo for Global variables

Would like to seek guidance from folks if this React implementation makes sense and understand the pitfalls if any. The implementation works but I am unsure if its the correct practice. Please kindly advise.
Idea - Create an AppContext that allows reusability of global states (or even functions) - instead of the conventional useContext + useReducer
AppContext.jsx
import React from 'react';
export const AppContext = React.createContext(null);
export const AppContextProvider = (props) => {
const [ appState, setAppState ] = React.useState({});
const appStateProvider = React.useMemo(() => ({ appState, setAppState }), [ appState, setAppState ]);
const setAppStateItem = (key, value) => {
appStateProvider.setAppState(state => { return { ...state, [key]: value} })
return value;
}
const getAppStateItem = (key = '', fallback) => {
return appState[key] || fallback;
}
const deleteAppStateItem = (key = '') => {
if(key in appState) {
appStateProvider.setAppState(state => {
state[key] = undefined;
return state;
})
}
}
return (
<AppContext.Provider value={{ appStateProvider, setAppStateItem, getAppStateItem, deleteAppStateItem }}>
{props.children}
</AppContext.Provider>
)
}
Create.jsx
import React from 'react';
import { AppContext } from 'contexts';
const { setAppStateItem } = React.useContext(AppContext);
....
setAppStateItem('count', 5);
....
Consume.jsx
import React from 'react';
import { AppContext } from 'contexts';
const { getAppStateItem, setAppStateItem } = React.useContext(AppContext);
....
const count = getAppStateItem('count');
....
Here was an approach to create a global state using useContext and useReducer following a pattern similar to redux. You essentially set up a Store with useReducer and a Context.Provider that you then wrap the rest of your application in. Here was a small implementation I had going:
import React, { createContext, useReducer } from "react";
import Reducer from './Reducer'
const initialState = {
openClose: false,
openOpen: false,
ticker: "BTCUSDT",
tickerRows: [],
positionRows: [],
prices: {},
walletRows: []
};
const Store = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Context.Provider value={[state, dispatch]}>
{children}
</Context.Provider>
)
};
export const ACTIONS = {
SET_CLOSE_OPEN: 'SET_CLOSE_OPEN',
SET_OPEN_OPEN: 'SET_OPEN_OPEN',
SET_TICKER: 'SET_TICKER',
SET_TICKER_ROWS: 'SET_TICKER_ROWS',
SET_POSITION_ROWS: 'SET_POSITION_ROWS',
SET_PRICES: 'SET_PRICES',
SET_WALLET_ROWS: 'SET_WALLET_ROWS'
}
export const Context = createContext(initialState);
export default Store;
Here is the reducer:
import { ACTIONS } from './Store'
const Reducer = (state, action) => {
switch (action.type) {
case ACTIONS.SET_CLOSE_OPEN:
return {
...state,
openClose: action.payload
};
case ACTIONS.SET_OPEN_OPEN:
return {
...state,
openOpen: action.payload
};
...
default:
return state;
}
};
export default Reducer;
I put the Store component in index.js so that it's context is available to all of the components in the app.
ReactDOM.render(
<React.StrictMode>
<Store>
<App />
</Store>
</React.StrictMode>,
document.getElementById('root')
);
Then when you want to access and update the state, you just import the actions and context and the useContext hook:
import { useContext} from "react";
import { ACTIONS, Context } from './store/Store';
and then you just add
const [state, dispatch] = useContext(Context);
inside your functional component and you can access the state and use dispatch to update it.
One limitation is that every component that accesses the state with useContext re-renders every time anything in the state gets updated, not just the part of the state that the component depends on. One way around this is to use the useMemo hook to control when the component re-renders.
export default function WalletTable({ priceDecimals }) {
const classes = useStyles();
const [state, dispatch] = useContext(Context);
async function getCurrentRows() {
const response = await fetch("http://localhost:8000/wallet");
const data = await response.json();
dispatch({ type: ACTIONS.SET_WALLET_ROWS, payload: data });
}
useEffect(() => {
getCurrentRows();
}, []);
const MemoizedWalletTable = useMemo(() => {
return (
<TableContainer component={Paper}>
...
</TableContainer>
);
}, [state.walletRows])
return MemoizedWalletTable
}
Having to memoize everything makes it seem like maybe just using redux isn't all that much more complicated and is easier to deal with once set up.

Redux - Why is loginStatus undefined when component first starts rendering

I'm learning Redux and have come across an issue that I have not encountered before when using React without redux. I'm trying to display a piece of my state inside one of my components name loginStatus. The reducer I have setup this state with has an initial state but whenever I try and launch the application I get the console error:
Cannot read property 'loginStatus' of undefined
Here is my code:
Component
import React, {Component} from 'react';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import * as authActions from './userAuthActions';
class App extends Component {
constructor(props) {
super(props);
}
render() {
if(typeof(this.props.userAuthReducer) !== 'undefined') {
test = this.props.userAuthReducer.loginStatus;
console.log(test)
} else {
console.log("it's undefined")
}
return (
<div className={"popup-logins " + this.props.userAuthReducer.loginStatus}>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
userAuthReducer:state.userAuthReducer
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(authActions,dispatch);
};
export default connect(mapStateToProps,mapDispatchToProps)(App);
userAuthActions.js
export const loginUser = () => {
return {
type:'loginUser',
loggedIn:true
}
}
export const toggleRegLog = () => {
return {
type:'toggleRegLog'
}
}
userAuthReducer
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
let newState;
if (action.loggedIn) {
Object.assign({}, state, {
loginStatus: "logged-in"
})
}
else {
Object.assign({}, state, {
loginStatus: "not-logged-in"
})
}
return newState;
break;
default:
return state;
}
}
export default userAuthReducer;
combine reducers
import {combineReducers} from 'redux';
import userAuthReducer from './userAuthReducer';
function lastAction(state = null, action) {
return action;
}
export default combineReducers({
lastAction,userAuthReducer
});
What's strange is that I initially get a console.log of it's undefined" when I first start up the app and then immediately after I get the value "not-logged-in". I need to use this to hide/ show certain parts of my app if the user is logged in.
Normally if I use React without Redux I use this method all the time without any issues but can't understand what I might have done wrong here?
Thanks
You're not really assigning a value to newState in your reducer, so essentially you're returning undefined, which of course doesn't have a loginStatus property. Changing your reducer so to something like this will probably solve the problem:
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
let newState;
if (action.loggedIn) {
newState = Object.assign({}, state, {
loginStatus: "logged-in"
})
}
else {
newState = Object.assign({}, state, {
loginStatus: "not-logged-in"
})
}
return newState;
break;
default:
return state;
}
}
export default userAuthReducer;
Object.assign returns a new object, applies the data from state and the last argument containing the loginStatus property and passes that to the newState variable, which gets returned at the end of the switch case.
Edit
This edit below makes it easier to reason about the logic in the reducer:
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
if (action.loggedIn) {
return Object.assign(state, { loginStatus: "logged-in" })
}
return Object.assign(state, { loginStatus: "not-logged-in" })
default:
return state;
}
}
export default userAuthReducer;

mapStateToProps returning undefined state from reducer

I am getting this error:
I have this reducer for async:
const initUserState = {
fetching: false,
fetched: false,
users: [],
error: null
};
const userReducer = (state = initUserState, action) => {
switch(action.type){
case "FETCH_USER":
state = {
...state,
users : action.payload
};
break;
case "FETCH_USER_START":
state = {
...state,
fetching: true
};
break;
case "FETCH_USER_SUCCESS":
state = {
...state,
fetched: true,
users: action.payload
};
break;
case "FETCH_USER_ERROR":
state = {
...state,
fetched: false,
error: action.payload
};
break;
default:
break;
}
return state;
};
export default userReducer;
And my container is:
import React from 'react';
import { Blog } from '../components/Blog';
import { connect } from 'react-redux';
//import { act_fetchAllUser } from '../actions/userActions';
import axios from 'axios';
class App extends React.Component {
componentWillMount(){
this.props.fetchAllUser();
}
render(){
console.log("Prop: " , this.props.user);
return(
<div>
<h1>My Blog Posts</h1>
<Blog/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
user: state.userReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAllUser: () => {
dispatch((dispatch) => {
dispatch({ type: "FETCH_USER_START" })
axios.get('http://infosys.esy.es/test/get_allBlog.php')
.then((response) => {
dispatch({
type: "FETCH_USER_SUCCESS",
payload: response.data
});
})
.catch((err) => {
dispatch({
type: "FETCH_USER_ERROR",
payload: err
});
})
});
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Im new to redux and I am into async now...
my application should load the users on page load but why my user state is returning undefined state? in the render method, i am logging the user props that contains the returned state from userReducer(mapStateToProps) but i am getting undefined. But when I getState() in store and log it, I am getting the expected result but it is not the ideal place to get the state right?... What i am doing wrong?
My store is:
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import userReducer from './reducers/userReducer';
import thunk from 'redux-thunk';
const store = createStore(userReducer,applyMiddleware(thunk, logger()));
//store.subscribe(() => console.log('State: ',store.getState()));
export default store;
And my index is:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './container/App';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider> , document.getElementById('root'));
Also, on my default case in userReducer, what should I code there.. should I just break; or do I need also to return state it? Thank you
You should have;
const mapStateToProps = (state) => {
return {
user: state.users
};
};
mapStateToProps passes state (global store) as a props to the component.
Global store
const initUserState = {
fetching: false,
fetched: false,
users: [],
error: null
};
inside component listening to user state
const mapStateToProps = (state) => {
return {
user: state.users
};
};

Categories

Resources