Cannot read properties of undefined (the general state is undefined) - reactjs

I just finished a small project and I tried to publish it on Vercel, the project works without errors locally, but on the server I get the error: "Cannot read properties of undefined (reading 'notes')", apparently not It recognizes the state that I have in my application, this would be my useNotes file:
import { useContext } from "react";
import { NotesContext } from "../context/NotesContext";
export const useNotes = () => {
const { notestate, toggleNote, addNote, changeState, handleDeleteNote } = useContext(NotesContext);
const { notes } = notestate;
return {
noteGeneral: notestate,
notes: notes,
notinteresting: notes?.filter(note => !note?.interesting).length,
interesting: notes?.filter(note => note?.interesting === true).length,
lisInteresting: notes?.filter(note => note?.interesting === true),
listNotInteresting: notes?.filter(note => note?.interesting === false),
toggleNote,
addNote,
changeState,
handleDeleteNote
}
}
this would be the context of my application:
import { createContext } from "react";
import { Note, NoteState } from "../interfaces/interfaces";
export type NoteContextProps = {
notestate: NoteState,
toggleNote: (id: number) => void;
addNote: (note: Note) => void;
changeState: (action: string) => void;
handleDeleteNote: (id: number) => void;
}
export const NotesContext = createContext<NoteContextProps>({} as NoteContextProps);
and this is the provider of my application
import { useEffect, useReducer } from 'react';
import { Note, NoteState } from '../interfaces/interfaces';
import { NoteReducer } from './NoteReducer';
import { NotesContext } from './NotesContext';
import { DateNote } from '../../helpers/DateNote';
const { today } = DateNote();
const INITIAL_STATE: NoteState = {
notesCount: 2,
notes: [
{
id: 1,
description: 'Welcome to NotesApp, here you can write all the notes you want. If you liked the project leave a comment on my social networks :)',
interesting: true,
title: 'Hello there',
created: today
},
],
active: 'All',
}
interface props {
children: JSX.Element | JSX.Element[]
}
const NotesProvider = ({ children }: props) => {
const localData = localStorage.getItem('notes');
const toggleNote = (id: number) => {
dispatch({ type: 'toggleInteresting', payload: { id } })
}
const addNote = (note: Note) => {
dispatch({ type: 'addNote', payload: note })
}
const changeState = (active: string) => {
dispatch({ type: 'changeState', payload: active })
}
const handleDeleteNote = (id: number) => {
dispatch({ type: 'deleteNote', payload: { id } })
}
const [notestate, dispatch] = useReducer(NoteReducer, INITIAL_STATE, () => {
return localData ? JSON.parse(localData) : localStorage.setItem('notes', JSON.stringify(INITIAL_STATE))
});
useEffect(() => {
localStorage.setItem('notes', JSON.stringify(notestate))
}, [notestate])
return (
<NotesContext.Provider value={{ toggleNote, addNote, changeState, handleDeleteNote, notestate }}>
{children}
</NotesContext.Provider>
)
}
export default NotesProvider;
I am storing the state of my application in Localstorage.
I've been looking for the solution for more than two days but I can't find it

const [notestate, dispatch] = useReducer(NoteReducer, INITIAL_STATE, () => {
return localData ? JSON.parse(localData) : localStorage.setItem('notes', JSON.stringify(INITIAL_STATE))
});
I think here when there is no local data, the notestate receives value from localStorage.setItem(), which is undefined, so the notestate will be undefined.

Related

How do write unit test for function inside Context API?

