useReducer with useContext hook behaves weirdly, and runs and infinite loop - javascript

I am trying to make a React project which fetches data from an API, and I'm trying to use useReducer hook with useContext from an external file.
Folder Structure:
public
src (and in src folder, I have context.js and reducer.js along with rest of the components and and other files)
package.json
reducer.js:
const reducer = (currentState, action)=>{
switch(action.type){
case 'setBreedList':
// console.log('setBreedList ran')
return {...currentState, breedList: [...currentState.breedList, action.payload]}
// return currentState
// return {...currentState.breedList, action.payload}
default:
return currentState
}
}
export default reducer
context.js
import React, {useContext, useReducer} from 'react';
import reducer from './reducer';
const AppContext = React.createContext();
const initalState = {
breedList: [],
imageList: []
}
const AppProvider = ({children})=>{
const [state, dispatch] = useReducer(reducer, initalState);
const fetchAllBreeds = ()=>{
fetch('https://dog.ceo/api/breeds/list/all')
.then(res=>res.json())
.then(data=>dispatch({type: 'setBreedList', payload: data}))
}
return (
<AppContext.Provider value={{...state, fetchAllBreeds}}>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = ()=>{
return useContext(AppContext)
}
export {AppContext, AppProvider}
and this is index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {AppProvider} from './utils/context';
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById('root')
);
and finally, this is Input.jsx, where I call the fetchAllBreeds function:
import React from 'react'
import {useGlobalContext} from '../utils/context'
const Input = () => {
const {state, fetchAllBreeds} = useGlobalContext();
fetchAllBreeds();
console.log('hello')
return (
<div>
<p>hello</p>
</div>
)
}
export default Input
Now, when I try to call the fetchAllBreeds function in the Input.jsx file above, it prints on the console, 'hello', endlessly. I can't figure out why this is happening and I have tried some different things, but the result is the same. Will be grateful to your help.

Input component is calling fetchAllBreeds function, that will update the context value which in turn will re-render Input component, hence fetchAllBreeds will re-run. Don't call any method inside the component body which will cause re-renders.
Use useEffect hook inside Input component. which will execute only once.
import React, {useEffect} from 'react'
import {useGlobalContext} from '../utils/context'
const Input = () => {
const {state, fetchAllBreeds} = useGlobalContext();
useEffect(()=>{
fetchAllBreeds();
}, [])
return (
<div>
<p>hello</p>
</div>
)
}
export default Input

Related

react global context is not updating

I am relatively new to react and cant seem to get my global context to update.I have wrapped all my child components with the provider but it seems like my update methods in my useState() variables are not working as expected. In my index.js file, I wrap my App component in the provider first:
import App from './App';
import reportWebVitals from './reportWebVitals';
import {
BrowserRouter,
Route,
Routes
} from "react-router-dom";
import {UserContextProvider} from './components/UserContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<UserContextProvider>
<App/>
</UserContextProvider>
);
Here is my UserContext.js file:
import React from "react";
import { useState, createContext } from "react";
export const UserContext = createContext({})
export const UserContextProvider = ({children}) =>{
const [contextUsername, setUserName] = useState(null)
const [contextFirstname, setFirstName] = useState(null)
const [contextLastname, setLastName] = useState(null)
const [contextLoggedin, setLoggedIn] = useState(false)
const value={contextUsername, setUserName, contextFirstname, setFirstName,
contextLastname,setLastName,contextLoggedin,setLoggedIn}
return(
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
)
}
I am trying to access my context in my Landing component:
import React, { useContext } from 'react';
import { useState } from 'react';
import {Link, useNavigate} from "react-router-dom"
import { UserContext } from './UserContext';
const Landing = () =>{
const {contextUsername,setUserName} = useContext(UserContext)
const onExit = (convertedData) =>{
setUserName("test")
console.log(contextUsername)
navigate('/');
}
return(
<div>
<button onLick={onExit}></button>
</div>
)
}
export default Landing
However, in my console.log statement my contextUsername is 'undefined'.
The update state doesn't work like that. react create a queue for state update so we can not get the latest value right after the update call. since you are console.log(contextUsername) right after setUserName it console's undefined.
and don't pass updateState-method(ex. setUserName) directly in context create method to change state value from there call updateState-method(ex. setUserName) like
const UpdateUserName = (value) =>{
setUserName(value)
}
if you want to check the updated value of contextUsername use useEffect
useEffect(() => {
console.log(contextUsername);
},[contextUsername])

