Eslint: component definition is missing display name for Combined Context - javascript

I have a function that combines multiple context provider components and returns one combined Context Provider. I am getting an eslint error: Component definition is missing display name (eslintreact/display-name). How can I resolve the error?
const providers = [AuthProvider, CartProvider, LocationProvider];
const combineComponents = (...components: FC[]): FC => {
return components.reduce(
(AccumulatedComponents, CurrentComponent) => {
return ({ children }: ComponentProps<FC>): JSX.Element => {
return (
<AccumulatedComponents>
<CurrentComponent>{children}</CurrentComponent>
</AccumulatedComponents>
);
};
},
({ children }) => <>{children}</>
);
};
export const CombinedContextProvider = combineComponents(...providers);
The CombinedContextProvider component can then be used in App.tsx in the following way:
import React from 'react';
const App: React.FC = ({ Component, pageProps }) => {
<CombinedContextProvider>
<Component {...pageProps} />
</CombinedContextProvider>
)};
export default App;

Based on a comment that mentioned the issue was an anonymous function, I was able to resolve the error with the following code:
import React, { ComponentProps, FC } from 'react';
import { AuthProvider } from './auth_context';
import { CartProvider } from './cart_context';
import { LocationProvider } from './location_context';
const providers = [AuthProvider, CartProvider, LocationProvider];
const combineComponents = (...components: FC[]): FC => {
return components.reduce(
(AccumulatedComponents, CurrentComponent) => {
return function AccumulateComponents({ children }: ComponentProps<FC>): JSX.Element {
return (
<AccumulatedComponents>
<CurrentComponent>{children}</CurrentComponent>
</AccumulatedComponents>
);
};
},
({ children }) => <>{children}</>,
);
};
export const CombinedContextProvider = combineComponents(...providers);

Related

Nextjs 13 Hydration failed because the initial UI does not match what was rendered on the server

