Maximum update depth exceeded when implementing useLocalStorage - reactjs

I'm implementing a localStorage on NextJs TypeScript by following https://upmostly.com/next-js/using-localstorage-in-next-js but I get an error
on the context provider repeatedly.
Here is my code.
// My implementation for the context
import { useLocalStorage } from '#/Hooks/useLocalStorage';
import { Invoice, Invoices } from '#/Types/invoice';
import { createContext, Dispatch, SetStateAction, useContext } from 'react';
export const defaultInvoiceValue: Invoice = {
title: '',
items: [],
note: '',
status: '',
};
export const InvoiceContext = createContext<Invoices>({
invoices: [defaultInvoiceValue],
});
export const SetInvoicesContext = createContext<
Dispatch<SetStateAction<Invoices>>
>((value) => {
console.log('Set invoice context', value);
});
export const useInvoices = () =>
useLocalStorage<Invoices>('invoice', { invoices: [defaultInvoiceValue] });
export const useInvoiceContext = () => {
return useContext(InvoiceContext);
};
export const useSetInvoiceContext = () => {
return useContext(SetInvoicesContext);
};
// Provider wrapper
import { InvoiceContext, SetInvoicesContext, useInvoices } from '#/Context/InvoiceContext';
import { PropsWithChildren } from 'react';
export const InvoicesContextProvider = ({ children }: PropsWithChildren) => {
const [invoices, setInvoices] = useInvoices();
return (
<InvoiceContext.Provider value={invoices}>
<SetInvoicesContext.Provider value={setInvoices}>
{children}
</SetInvoicesContext.Provider>
</InvoiceContext.Provider>
)
};
The default context works fine. useSetInvoiceContext() also doesn't work

I would recommend you to check if the value provided in "invoices" in the hook useInvoinces is actually the same as the current value, before calling setInvoices. This will help you to avoid the infinite rendering.
For example, if the title and the items are the same as the current invoices, it will not update the state of invoices.
Something like:
let areNewInvoicesSameAsCurrent = newInvoices?.items?.every(invoice => currentInvoices.items.includes(invoice) );
if(newInvoices.title !== currentInvoices.title && !areNewInvoicesSameAsCurrent){
setInvoices(newInvoices)
}

Related

query inside/outside React context

I have an existential question about react context.
Let's say I have a component that uses useEffect to make call my back-end and get some data. then useState set it inside the context, it works fine, but what if I use useEffect and call my back-end inside the context and then just read data in the component. what you think which one is more "accurate"
for example
old method
context.Js
import React, { useState, useEffect } from "react";
const defaultValue = {someState};
export const SomeContext = React.createContext();
export const SomeProvider = ({ someId }) => {
const [state, setState] = useState(defaultValue);
setData = (data) => { do some logic then setState(data)}
const value = {
...state,
setData
};
return <SomeContext.Provider value={value}>{children}</SomeContext.Provider>;
};
index.js
import React, { useState, useEffect } from "react";
import { SomeContext, SomeProvider } from "context/SomeContext";
import { getById } from "services";
const Layout = ({ someId }) => {
const { data, setData } = useContext(GroupContext);
useEffect(()=>{
//getById call my back-end to get some data
getById(someId).then(setData)
}, [])
return <Component />
}
export default props => {
const providerProps = {
someId,
};
return (
<GroupProvider {...providerProps}>
<Layout activeTab={activeTab} {...props} />
</GroupProvider>
);
};
new method
context.Js
import React, { useState, useEffect } from "react";
import { getById } from "services";
const defaultValue = {someState};
export const SomeContext = React.createContext();
export const SomeProvider = ({ someId }) => {
const [state, setState] = useState(defaultValue);
useEffect(()=>{
//getById call my back-end to get some data
getById(someId).then(setState)
}, [])
const value = {
...state,
};
return <SomeContext.Provider value={value}>{children}</SomeContext.Provider>;
};
index.js
import { SomeContext, SomeProvider } from "context/SomeContext";
const Layout = ({ }) => {
const { data } = useContext(GroupContext);
return <Component />
}
export default props => {
const providerProps = {
someId,
};
return (
<GroupProvider {...providerProps}>
<Layout activeTab={activeTab} {...props} />
</GroupProvider>
);
};
something like this

How to create a generic Context.Provider in React?