data is null after fetching it from API

I'm trying to make movies app for portfolio, I got an api with some data and I'm trying to fetch it with useEffect and then setting the state, I also use useContext hook for passing data to children props but the data is empty.
This is App.js
import "./App.css";
import { useEffect, useState, createContext } from "react";
import Axios from "axios";
import { Main } from "./components/Main/Main";
export const AppContext = createContext();
function App() {
const [data, setData] = useState([]);
useEffect(() => {
Axios.get("http://www.omdbapi.com/?s=star wars&apikey=459f1ce1").then((res) => {
setData(res.data.Search);
})
}, []);
return (
<div className="App">
<AppContext.Provider value={data}>
<Main />
</AppContext.Provider>
</div>
);
}
export default App;
This is Main.js
import React from "react";
import { useContext } from "react";
import { AppContext } from "../../App";
import "./Main.css";
export const Main = () => {
const { data } = useContext(AppContext);
console.log(data)
return <div>
</div>;
};
export default Main;
Currently the value of the context is just the value of the data state. To be able to destructure the context change your value to an object containing the data.
<AppContext.Provider value={{ data }}>

React-alert library does not seem to be re-rendering when state changes

So I'm trying to do error handling using a library called react-alert.
I'm using Axios as my REST handler. I created an action and a reducer, which are both working fine; saw my state update in my DevTools extension when I make a post request.
Here's my functional component called Alert.js
import React, { Fragment, useState } from "react";
import { useAlert } from "react-alert";
import { useSelector } from "react-redux";
import { useEffect } from "react";
export default function Alert(props) {
const alert = useAlert();
const error = useSelector((state) => state.errors.errors);
const [errorMSG, setERRORS] = useState();
useEffect(() => {
if (error !== errorMSG) {
setERRORS(error);
alert.show(errorMSG);
} else {
alert.show("Welcome!");
}
}, [errorMSG]);
return <Fragment />;
then I called it in my main App.js
//Main Imports
import React, { Component, Fragment } from "react";
import ReactDOM from "react-dom";
// React Alert
import { Provider as AlertProvider } from "react-alert";
import AlertTemplate from "react-alert-template-basic";
import Alert from "./layout/Alert";
const alertOptions = {
timeout: 3000,
position: "top center",
};
//Components import
import Header from "./layout/Header";
import Dashboard from "./products/Dashboard";
//Redux import
import { Provider } from "react-redux";
import store from "../store";
class App extends Component {
render() {
return (
<Provider store={store}>
<AlertProvider template={AlertTemplate} {...alertOptions}>
<Alert />
<Fragment>
<Header />
<div className="container-fluid">
<Dashboard />
</div>
</Fragment>
</AlertProvider>
</Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
I do see the Welcome! when the app mounts, but when I have an error I see nothing. Could anyone help me find a solution to this; would be really appreciated.
You see "Welcome" because when your component mounts first time, it calls the useEffect hook. But the useEffect hook isn't getting triggered afterwards because of the wrong dependency item provided for the useEffect. You need to add error instead of errorMSG, because error is coming from your store and you want to show the message when there is a new error emitted:
useEffect(() => {
if (error !== errorMSG) {
setERRORS(error);
alert.show(errorMSG);
} else {
alert.show("Welcome!");
}
}, [error]);

Redux, React-Redux accessing variable from one page to next

I'm defining a variable in Page1 and would like to access it in Page2 and then when clicking back to Page1 retrieve the same variable
So far, the variable is set on Page1 but cannot be retrieved on Page2
index.js
import {createStore, applyMiddleware, combineReducers} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import {Provider} from 'react-redux'
import variableReducer from './reducers'
const store = createStore(
variableReducer,
composeWithDevTools(applyMiddleware(thunk))
)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
serviceWorker.unregister();
actions/index.js
export const SET_MY_VARIABLE = 'SET_MY_VARIABLE'
export const setMyVariable = myVariable => ({
type: SET_MY_VARIABLE,
payload: {myVariable}
})
reducers/index.js
import {SET_MY_VARIABLE} from '../actions'
const initialState = {
myVariable: ''
}
const variableReducer = (state=initialState, action) => {
switch (action.type) {
case SET_MY_VARIABLE:
return {
...state,
myVariable: action.payload.myVariable
}
default:
return state
}
}
export default variableReducer
components/Page1.js
import React, {useEffect} from 'react'
import {connect, useDispatch} from 'react-redux'
import {setMyVariable} from '../actions'
const Page1 = (props) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(setMyVariable(5000))
}, [])
return (
<div>
Setting variable<br />
Go to page 2
</div>
)
}
const mapState = state => {
return {
myVariable: state.myVariable
}
}
export default connect(mapState)(Page1)
components/Page2.js
import React from 'react'
import {connect} from 'react-redux'
const Page2 = (props) => {
const {myVariable} = props
console.log('props: ', props)
return (
<div>
Variable: {myVariable}
</div>
)
}
const mapState = state => {
console.log('map2 ', state.myVariable)
return {
myVariable: state.myVariable
}
}
export default connect(mapState)(Page2)
I should be able to set variables to the store in one component and access them throughout the entire App. Instead, I'm not able to retrieve them
Instead of using action.payload.myVariable use action.payload in your reducer/index.js
I've discovered the answer to my problem. I needed to change the <a href tag to a <Link> from react-router-dom in the Page1 component. The <a href was causing a complete reload of all JS and losing state. Here's the corrected component:
components/Page1.js
import React, {useEffect} from 'react'
import {connect, useDispatch} from 'react-redux'
import {setMyVariable} from '../actions'
import {Link} from 'react-router-dom'
const Page1 = (props) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(setMyVariable(5000))
}, [])
return (
<div>
Variable: {myVariable}<br />
<Link to="/page2">Go to page 2</Link>
</div>
)
}
const mapState = state => {
return {
myVariable: state.myVariable
}
}
export default connect(mapState)(Page1)