Currently I am not able to test the lines as test coverage says. Please help me.
Test coverage is saying lines like dispatch but I don't know how to test it.
This is my file ListMeetingContext.tsx
import React, { createContext, ReactElement, useReducer } from 'react';
import { IMeeting } from '#interfaces';
import { REDUCER_ACTIONS } from '#enums/ReducerAction';
import { meetingData } from '#constants';
export const initListMeeting: IMeeting[] = meetingData;
type ReducerAction = {
type: REDUCER_ACTIONS;
payload: IMeeting | number;
};
export type MeetingsContext = {
children: ReactElement | ReactElement[];
};
export type ListMeetingContextType = {
listMeeting: IMeeting[];
handleAddMeeting: (newMeeting: IMeeting) => void;
handleUpdateMeeting: (newMeeting: IMeeting) => void;
handleRemoveMeeting: (id: number) => void;
};
export const reducer = (
state: IMeeting[],
action: ReducerAction,
): IMeeting[] => {
switch (action.type) {
case REDUCER_ACTIONS.ADD_MEETING:
// TODO: as IMeeting
return [...state, action.payload as IMeeting];
case REDUCER_ACTIONS.REMOVE_MEETING:
return state.filter((meeting) => meeting.id !== action.payload);
case REDUCER_ACTIONS.UPDATE_MEETING:
return state.map((meeting: IMeeting) =>
// TODO: as IMeeting
meeting.id === (action.payload as IMeeting).id
? (action.payload as IMeeting)
: meeting,
);
default:
return state;
}
};
export const ListMeetingContext = createContext<ListMeetingContextType>({
listMeeting: meetingData,
handleAddMeeting: () => null,
handleUpdateMeeting: () => null,
handleRemoveMeeting: () => null,
});
export const StateProvider = ({ children }: MeetingsContext): JSX.Element => {
const [listMeeting, dispatch] = useReducer(reducer, initListMeeting);
const handleAddMeeting = (newMeeting: IMeeting) => {
// Uncovered line
dispatch({ type: REDUCER_ACTIONS.ADD_MEETING, payload: newMeeting });
};
const handleUpdateMeeting = (newMeeting: IMeeting) => {
// Uncovered line
dispatch({ type: REDUCER_ACTIONS.UPDATE_MEETING, payload: newMeeting });
};
const handleRemoveMeeting = (id: number) => {
// Uncovered line
dispatch({ type: REDUCER_ACTIONS.REMOVE_MEETING, payload: id });
};
return (
<ListMeetingContext.Provider
value={{
listMeeting,
handleAddMeeting,
handleRemoveMeeting,
handleUpdateMeeting,
}}
>
{children}
</ListMeetingContext.Provider>
);
};
This is Image about test Coverage
The test for coverage lines tells me to put it at the bottom
This is file ListMeetingContext.test.tsx
import React from 'react';
import {
ListMeetingContext,
MeetingsContext,
reducer,
StateProvider,
} from '#context';
import { REDUCER_ACTIONS } from '#enums';
import { IMeeting } from '#interfaces';
import renderer from 'react-test-renderer';
import { meetingData } from '#constants';
import { fireEvent, render, screen } from '#testing-library/react';
describe('Test [ListMeetingContext] function', () => {
test('Action [ADD_MEETING] should update context state/add new item to context state with action[ADD_MEETING]', () => {
const state: IMeeting[] = [];
const action = {
type: REDUCER_ACTIONS.ADD_MEETING,
payload: meetingData[0],
};
const result = reducer(state, action);
expect(result).toEqual([action.payload]);
});
test('Action [REMOVE_MEETING] should update context state/remove an item from context state with action[REMOVE_MEETING]', () => {
const state: IMeeting[] = meetingData;
const action = {
type: REDUCER_ACTIONS.REMOVE_MEETING,
payload: 1,
};
const result = reducer(state, action);
expect(result).toEqual([meetingData[1]]);
});
test('Action [UPDATE_MEETING] should update context state/update an item from context state with action[UPDATE_MEETING]', () => {
const state: IMeeting[] = [meetingData[0]];
const action = {
type: REDUCER_ACTIONS.UPDATE_MEETING,
payload: meetingData[0],
};
const result = reducer(state, action);
expect(result).toEqual([action.payload]);
});
test('Action [EMPTY_ACTION] should return state', () => {
const state: IMeeting[] = [meetingData[0]];
const action = {
type: REDUCER_ACTIONS.EMPTY_ACTION,
payload: meetingData[0],
};
const result = reducer(state, action);
expect(result).toEqual(state);
});
test('[StateProvider] with default value', () => {
const component = renderer
.create(<StateProvider {...({} as MeetingsContext)} />)
.toJSON();
expect(component).toMatchSnapshot();
});
type Props = {
handleAddItem: () => void;
handleUpdateItem: () => void;
handleRemoveItem: () => void;
};
const TestComponent = ({
handleAddItem,
handleUpdateItem,
handleRemoveItem,
}: Props): JSX.Element => {
return (
<>
<button data-testid="remove-meeting-button" onClick={handleRemoveItem}>
Remove
</button>
<button data-testid="add-meeting-button" onClick={handleAddItem}>
Add
</button>
<button data-testid="update-meeting-button" onClick={handleUpdateItem}>
Update
</button>
</>
);
};
test('component handles button click', () => {
const handleAddMeeting = jest.fn();
const handleUpdateMeeting = jest.fn();
const handleRemoveMeeting = jest.fn();
const listMeeting = meetingData;
render(
<ListMeetingContext.Provider
value={{
listMeeting,
handleAddMeeting,
handleUpdateMeeting,
handleRemoveMeeting,
}}
>
<TestComponent
handleAddItem={handleAddMeeting}
handleUpdateItem={handleUpdateMeeting}
handleRemoveItem={handleRemoveMeeting}
/>
</ListMeetingContext.Provider>,
);
fireEvent.click(screen.getByTestId('remove-meeting-button'));
expect(handleRemoveMeeting).toHaveBeenCalledTimes(1);
});
});
Currently I am not able to test the lines as test coverage says. Please help me