I am using next 13.1.0.
I have a ContextProvider that sets a light and dark theme
'use client';
import { Theme, ThemeContext } from '#store/theme';
import { ReactNode, useState, useEffect } from 'react';
interface ContextProviderProps {
children: ReactNode
}
const ContextProvider = ({ children }: ContextProviderProps) => {
const [theme, setTheme] = useState<Theme>('dark');
useEffect(() => {
const storedTheme = localStorage.getItem('theme');
if (storedTheme === 'light' || storedTheme === 'dark') {
setTheme(storedTheme);
} else {
localStorage.setItem('theme', theme);
}
// added to body because of overscroll-behavior
document.body.classList.add(theme);
return () => {
document.body.classList.remove(theme);
};
}, [theme]);
const toggle = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
};
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
};
export { ContextProvider };
I use it in my root layout
import '#styles/globals.scss';
import { GlobalContent } from '#components/GlobalContent/GlobalContent';
import { ContextProvider } from '#components/ContextProvider/ContextProvider';
import { Inter } from '#next/font/google';
import { ReactNode } from 'react';
const inter = Inter({ subsets: ['latin'] });
interface RootLayoutProps {
children: ReactNode
}
const RootLayout = ({ children }: RootLayoutProps) => {
return (
<html lang="en" className={inter.className}>
<head />
<body>
<ContextProvider>
<GlobalContent>
{children}
</GlobalContent>
</ContextProvider>
</body>
</html>
);
};
export default RootLayout;
And I consume the theme value in my GlobalContent
'use client';
import styles from '#components/GlobalContent/GlobalContent.module.scss';
import { GlobalHeader } from '#components/GlobalHeader/GlobalHeader';
import { GlobalFooter } from '#components/GlobalFooter/GlobalFooter';
import { ThemeContext } from '#store/theme';
import { ReactNode, useContext } from 'react';
interface GlobalContentProps {
children: ReactNode
}
const GlobalContent = ({ children }: GlobalContentProps) => {
const { theme } = useContext(ThemeContext);
return (
<div className={`${theme === 'light' ? styles.lightTheme : styles.darkTheme}`}>
<GlobalHeader />
<div className={styles.globalWrapper}>
<main className={styles.childrenWrapper}>
{children}
</main>
<GlobalFooter />
</div>
</div>
);
};
export { GlobalContent };
I get the error
Hydration failed because the initial UI does not match what was rendered on the server.
React docs error link
I don't understand why I am getting this error because I am accessing localStorage inside my useEffect, so I expect the HTML generated on the server to be the same with the client before the first render.
How can I solve this error?
I have made a workaround that solves the issue for now at the cost of giving up SSR.
By using a dynamic import on my ContextProvider, I disable server-rendering and the error is gone. As a bonus, the flashing issue from my default dark theme to my light theme saved on localStorage is gone. But I give up the benefits of SSR. If someone finds a better solution, please do share.
import '#styles/globals.scss';
import { GlobalContent } from '#components/GlobalContent/GlobalContent';
import { Inter } from '#next/font/google';
import dynamic from 'next/dynamic';
import { ReactNode } from 'react';
const inter = Inter({ subsets: ['latin'] });
interface RootLayoutProps {
children: ReactNode
}
// Fixes: Hydration failed because the initial UI does not match what was rendered on the server.
const DynamicContextProvider = dynamic(() => import('#components/ContextProvider/ContextProvider').then(mod => mod.ContextProvider), {
ssr: false
});
const RootLayout = ({ children }: RootLayoutProps) => {
return (
<html lang="en" className={inter.className}>
<head />
<body>
<DynamicContextProvider>
<GlobalContent>
{children}
</GlobalContent>
</DynamicContextProvider>
</body>
</html>
);
};
export default RootLayout;
This solution does not disable SSR site wide. I added a new test page with the following code
async function getData() {
const res = await fetch('https://rickandmortyapi.com/api/character', { cache: 'no-store' });
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const data = await getData();
return (
<main>
{data.results.map((c: any) => {
return (
<p key={c.id}>{c.name}</p>
);
})}
</main>
);
}
After running npm run build, I can see that the test page is using ssr
On checking the response for the test page, I can see the HTML response
I solved the error just by dynamically importing the default export of ContextProvider like this in _app.tsx . I'm also persisting context state in localStorage and it works without a problem .
_app.tsx
import dynamic from "next/dynamic";
const TodoProvider = dynamic(
() => import("#/util/context").then((ctx) => ctx.default),
{
ssr: false,
}
);
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<TodoProvider>
<Component {...pageProps} />
</TodoProvider>
);
}
context.tsx
import React, {
useState,
FC,
createContext,
ReactNode,
useEffect,
} from "react";
export const TodoContext = createContext<TodoContextType | null>(null);
interface TodoProvider {
children: ReactNode;
}
const getInitialState = () => {
if (typeof window !== "undefined") {
const todos = localStorage.getItem("todos");
if (todos) {
return JSON.parse(todos);
} else {
return [];
}
}
};
const TodoProvider: FC<TodoProvider> = ({ children }) => {
const [todos, setTodos] = useState<ITodo[] | []>(getInitialState);
const saveTodo = (todo: ITodo) => {
const newTodo: ITodo = {
id: Math.random(),
title: todo.title,
description: todo.description,
status: false,
};
setTodos([...todos, newTodo]);
};
const updateTodo = (id: number) => {
todos.filter((todo: ITodo) => {
if (todo.id === id) {
todo.status = !todo.status;
setTodos([...todos]);
}
});
};
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("todos", JSON.stringify(todos));
}
}, [todos]);
return (
<TodoContext.Provider value={{ todos, saveTodo, updateTodo }}>
{children}
</TodoContext.Provider>
);
};
export default TodoProvider;

React context not updating