I am using react's context to share data across component.
For example, I could create a user context:
const useFirebaseUser = () => {
const [user, setUser] = useState({} as User);
useEffect(() => {
return firebase.auth().onAuthStateChanged((user) => {
if (user) {
const { displayName, photoURL, uid } = user;
setUser({
displayName,
photoURL,
uid,
isAuthenticated: true,
} as User);
} else {
setUser({} as User);
}
});
}, []);
return user;
};
export const FirebaseUserContext = createContext({} as User);
export const GlobalFirebaseUserProvider = ({ children }: { children: ReactNode }) => (
<FirebaseUserContext.Provider value={useFirebaseUser()}>{children}</FirebaseUserContext.Provider>
);
similarly, I could also create a similar context to share other data, like todos
const useTodos = () => {
const [todos, setTodos] = useState(['']);
// ..
return { todos, setTodos };
};
export const TodosContext = createContext(
{} as { todos: string[]; setTodos: React.Dispatch<React.SetStateAction<string[]>> }
);
export const TodosContextProvider = ({ children }: { children: ReactNode }) => (
<TodosContext.Provider value={useTodos()}>{children}</TodosContext.Provider>
);
Upon these, I want to abstract out the value part. I am trying to create a Generic Provider:
import React, { createContext, ReactNode } from 'react';
export const CreateGenericContext = <T extends {}>(value: T) => {
const GenericContext = createContext({} as T);
const GenericContextProvider = ({ children }: { children: ReactNode }) => (
<GenericContext.Provider value={value}>{children}</GenericContext.Provider>
);
return { GenericContext, GenericContextProvider };
};
thus my user context could simplify into
export const {
GenericContext: UserContext,
GenericContextProvider: UserContextProvier,
} = CreateGenericContext(useUser());
However, React throw error message:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Is this mean it is impossible to create a generic context providre for React? I had searched online, and tutorial seems show that for context not using hooks would work. However, in case of using react hooks, how to create a generic context provider in react?
Delay the custom hook, hook can only be called/invoked inside function component.
import React, { createContext, ReactNode } from 'react';
export const createGenericContext = <T extends {}>(hook: () => T) => {
const GenericContext = createContext({} as T);
const GenericContextProvider = ({ children }: { children: ReactNode }) => (
<GenericContext.Provider value={hook()}>{children}</GenericContext.Provider>
);
return { GenericContext, GenericContextProvider };
};

Should I add async code in container component?

I'm making my first React-Redux project.
I wanna get data from getListAPI.
I checked console.log(data) in [GET_LIST_SUCCESS], and there was what I wanted.
But console.log(temp) in container, I expect 'data', it was just action object(only type exists).
How can I get the 'data'?
// container
import React from 'react';
import { useDispatch } from 'react-redux';
import Home from 'presentations/Home';
import * as homeActions from 'modules/home';
const HomeContainer = () => {
const dispatch = useDispatch();
const temp = dispatch(homeActions.getList());
console.log(temp);
return (
<Home />
);
}
export default HomeContainer;
// Redux module
import axios from 'axios';
import { call, put, takeEvery } from 'redux-saga/effects';
import { createAction, handleActions } from 'redux-actions';
function getListAPI() {
return axios.get('http://localhost:8000/');
}
const GET_LIST = 'home/GET_LIST';
const GET_LIST_SUCCESS = 'home/GET_LIST_SUCCESS';
const GET_LIST_FAILURE = 'home/GET_LIST_FAILURE';
export const getList = createAction(GET_LIST);
function* getListSaga() {
try {
const response = yield call(getListAPI);
yield put({ type: GET_LIST_SUCCESS, payload: response });
} catch (e) {
yield put({ type: GET_LIST_FAILURE, payload: e });
}
}
const initialState = {
data: {
id: '',
title: '',
created_at: '',
updated_at: '',
content: '',
view: '',
}
};
export function* homeSaga() {
yield takeEvery('home/GET_LIST', getListSaga);
}
export default handleActions(
{
[GET_LIST_SUCCESS]: (state, action) => {
const data = action.payload.data;
console.log(data);
return {
data
};
}
}, initialState
);
Maybe I need like async/await or Promise.then() or useCallback, etc in container?
Because I thought Redux-Saga handles async, but container isn't in Redux-Saga area.
So shouldn't I inject the container with async processing?
I wrote some code for test.
Expecting to receive other data in a few seconds.
// container
// const temp = dispatch(homeActions.getList());
let temp = dispatch(homeActions.getList());
let timer = setInterval(() => console.log(temp), 1000);
setTimeout(() => { clearInterval(timer); alert('stop');}, 50000);
Nothing changed.
It's just log action object(only type exists).
What am I missing?
dispatch() returns the action dispatched to the store (that's why the console.log(temp) shows the action itself).
You need to create a selector to fetch the data from the store and use the useSelector() hook:
// container
import React from 'react';
import { useDispatch } from 'react-redux';
import Home from 'presentations/Home';
import * as homeActions from 'modules/home';
const selectData = (state) => state.data
const HomeContainer = () => {
const dispatch = useDispatch();
const temp = useSelector(selectData)
dispatch(homeActions.getList());
// Do something with temp
return (
<Home />
);
}
export default HomeContainer;

React UseState hook causing infinite loop

I am using ReactJs to grab an RSS news feed every 5 seconds to convert it into a JSON string to render it on the webpage. I am using both useEffect and useState hook for this purpose as I am passing the JSON string in the useState hook variable, however. It kind of works but it produces an infinite loop. I have searched through the fixes provided in stack overflow but I couldn't find the exact problem. Here is my code snippet.'
import React, {useEffect, useState} from 'react';
import Carousel from 'react-bootstrap/Carousel';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {getNews} from "../../actions/news";
import Parser from 'rss-parser';
const NewsCarousel = ({getNews, news: {news, loading} }) => {
const [getFeed, setFeed] = useState({
feed: ''
});
useEffect(() => {
const interval = setInterval(() => {
getNews();
}, 5000);
return () => clearInterval(interval);
}, [getNews]);
const { feed } = getFeed;
const newsFeed = feed => setFeed({ ...getFeed, feed: feed });
let parser = new Parser();
parser.parseString(news, function(err, feed){
if (!err) {
newsFeed(feed);
} else {
console.log(err);
}
});
console.log(feed);
return (
<div className="dark-overlay">
</div>
);
};
NewsCarousel.propTypes = {
getNews: PropTypes.func.isRequired,
news: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
news: state.news
});
export default connect(mapStateToProps, {getNews}) (NewsCarousel);
Its when I console.log my feed variable that's when I see in the console the infinite logs.
Below is my getNews Action
import axios from 'axios';
import { GET_NEWS, NEWS_FAIL } from "./types";
export const getNews = () => async dispatch => {
try{
const res = await axios.get('https://www.cbc.ca/cmlink/rss-
topstories');
dispatch({
type: GET_NEWS,
payload: res.data
})
} catch(err) {
dispatch({
type: NEWS_FAIL,
payload: { msg: err}
})
}
};
You need to parse your news only when there is a change in new props. Add another useEffect with news as a dependency so it will be called when the news changes and then update your state there.
import React, {useEffect, useState} from 'react';
import Carousel from 'react-bootstrap/Carousel';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {getNews} from "../../actions/news";
import Parser from 'rss-parser';
const NewsCarousel = ({getNews, news: {news, loading} }) => {
const [getFeed, setFeed] = useState({
feed: ''
});
useEffect(() => {
const interval = setInterval(() => {
getNews();
}, 5000);
return () => clearInterval(interval);
}, [getNews]);
useEffect(() => {
const newsFeed = feed => setFeed({ ...getFeed, feed: feed });
const parser = new Parser();
parser.parseString(news, function(err, feed){
if (!err) {
newsFeed(feed);
} else {
console.log(err);
}
});
}, [news]);
return (
<div className="dark-overlay">
</div>
);
};
NewsCarousel.propTypes = {
getNews: PropTypes.func.isRequired,
news: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
news: state.news
});
export default connect(mapStateToProps, {getNews}) (NewsCarousel);