Using `useContext` with TypeScript - Type 'MyType | null` is not assignable to type 'MyType'

I'm working on implementing TypeScript on a small codebase I've been working on and having a few troubles with the above error. I've searched for answers but none seemed to fix the actual issue I was having.
I'm getting the error:
Type 'ContextType | null' is not assignable to type 'ContextType'.
I have tried setting it to null, undefined, and object, and nothing seems to help so I'd appreciate some help with this one!
Store.txt
import { useReducer, createContext, useMemo } from "react";
import { INITIAL_DATA } from './todo/constants';
import { ContextType, ITodo, ACTIONTYPE } from './todo/models';
export const StoreContext = createContext<ContextType | null>(null)
export enum ACTIONS {
DELETE_TODO = "delete_todo",
ADD_TODO = "add_todo",
};
const reducer = (state: ITodo, action: ACTIONTYPE) => {
switch (action.type) {
case ACTIONS.DELETE_TODO:
return [...action.payload];
case ACTIONS.ADD_TODO:
return action.payload;
default:
return state;
}
};
interface Props {
children: React.ReactNode;
}
export const StoreProvider = ({ children }: Props) => {
const [state, dispatch] = useReducer(reducer, INITIAL_DATA);
const contextValue: ContextType = useMemo(() => {
return { state, dispatch };
}, [state, dispatch]);
return (
<StoreContext.Provider value={contextValue}>
{children}
</StoreContext.Provider>
);
};
Component.tsx
import { useContext } from 'react';
import { StoreContext, ACTIONS } from '../../../store';
import { ContextType } from '../../models'
export const AddTodo = () => {
const { state, dispatch }: ContextType = useContext(StoreContext);
const validateFirstStep = async () => {
return await isValid(firstStepForm);
}
const closeDrawer = () => {
onClose();
}
const handleSubmit = async () => {
const newTodoEntry = { /** todo **/ }
const newData = [...state, newTodoEntry];
dispatch({ type: ACTIONS.ADD_TODO, payload: newData });
}
return (
<div>
{ /* Something Todo happens here */ }
</div>
)
}
Models.tsx
import { ACTIONS } from '../../store';
export type ITodos = {
id: string;
todoName: string;
todoType: string;
}[];
export type ContextType = {
state: ITodos;
dispatch: React.Dispatch<ACTIONTYPE>;
}
export type ACTIONTYPE =
| { type: ACTIONS.ADD_TODO, payload: ITodos }
| { type: ACTIONS.DELETE_TODO; payload: ITodos }
You need to provide default context value in case of there is no provider higher in the react tree;
export const StoreContext = createContext<ContextType>({todos: [], dispatch: () => {}})

Async call results to Warning: Maximum update depth exceeded in React & Redux