I have set a basic sample project that use Context to store the page title, but when I set it the component is not rerendered.
Principal files:
Context.js
import React from 'react'
const Context = React.createContext({})
export default Context
AppWrapper.js
import React from 'react'
import App from './App'
import Context from './Context'
function AppWrapper () {
return (
<Context.Provider value={{page: {}}}>
<App />
</Context.Provider>
)
}
export default AppWrapper
App.js
import React, { useContext } from 'react';
import Context from './Context';
import Home from './Home';
function App() {
const { page } = useContext(Context)
return (
<>
<h1>Title: {page.title}</h1>
<Home />
</>
);
}
export default App;
Home.js
import React, { useContext } from 'react'
import Context from './Context'
function Home () {
const { page } = useContext(Context)
page.title = 'Home'
return (
<p>Hello, World!</p>
)
}
export default Home
full code
What am I doing wrong?
Think about React context just like you would a component, if you want to update a value and show it then you need to use state. In this case your AppWrapper where you render the context provider is where you need to track state.
import React, {useContext, useState, useCallback, useEffect} from 'react'
const PageContext = React.createContext({})
function Home() {
const {setPageContext, page} = useContext(PageContext)
// essentially a componentDidMount
useEffect(() => {
if (page.title !== 'Home')
setPageContext({title: 'Home'})
}, [setPageContext])
return <p>Hello, World!</p>
}
function App() {
const {page} = useContext(PageContext)
return (
<>
<h1>Title: {page.title}</h1>
<Home />
</>
)
}
function AppWrapper() {
const [state, setState] = useState({page: {}})
const setPageContext = useCallback(
newState => {
setState({page: {...state.page, ...newState}})
},
[state, setState],
)
const getContextValue = useCallback(
() => ({setPageContext, ...state}),
[state, updateState],
)
return (
<PageContext.Provider value={getContextValue()}>
<App />
</PageContext.Provider>
)
}
Edit - Updated working solution from linked repository
I renamed a few things to be a bit more specific, I wouldn't recommend passing setState through the context as that can be confusing and conflicting with a local state in a component. Also i'm omitting chunks of code that aren't necessary to the answer, just the parts I changed
src/AppContext.js
export const updatePageContext = (values = {}) => ({ page: values })
export const updateProductsContext = (values = {}) => ({ products: values })
export const Pages = {
help: 'Help',
home: 'Home',
productsList: 'Products list',
shoppingCart: 'Cart',
}
const AppContext = React.createContext({})
export default AppContext
src/AppWrapper.js
const getDefaultState = () => {
// TODO rehydrate from persistent storage (localStorage.getItem(myLastSavedStateKey)) ?
return {
page: { title: 'Home' },
products: {},
}
}
function AppWrapper() {
const [state, setState] = useState(getDefaultState())
// here we only re-create setContext when its dependencies change ([state, setState])
const setContext = useCallback(
updates => {
setState({ ...state, ...updates })
},
[state, setState],
)
// here context value is just returning an object, but only re-creating the object when its dependencies change ([state, setContext])
const getContextValue = useCallback(
() => ({
...state,
setContext,
}),
[state, setContext],
)
return (
<Context.Provider value={getContextValue()}>
...
src/App.js
...
import AppContext, { updateProductsContext } from './AppContext'
function App() {
const [openDrawer, setOpenDrawer] = useState(false)
const classes = useStyles()
const {
page: { title },
setContext,
} = useContext(Context)
useEffect(() => {
fetch(...)
.then(...)
.then(items => {
setContext(updateProductsContext({ items }))
})
}, [])
src/components/DocumentMeta.js
this is a new component that you can use to update your page names in a declarative style reducing the code complexity/redundancy in each view
import React, { useContext, useEffect } from 'react'
import Context, { updatePageContext } from '../Context'
export default function DocumentMeta({ title }) {
const { page, setContext } = useContext(Context)
useEffect(() => {
if (page.title !== title) {
// TODO use this todo as a marker to also update the actual document title so the browser tab name changes to reflect the current view
setContext(updatePageContext({ title }))
}
}, [title, page, setContext])
return null
}
aka usage would be something like <DocumentMeta title="Whatever Title I Want Here" />
src/pages/Home.js
each view now just needs to import DocumentMeta and the Pages "enum" to update the title, instead of pulling the context in and manually doing it each time.
import { Pages } from '../Context'
import DocumentMeta from '../components/DocumentMeta'
function Home() {
return (
<>
<DocumentMeta title={Pages.home} />
<h1>WIP</h1>
</>
)
}
Note: The other pages need to replicate what the home page is doing
Remember this isn't how I would do this in a production environment, I'd write up a more generic helper to write data to your cache that can do more things in terms of performance, deep merging.. etc. But this should be a good starting point.
Here is a working version of what you need.
import React, { useState, useContext, useEffect } from "react";
import "./styles.css";
const Context = React.createContext({});
export default function AppWrapper() {
// creating a local state
const [state, setState] = useState({ page: {} });
return (
<Context.Provider value={{ state, setState }}> {/* passing state to in provider */}
<App />
</Context.Provider>
);
}
function App() {
// getting the state from Context
const { state } = useContext(Context);
return (
<>
<h1>Title: {state.page.title}</h1>
<Home />
</>
);
}
function Home() {
// getting setter function from Context
const { setState } = useContext(Context);
useEffect(() => {
setState({ page: { title: "Home" } });
}, [setState]);
return <p>Hello, World!</p>;
}
Read more on Hooks API Reference.
You may put useContext(yourContext) at wrong place.
The right position is inner the <Context.Provider>:
// Right: context value will update
<Context.Provider>
<yourComponentNeedContext />
</Context.Provider>
// Bad: context value will NOT update
<yourComponentNeedContext />
<Context.Provider>
</Context.Provider>

Why my "mapDispatchToProps" does not see my action function?

Please tell me why, when I call this.props.getOnEvents(), an error occurs that “getOnEvents() is not a function”, what’s wrong here and how to fix it?
I’m looking at the console.log and there is this function in the props, but when I try to call, an error flies out that it’s not a function
EventCalendar.js
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import EventCalendarTable from './EventCalendarTable';
import EventCalendarView from './EventCalendarView';
const styles = theme => ({
root: {
width: '80%',
margin: '20px auto 0'
},
});
class EventCalendar extends Component {
constructor(props) {
super(props);
this.state = {
viewEvents: 'table'
};
}
componentDidMount() {
this.props.onGetEvents();
}
changeEventsView = (value) => {
this.setState({ viewEvents: value });
}
render() {
console.log(this.props);
const { classes } = this.props;
return (
<div className={classes.root}>
<EventCalendarView changeEventsView={this.changeEventsView}/>
{
this.state.viewEvents === 'table'
? <EventCalendarTable />
: <div>test</div>
}
</div>
);
}
}
export default withStyles(styles)(EventCalendar);
EventPage/component.jsx
import React from 'react';
import EventCalendar from '../../components/EventCalendar';
import './index.scss';
function Events(props) {
return (
<React.Fragment>
<EventCalendar props={props}/>
</React.Fragment>
);
}
export default Events;
EventPage/container.js
import { connect } from 'react-redux';
import EventsPage from './component';
import { getEvents } from '../../../store/modules/Events/operations';
const mapStateToProps = ({ Events }) => ({
Events
});
const mapDispatchToProps = dispatch => ({
onGetEvents: () => {
console.log(123);
dispatch(getEvents());
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(EventsPage);
Events/actions.js
import * as types from './types';
export const eventsFetch = value => ({
type: types.FETCHING_EVENTS,
payload: value
});
export const setEvents = ({ objById, arrayIds }) => ({
type: types.SET_EVENTS,
payload: {
eventById: objById,
eventsOrder: arrayIds
}
});
Events/types.js
export const FETCHING_EVENTS = 'Events/FETCHING_EVENTS';
export const SET_EVENTS = 'Events/SET_EVENTS';
Events/operation.js
import FetchClient from 'app/utils/FetchClient';
import IdsAndByIds from 'app/utils/IdsAndByIds';
import { eventsFetch, setEvents } from './actions';
export const getEvents = () => async (dispatch) => {
try {
const { data } = await FetchClient.get('/events');
dispatch(setEvents(IdsAndByIds(data)));
dispatch(eventsFetch(false));
} catch (error) {
console.log(error);
}
};
Events/reducer.js
import { createReducer } from 'store/utils';
import * as types from './types';
const usersInitState = {
fetching: true,
events: {
eventById: null,
usersOrder: null
},
error: null
};
const eventsReducer = createReducer(usersInitState)({
[types.FETCHING_EVENTS]: (state, { payload }) => ({
...state,
fetching: payload
}),
[types.SET_EVENTS]: (state, { payload }) => ({
...state,
events: {
...payload
}
})
});
export default eventsReducer;
Events/index.js
import { combineReducers } from 'redux';
import * as eventsListOperations from './operations';
import reducer from './reducers';
const EventsReducer = combineReducers({
eventsList: reducer
});
export default EventsReducer;
export { eventsListOperations };
The issue here is a minor one, Since you are connecting Events component to connect, you are receiveing the prop onGetEvents in that component, Now inside this component you are passing the props by a name props to the EventCalendar component
<EventCalendar props={props}/>
Now the props in EventCalender will contain a key called as props which wil lhave your data but you are trying to access it directly on props which is why it is undefined.
The correct way to pass the props here would be to use spread syntax like
<EventCalendar {...props}/>

React: Context to pass state between two hierarchies of components

I am developing a website in which I want to be able to access the state information anywhere in the app. I have tried several ways of implementing state but I always get following error message:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of SOS.
Here is my SOS->index.js file:
import React, { useContext } from 'react';
import axios from 'axios';
import CONST from '../utils/Constants';
import { Grid, Box, Container } from '#material-ui/core';
import { styled } from '#material-ui/styles';
import { Header } from '../Layout';
import ListItem from './ListItem';
import SOSButton from './SOSButton';
import FormPersonType from './FormPersonType';
import FormEmergencyType from './FormEmergencyType';
import StateContext from '../App';
import Context from '../Context';
export default function SOS() {
const { componentType, setComponentType } = useContext(Context);
const timerOn = false;
//'type_of_person',
const ambulance = false;
const fire_service = false;
const police = false;
const car_service = false;
//static contextType = StateContext;
const showSettings = event => {
event.preventDefault();
};
const handleComponentType = e => {
console.log(e);
//this.setState({ componentType: 'type_of_emergency' });
setComponentType('type_of_emergency');
};
const handleEmergencyType = new_emergency_state => {
console.log(new_emergency_state);
// this.setState(new_emergency_state);
};
const onSubmit = e => {
console.log('in OnSubmit');
axios
.post(CONST.URL + 'emergency/create', {
id: 1,
data: this.state //TODO
})
.then(res => {
console.log(res);
console.log(res.data);
})
.catch(err => {
console.log(err);
});
};
let component;
if (componentType == 'type_of_person') {
component = (
<FormPersonType handleComponentType={this.handleComponentType} />
);
} else if (componentType == 'type_of_emergency') {
component = (
<FormEmergencyType
handleComponentType={this.handleComponentType}
handleEmergencyType={this.handleEmergencyType}
emergencyTypes={this.state}
timerStart={this.timerStart}
onSubmit={this.onSubmit}
/>
);
}
return (
<React.Fragment>
<Header title="Send out SOS" />
<StateContext.Provider value="type_of_person" />
<Container component="main" maxWidth="sm">
{component}
</Container>
{/*component = (
<HorizontalNonLinearStepWithError
handleComponentType={this.handleComponentType}
/>*/}
</React.Fragment>
);
}
I would really appreciate your help!
Just for reference, the Context file is defined as follows:
import React, { useState } from 'react';
export const Context = React.createContext();
const ContextProvider = props => {
const [componentType, setComponentType] = useState('');
setComponentType = 'type_of_person';
//const [storedNumber, setStoredNumber] = useState('');
//const [functionType, setFunctionType] = useState('');
return (
<Context.Provider
value={{
componentType,
setComponentType
}}
>
{props.children}
</Context.Provider>
);
};
export default ContextProvider;
EDIT: I have changed my code according to your suggestions (updated above). But now I get following error:
TypeError: Cannot read property 'componentType' of undefined
Context is not the default export from your ../Context file so you have to import it as:
import { Context } from '../Context';
Otherwise, it's trying to import your Context.Provider component.
For your file structure/naming, the proper usage is:
// Main app file (for example)
// Wraps your application in the context provider so you can access it anywhere in MyApp
import ContextProvider from '../Context'
export default () => {
return (
<ContextProvider>
<MyApp />
</ContextProvider>
)
}
// File where you want to use the context
import React, { useContext } from 'react'
import { Context } from '../Context'
export default () => {
const myCtx = useContext(Context)
return (
<div>
Got this value - { myCtx.someValue } - from context
</div>
)
}
And for godsakes...rename your Context file, provider, and everything in there to something more explicit. I got confused even writing this.

Fetching data from api in componentDidMount is returning null

I am trying to fetch data in componentDidMount lifecycle method of react but I am not getting it.
my method is:
componentDidMount() {
const { taskId } = this.props
getTask(taskId)
.then(data => {
console.log(data);
this.setState({task: data});
})
}
my api is:
export const getTask = (unique_id) => {
console.log(unique_id)
return fetch('https://punctual-backend-staging.herokuapp.com/api/v1/homeowner_tasks/'+ unique_id).then(res => {
return res.json();
});
};
this is my whole component:
import React, { Component } from 'react'
import { getTask } from '../../modules/clients';
import ClientTaskShow from '../../components/tasks/ClientTaskShow'
class ClientTaskShowContainer extends Component {
constructor(props) {
super(props)
this.state = {
messageModalOpen: false,
selectedPartnerId: null,
task:{}
}
}
componentDidMount() {
console.log("hello")
const { taskId } = this.props
getTask(taskId)
.then(data => {
console.log(data);
this.setState({task: data});
})
}
render() {
const taskSelected = this.state.task;
console.log(taskSelected)
return (
<ClientTaskShow
task={taskSelected}
/>
)
}
}
export default ClientTaskShowContainer;
code from where calling clienttaskShowContainer:
import React from 'react'
import Head from 'next/head'
import Layout from '../../components/Layout'
import ClientTaskShowContainer from '../../containers/tasks/ClientTaskShowContainer'
import requireAuth from '../../lib/requireAuth'
const ClientTasksShow = ({ query }) => {
const { taskId } = query
return (
<Layout fluid fullHeight clientTaskHeader='true'>
<Head>
<title>Client Task Details | Punctual</title>
</Head>
<ClientTaskShowContainer taskId={taskId} />
</Layout>
)
}
ClientTasksShow.getInitialProps = async ({ query }) => ({
query
})
export default requireAuth(ClientTasksShow)
I think its not hitting the API even. Although it hit once I restart the server but not again. I am not able to replicate the problem.
At some sites I found we should use .then for API call others says we can't pass perimeter in API call in componentDidMount. What is the exact solution for this. Please help. Thanks in advance.
This code is working
//Calling component
import React from "react";
import CallComp from "./CallComp";
import ReactDOM from "react-dom";
function App() {
return (
<div className="App">
<CallComp taskId={"7693fbf81a33"} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// Child Component
import React, { Component } from "react";
import ClientTaskShow from "./ClientTaskShow";
class ClientTaskShowContainer extends Component {
constructor(props) {
super(props);
this.state = {
task: {}
};
}
componentDidMount() {
const { taskId } = this.props;
fetch(
`https://punctual-backend-staging.herokuapp.com/api/v1/homeowner_tasks/${taskId}`
)
.then(response => response.json())
.then(data => this.setState({ task: data }))
.catch(error => console.log("the error is", error));
}
render() {
const taskSelected = this.state.task;
console.log("task selected is ", taskSelected);
return (
<div>
{Object.keys(taskSelected).length ? (
<ClientTaskShow task={taskSelected} />
) : (
<div>No data to show</div>
)}
</div>
);
}
}
export default ClientTaskShowContainer;
// Demo ClientTaskShow
import React from "react";
const ClientTaskShow = ({ task }) => {
return <h1>{task.unique_code}</h1>;
};
export default ClientTaskShow;
Actually its working
console.log(data) returns error message from api
You should return promise from function to know api request is resolved or not
try this:
export const getTask = (id) => {
return new Promise(function (resolve, reject) {
fetch('https://punctual-backend-staging.herokuapp.com/api/v1/homeowner_tasks/' + id).then((res) => {
resolve(res.json())
})
});
}
And call like this:
componentDidMount() {
getTask(1).then((data)=>{
console.log(data);
});
}
You can replace params with your id
Hope this helps.

Categories

Resources