How can I share state data between components using custom hooks?

I have the following custom hook called useFlash:
import { useState } from 'react';
export default function useFlash() {
const [messages, setMessages] = useState([]);
const showFlash = (message: string) => {
setMessages([...messages, message]);
};
const clearMessage = (index: number) => {
setMessages(messages.filter((_m, i) => index !== i));
};
return {
messages,
showFlash,
clearMessage
};
}
Then I have this HOC providing it to two other components:
import React from 'react';
import useFlash from '../effects/useFlash';
const withFlash = (WrappedComponent: React.Component) => {
const WithFlash = () => {
const { messages, showFlash, clearMessage } = useFlash();
return (
<WrappedComponent
messages={messages}
showFlash={showFlash}
clearFlashMessage={clearMessage}
/>
);
};
return WithFlash;
};
export default withFlash;
It works well, except each use of the HOC gets its own state data. I need the state to be global. I know I can use contexts with consumer/providers, but I thought this way would be a little simpler. It is not proving to be true, is there a way to make this global?
You'll need to use Context, but it's not that bad..
create your context..
import React, { useState } from 'react';
export const FlashContext = React.createContext();
export const FlashProvider = ({ children }) => {
const [messages, setMessages] = useState([]);
return (
<FlashContext.Provider value={{ messages, setMessages }}>
{children}
</FlashContext.Provider>
);
};
wrap your components in the provider somewhere higher in the tree..
import React from "react";
import { FlashProvider } from "./flash-context";
const App = () => <FlashProvider><TheRest /></FlashProvider>;
export default App;
then use the context in your custom hook..
import React, { useContext } from "react";
import { FlashContext } from "./flash-context";
export default function useFlash() {
const { messages, setMessages } = useContext(FlashContext);
const showFlash = (message) => {
setMessages([...messages, message]);
};
const clearMessage = (index) => {
setMessages(messages.filter((_m, i) => index !== i));
};
return {
messages,
showFlash,
clearMessage
};
}

Resources