I have an app built with React, Redux that pulls data from a RESTful service sitting in my local. I tested the implementation with dummy data and works fine. However, when I hook up the async service the calls result in havoc with the below error:
Here is the code
reducer.js
import {
LOAD_ALL_PRODUCTS_SUCCESS,
LOAD_ALL_PRODUCTS_REQUEST,
LOAD_ALL_PRODUCTS_FAIL,
LOAD_PRODUCT_REQUEST,
LOAD_PRODUCT_SUCCESS,
LOAD_PRODUCT_FAIL,
} from './actions';
export const productData = (state = { loader: {}, products: [] }, action) => {
const { type, payload } = action;
switch (type) {
case LOAD_ALL_PRODUCTS_REQUEST: {
return { loader: true, products: [] };
}
case LOAD_ALL_PRODUCTS_SUCCESS: {
return { loader: false, products: payload };
}
case LOAD_ALL_PRODUCTS_FAIL: {
return { loader: false, error: payload };
}
default:
return state;
}
};
thunk.js
import axios from 'axios';
import { mockData } from '../MockData';
import {
loadAllProductFailed,
loadAllProductRequest,
loadAllProductSuccess,
LOAD_PRODUCT_FAIL,
LOAD_PRODUCT_REQUEST,
LOAD_PRODUCT_SUCCESS,
} from './actions';
export const loadInitialProducts = () => async (dispatch) => {
try {
dispatch(loadAllProductRequest());
//this is where the issues is
const response = await axios.get('http://localhost:8080/v1/product/all');
const payload = await response.data;
console.log(payload);
dispatch(loadAllProductSuccess(payload));
} catch (error) {
dispatch(
loadAllProductFailed(
error.response && error.response.data.message
? error.response.data.message
: error.message
)
);
}
};
export const loadProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: LOAD_PRODUCT_REQUEST });
//do a axios api call for product api
dispatch({
type: LOAD_PRODUCT_SUCCESS,
payload: mockData.find(({ productId }) => productId == id),
});
} catch (error) {
dispatch({
type: LOAD_PRODUCT_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const LOAD_ALL_PRODUCTS_REQUEST = 'LOAD_PRODUCTS_REQUEST';
export const loadAllProductRequest = () => ({
type: LOAD_ALL_PRODUCTS_REQUEST,
});
export const LOAD_ALL_PRODUCTS_SUCCESS = 'LOAD_ALL_PRODUCTS_SUCCESS';
export const loadAllProductSuccess = (payload) => ({
type: LOAD_ALL_PRODUCTS_SUCCESS,
payload: payload,
});
export const LOAD_ALL_PRODUCTS_FAIL = 'LOAD_ALL_PRODUCTS_FAIL';
export const loadAllProductFailed = (error) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: error,
});
export const LOAD_PRODUCT_REQUEST = 'LOAD_PRODUCT_REQUEST';
export const loadProductRequest = () => ({
type: LOAD_ALL_PRODUCTS_FAIL,
});
export const LOAD_PRODUCT_SUCCESS = 'LOAD_PRODUCT_SUCCESS';
export const loadProductSuccess = (payload) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: payload,
});
export const LOAD_PRODUCT_FAIL = 'LOAD_PRODUCT_FAIL';
export const loadProductFailed = (error) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: error,
});
Home.js
import React, { useState, useEffect } from 'react';
import { conwayAPI } from '../ConwayAPI';
import { Container, Col, Row } from 'react-bootstrap';
import Product from './Product';
import { useDispatch, useSelector } from 'react-redux';
import _ from 'lodash';
import { loadInitialProducts } from '../app/thunk';
const Home = () => {
//local state maintained only for this component
const [filterProducts, setFilterProducts] = useState([]);
const dispatch = useDispatch();
const productList = useSelector((state) => state.productData);
const { loader, error, products } = productList;
useEffect(() => {
dispatch(loadInitialProducts());
}, [dispatch, products]);
const doSearch = (text) => {
_.isEmpty(text)
? setFilterProducts(products)
: setFilterProducts(
filterProducts.filter((product) =>
product.productName.toLowerCase().includes(text.toLowerCase())
)
);
};
return (
<Container fluid>
<Row md={7} lg={5} className='justify-content-md-center'>
{filterProducts.length &&
filterProducts.map((datam, key) => {
return (
<Col key={key}>
<Product product={datam} key={key} />
</Col>
);
})}
</Row>
</Container>
);
};
export default Home;
When I click on a Nav panel the Home.js gets called and the error starts. What can I do differently to eliminate this error?

How to do AuthContext, createDataContext using Typescript for React Native Expo dev?