Question around using useContext and useReducer hooks

I am trying to practice using hooks and i am not able to wrap my head around it.
I have a single component MessageBoard component that reads the data from state which just displays a simple list of messages.
I am passing down the dispatch and state via createContext so that the child components can consume it, which in-turn uses useContext in the child components to read the value.
When the page is refreshed, I expect to see the initial UI but it fails to render that the value in the context is undefined. I have already provided the initial state to the reducer when initializing it.
App.js
import React from "react";
import MessageBoard from "./MessageBoard";
import MessagesContext from "../context/MessagesContext";
function App() {
return (
<div>
<MessagesContext>
<h2>Reaction</h2>
<hr />
<MessageBoard />
</MessagesContext>
</div>
);
}
export default App;
MessageBoard.js
import React, { useContext } from "react";
import MessagesContext from "../context/MessagesContext";
function MessageBoard(props) {
const { state } = useContext(MessagesContext);
return (
<div>
{state.messages.map(message => {
return (
<div key={message.id}>
<h4>{new Date(message.timestamp).toLocaleDateString()}</h4>
<p>{message.text}</p>
<hr />
</div>
);
})}
</div>
);
}
export default MessageBoard;
MessagesContext.js
import React, { createContext, useReducer } from "react";
import reducer, { initialState } from "../state/reducer";
export default function MessagesContext(props) {
const Context = createContext(null);
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Context.Provider
value={{
dispatch,
state
}}
>
{props.children}
</Context.Provider>
);
}
Broken Example - https://codesandbox.io/s/black-dust-13kj2
Instead if I change the MessagesContext file a bit and instead the Provider is directly injected into the App, it works as expected. Wondering what I have misunderstood here and what might be going on ?
MessagesContext.js
import { createContext } from "react";
export default createContext(null);
App.js
import React, { useReducer } from "react";
import reducer, { initialState } from "../state/reducer";
import PublishMessage from "./PublishMessage";
import MessageBoard from "./MessageBoard";
import MessagesContext from "../context/MessagesContext";
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<MessagesContext.Provider
value={{
dispatch,
state
}}
>
<h2>Reaction</h2>
<hr />
<PublishMessage />
<hr />
<MessageBoard />
</MessagesContext.Provider>
</div>
);
}
export default App;
Working Example - https://codesandbox.io/s/mystifying-meitner-vzhok
useContext accepts a context object (the value returned from React.createContext) and returns the current context value for that context.
const MyContext = createContext(null);
const value = useContext(MyContext);
// MessagesContext Not a contex object.
const { state } = useContext(MessagesContext);
In the first example:
// export the context.
export const Context = createContext(null);
export default function MessagesContext(props) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Context.Provider
value={{
dispatch,
state
}}
>
{props.children}
</Context.Provider>
);
}
and then use it:
import { Context } from '../context/MessagesContext';
function MessageBoard() {
const { state } = useContext(Context);
...
}
Working broken example:

Categories

Resources