AuthContext.tsx
import createDataContext from './createDataContext';
import serverApi from '../api/server';
const authReducer = ({state, action}: any) => {
switch(action.type){
default:
return state;
}
};
const signup = () => {
return async ({email, password}: any) => {
try{
const response = await serverApi.post('/signup', {email, password});
console.log(response.data)
}catch(err){
console.log(err.message);
}
};
}
const signin = ({dispatch}:any) => {
return ({email, password}: any) => { };
}
const signout = ({dispatch}: any) => {
return () => {};
}
export const {Provider, Context} = createDataContext(
authReducer,
{signin, signout, signup},
{isSignedIn: false}
);
createDataContext
import React, { useReducer } from 'react';
export default ({reducer, actions, defaultValue}: any) => {
const Context = React.createContext();
const Provider = ({ children }: any) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions: any = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
I copy the code from a video tutorial where react native app has been developed with js extension. But the project I am working on has tsx extension i.e. TypeScript.
How to convert the above code so it will work in my typescript react native mobile app?
({reducer, actions, defaultValue}: any) is expecting one argument with three properties. But when you call it, you are passing three separate arguments. So you want (reducer: any, actions: any, defaultValue: any). Likewise, a reducer takes two arguments so you want authReducer = (state: any, action: any) =>, and so on for a bunch of your functions.
Now we want to get rid of all the any and use actual types! Some of those types we can import from react and others we will define ourselves.
The part that's tricky is getting your context to know the types for your specific action creators and what arguments each one requires. You want this so that you can get autocomplete suggestions for the actions and so you can know if you are calling them improperly. But that requires more advanced typescript like generics and mapped types so just copy and paste this and don't worry too much.
import React, { useReducer, FunctionComponent, Reducer, Dispatch } from 'react';
interface DataState {
isSignedIn: boolean;
// add any other properties here
}
interface SignInProps {
email: string;
password: string;
}
// you can change this
// it is common to use a type for `Action` that is a union of your specific actions
interface Action {
type: string;
payload: any;
}
// this is where I am getting tricky
type BoundActions<T> = {
[K in keyof T]: T[K] extends (d: Dispatch<Action>) => infer R ? R : never
}
type ContextValue<T> = {
state: DataState;
} & BoundActions<T>
export const createDataContext = <T extends {}>(reducer: Reducer<DataState, Action>, actions: T, defaultValue: DataState) => {
// context needs a defaultValue
const Context = React.createContext({state: defaultValue} as ContextValue<T>);
// type of children is known by assigning the type FunctionComponent to Provider
const Provider: FunctionComponent = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {} as BoundActions<T>;
for (let key in actions) {
// #ts-ignore - I don't want to make a confusing mess so just ignore this
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
const authReducer = (state: DataState, action: Action): DataState => {
switch (action.type) {
default:
return state;
}
};
const signup = (dispatch: Dispatch<Action>) => {
return async ({ email, password }: SignInProps) => {
try {
const response = await serverApi.post('/signup', { email, password });
console.log(response.data)
} catch (err) {
console.log(err.message);
}
};
}
const signin = (dispatch: Dispatch<Action>) => {
return ({ email, password }: SignInProps) => { };
}
const signout = (dispatch: Dispatch<Action>) => {
return () => { };
}
export const { Provider, Context } = createDataContext(
authReducer,
{ signin, signout, signup },
{ isSignedIn: false }
);
The point of doing all that is to get intellisense and type checking when you consume the context.
import React, { useContext } from 'react';
import { Provider, Context } from .... // your path
const SampleComponent = () => {
// knows all of the properties available on the context
const {state, signin, signout, signup} = useContext(Context);
const handleClick = () => {
// knows that these need email and password
signin({email: '', password: ''});
signup({email: '', password: ''});
// knows that this one is ok to call with no args
signout();
}
return (
<div>{state.isSignedIn ? "Signed In!" : "Not Signed In"}</div>
)
}
const SampleApp = () => (
<Provider>
<SampleComponent/>
</Provider>
)

React create hooks with next.js

I'm trying to add toast messages to my site using createContextand useContext from react to create a hook but when I use it I got an exception TypeError: addToast is not a function
import React, {
createContext, useContext, useCallback, useState,
} from 'react';
import { v4 as uuid } from 'uuid';
import Toast from '../components/Toast';
export interface ToastMessage {
id: string;
type?: 'success' | 'error' | 'default';
title: string;
description?: string;
}
interface ToastContextData {
addToast(message: Omit<ToastMessage, 'id'>): void;
removeToast(id: string): void;
}
const ToastContext = createContext<ToastContextData>({} as ToastContextData);
export const ToastProvider: React.FunctionComponent = ({ children }) => {
const [messages, setMessages] = useState<ToastMessage[]>([]);
const addToast = useCallback(
({ type, title, description }: Omit<ToastMessage, 'id'>) => {
const id = uuid();
const toast = {
id,
type,
title,
description,
};
setMessages((state) => [...state, toast]);
}, [],
);
const removeToast = useCallback((id: string) => {
setMessages((state) => state.filter((message) => message.id !== id));
}, []);
return (
<ToastContext.Provider value={{ addToast, removeToast }}>
{children}
<Toast messages={messages} />
</ToastContext.Provider>
);
};
export function useToast(): ToastContextData {
const context = useContext(ToastContext);
if (!context) {
throw new Error('use Toast must be used within a ToastProvider');
}
return context;
}
Using like this:
import { useToast } from '../../hooks/toast';
const { addToast } = useToast();
addToast({
type: 'error',
title: 'Problemo!',
description: 'That password and login doesn`t match. Try again?',
});
Obs: that code works perfectly in pure React (createReactApp) and on this project I'm using create next app

Resources