Object is undefined when trying to fetch it using useContext() - javascript

I am very new to React and was trying to make a context in React so that in my notes app such that I can trigger my custom made alert for relevant user activity but when I am trying to use the values from the context using useContext I am getting error : "Cannot destructure property 'alert' of 'Object(...)(...)' as it is undefined."
Here's the code:-
Creating Context
import React from 'react';
const AlertContext = React.createContext(null);
export default AlertContext;
Populating Value to the Context
import React,{useState} from 'react';
import AlertContext from "./AlertContext";
const ShowAlert = (props)=>{
const [alert,setAlert] = useState(null);
const showAlert = (message,type)=>{
setAlert({
msg:message,
type:type
})
setTimeout(()=>{
setAlert(null);
},3000);
}
return(
<AlertContext.Provider value={{alert,showAlert}}>
{props.children}
</AlertContext.Provider>
)
}
export default ShowAlert;
Trying to use the values
import React, { useContext } from "react";
import { Navbar, Button, Nav } from "react-bootstrap";
import { Link, useHistory } from "react-router-dom";
import ShowAlert from "../contexts/ShowAlert";
import MyAlert from "./MyAlert";
function Header() {
const {alert} = useContext(ShowAlert);
let history = useHistory();
const handleLogout = () => {
localStorage.removeItem("token");
history.push("/login");
};
return (
<>
<header>
<Navbar collapseOnSelect expand="lg" className="header">
<Navbar.Brand className="heading">
<Link to="/" className="headerLink">
Note Cloud
</Link>
<i className="fas fa-cloud-upload-alt cloudIcon"></i>
</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="me-auto"></Nav>
{localStorage.getItem("token") && (
<Nav>
<Nav.Link>
<Button variant="primary" size="lg" onClick={handleLogout}>
Logout
</Button>
</Nav.Link>
</Nav>
)}
</Navbar.Collapse>
</Navbar>
</header>
<MyAlert alert={alert}></MyAlert>
</>
);
}
export default Header;
Edit:- MyAlert Component
import React, { useContext } from "react";
import { Alert } from "react-bootstrap";
import ShowAlert from "../contexts/ShowAlert";
const MyAlert = (props) => {
const {alert} = useContext(ShowAlert);
const capitalize = (word) => {
if(word==="danger")
{
word = "error";
}
const lower = word.toLowerCase();
return lower.charAt(0).toUpperCase() + lower.slice(1);
};
return (
<div style={{ height: "50px" , width:"100%"}}>
{alert && (
<Alert
variant={alert.type}
>
<Alert.Heading>{capitalize(alert.type)}</Alert.Heading>
<p>{capitalize(alert.msg)}</p>
</Alert>
)}
</div>
);
};
export default MyAlert;
Error That I am getting

Try doing the Context like this instead of null.
import React from "react";
const AlertContext = React.createContext({
alert: {},
setAlert: (alert) => {},
});
export default

i think your export is the fail
export const AlertContext = React.createContext({})
or as well you can try:
< AlertContext.Consumer>
{(context) => {
console.log(context)
}}
</AlertContext.Consumer>

Related

React components spams read on firebase collection

So recently i tried making a chat app using React and firebase, to learn these tools. It goes very well, but for some reason it seems that whenever the database is active it spams read on the collection, even with no changes. I suspect some element is rerendering in an infinite loop, but i can't seem to fix it. Any suggestions for how i can display messages and only update whenever a new change is made to the collection?
import React, { useRef, useState } from 'react';
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useCollectionData } from 'react-firebase-hooks/firestore';
import './App.css';
import Navbar from './Navbar';
firebase.initializeApp({
// My configs are here
})
const auth = firebase.auth();
const firestore = firebase.firestore();
function App() {
const [user] = useAuthState(auth);
return (
<>
<div className="App">
<Navbar />
</div>
<section className="place-content-end">
{user ? <ChatRoom /> : <SignIn />}
<SignOut />
</section>
</>
);
}
function SignIn() {
const signInWithGoogle = () => {
const provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider);
}
return (
<>
<button className="sign-in ml-20 pl-5" onClick={signInWithGoogle}>Sign in with Google to join! </button>
<p className="ml-20 pl-5">Welcome to the chat!</p>
</>
)
}
function SignOut() {
return auth.currentUser && (
<button className="sign-out ml-20 pl-5" onClick={() => auth.signOut()}>Sign Out</button>
)
}
function ChatRoom() {
const messagesRef = firestore.collection('messages');
const query = messagesRef.orderBy('createdAt').limit(50);
const [messages] = useCollectionData(query, { idField: 'id' });
return (
<>
<div className="App ml-20 pl-5">
<h1>HELLO WORLD!</h1>
<main>
{messages && messages.map(msg => <DisplayMessage key={msg.id} message={msg} />)}
</main>
</div>
</>
);
}
function DisplayMessage(props) {
const { text, uid } = props.message;
return (<>
<div>
<p>{text}</p>
</div>
</>)
}
export default App;
And my firebase looks like this after a few minutes online on my local server
Try this and see if it works:
import React, { useRef, useState, useEffect } from "react";
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";
import { useAuthState } from "react-firebase-hooks/auth";
import { useCollectionData } from "react-firebase-hooks/firestore";
import "./App.css";
import Navbar from "./Navbar";
firebase.initializeApp({
// My configs are here
});
const auth = firebase.auth();
const firestore = firebase.firestore();
function App() {
const [user] = useAuthState(auth);
return (
<>
<div className="App">
<Navbar />
</div>
<section className="place-content-end">
{user ? <ChatRoom /> : <SignIn />}
<SignOut />
</section>
</>
);
}
function SignIn() {
const signInWithGoogle = () => {
const provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider);
};
return (
<>
<button className="sign-in ml-20 pl-5" onClick={signInWithGoogle}>
Sign in with Google to join!{" "}
</button>
<p className="ml-20 pl-5">Welcome to the chat!</p>
</>
);
}
function SignOut() {
return (
auth.currentUser && (
<button className="sign-out ml-20 pl-5" onClick={() => auth.signOut()}>
Sign Out
</button>
)
);
}
function ChatRoom() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const messagesRef = firestore.collection("messages");
const query = messagesRef.orderBy("createdAt").limit(50);
const [data] = useCollectionData(query, { idField: "id" });
setMessages(data);
}, []);
return (
<>
<div className="App ml-20 pl-5">
<h1>HELLO WORLD!</h1>
<main>
{messages.length > 0 &&
messages.map((msg) => (
<DisplayMessage key={msg.id} message={msg} />
))}
</main>
</div>
</>
);
}
function DisplayMessage(props) {
const { text, uid } = props.message;
return (
<>
<div>
<p>{text}</p>
</div>
</>
);
}
export default App;
I added useEffect in line 1. And changed your ChatRoom component.
Quick explanation
useEffect is part of the lifecycle of a Function Component. You give it a function to execute whenever the component mounts or props or state of the component changes. You decide this by what you add as the second argument of the useEffect call. If you give it an [] (empty array), it will only execute when the component is mounted. If you give, for example, [messages] (which a state created by useState) then the function will execute on component mount AND whenever messages change. React keeps track of this for you, this is why React is called react, if data changes it reacts to it and renders accordingly.
The above explanation is the basics of it. But there is also one more cool thing it can do:
If you return a function from inside of useEffect, this function will execute when the component is UNmounted. Like so:
useState(() => {
console.log("Component mounted");
return () => {
console.log("Component unmounted");
};
},[]);
In Class Components we have similar functions, they are called: componentDidMount, componentWillUnmount and others.You can read about it here React State & Lifecyle

Currently unable to display svg images from API in React app

Issue that I am currently having is displaying the svg images I am getting from an API for countries. Right now they are showing up as empty divs in the HTML and no errors. I am also using the ReactSVG package and still no luck.
Here below is the Home component which is making the API call and the Card component that fed the content:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { Container, Row } from 'react-bootstrap';
import CardComponent from '../components/Card';
const baseURL = 'https://restcountries.eu/rest/v2/all';
const Home = () => {
const [ countries, setCountries ] = useState(null);
useEffect(() => {
console.log('hello')
axios.get(baseURL).then((res) => {
setCountries(res.data);
})
}, []);
return (
<>
<Container>
<Row>
{countries && (
countries.map((country) => {
console.log(country.flag)
return <CardComponent key={country.name}
title={country.name}
image={country.flag}
population={country.population}
region={country.region}
capital={country.capital}/>
})
)}
</Row>
</Container>
</>
)
}
export default Home;
import React from 'react';
import {ReactSVG} from 'react-svg';
import { Card } from 'react-bootstrap';
const CardComponent = (props) => {
const { title, flag, population, region, capital } = props;
return (
<Card>
<ReactSVG src={flag}/>
<Card.Body>
<Card.Title>{title}</Card.Title>
<Card.Text>Population: <span id="Population">{population}</span><br></br></Card.Text>
<Card.Text>Region: <span id="Region">{region}</span><br></br></Card.Text>
<Card.Text>Capital: <span id="Capital">{capital}</span><br></br></Card.Text>
</Card.Body>
</Card>
)
}
export default CardComponent;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Here is also a link to my Git repo for this project https://github.com/codejohnson89/react-countries
In your code, there is some error. Please check this code.
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { Container, Row } from 'react-bootstrap';
import CardComponent from '../components/Card';
const baseURL = 'https://restcountries.eu/rest/v2/all';
const Home = () => {
const [ countries, setCountries ] = useState(null);
useEffect(() => {
console.log('hello')
axios.get(baseURL).then((res) => {
setCountries(res.data);
})
}, []);
return (
<>
<Container>
<Row>
{countries && (
countries.map((country) => {
console.log(country.flag)
return <CardComponent key={country.name}
title={country.name}
image={country.flag}
population={country.population}
region={country.region}
capital={country.capital}/>
})
)}
</Row>
</Container>
</>
)
}
export default Home;
import React from 'react';
import {ReactSVG} from 'react-svg';
import { Card } from 'react-bootstrap';
const CardComponent = (props) => {
const { title, image, population, region, capital } = props;
return (
<Card>
<img src={image} alt="title" width="80"/>
<Card.Body>
<Card.Title>{title}</Card.Title>
<Card.Text>Population: <span id="Population">{population}</span><br></br></Card.Text>
<Card.Text>Region: <span id="Region">{region}</span><br></br></Card.Text>
<Card.Text>Capital: <span id="Capital">{capital}</span><br></br></Card.Text>
</Card.Body>
</Card>
)
}
export default CardComponent;

How to implement material-ui Snackbar as a global function?

I am creating my react app with material-ui Snackbar.
In my project I have a lot of components and don't want to insert <Snackbar/> in each of them.
Is there a way to create function that will show snackbar, then just import and use this function in each component?
Something like:
import showSnackbar from 'SnackbarUtils';
showSnackbar('Success message');
You have to do it in react way. You can achieve this by creating a Higher Order Component.
Create a HOC that returns a snackbar component along with the wrappedComponent
Create a function in that HOC which accepts message, severity (if you are using Alert like me), duration and sets the appropriate states which are set to the props of the snackbar. And pass that function as a prop to the wrappedComponent.
Finally import this HOC wherever you want to display a snackbar, pass your component in it and call the HOC function from the prop (this.prop.functionName('Hello there!')) in the event handler where you want to display a snackbar and pass in a message.
Check this out.
https://stackblitz.com/edit/snackbar-hoc?file=src/SnackbarHOC.js
extend it as a Hook, and then you can call it once and use state with effects to show:
import { useSnackbar } from 'notistack';
import IconButton from "#mui/material/IconButton";
import CloseIcon from "#mui/material/SvgIcon/SvgIcon";
import React, {Fragment, useEffect, useState} from "react";
const useNotification = () => {
const [conf, setConf] = useState({});
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const action = key => (
<Fragment>
<IconButton onClick={() => { closeSnackbar(key) }}>
<CloseIcon />
</IconButton>
</Fragment>
);
useEffect(()=>{
if(conf?.msg){
let variant = 'info';
if(conf.variant){
variant = conf.variant;
}
enqueueSnackbar(conf.msg, {
variant: variant,
autoHideDuration: 5000,
action
});
}
},[conf]);
return [conf, setConf];
};
export default useNotification;
Then you can use it:
const [msg, sendNotification] = useNotification();
sendNotification({msg: 'yourmessage', variant: 'error/info.....'})
Here is a sample code for fully working example using Redux, Material-ui and MUI Snackbar
import { random } from 'lodash'
import { Action } from 'redux'
import actionCreatorFactory, { isType } from 'typescript-fsa'
const actionCreator = actionCreatorFactory()
export type Notification = {
message: string
}
export type NotificationStore = Notification & {
messageId: number
}
export const sendNewNotification =
actionCreator<Notification>('NEW_NOTIFICATION')
const defaultState: NotificationStore = { message: '', messageId: 1 }
const reducer = (
state: NotificationStore = defaultState,
action: Action
): NotificationStore => {
if (isType(action, sendNewNotification)) {
const {
payload: { message }
} = action
return { message, messageId: random(0, 200000) }
}
return state
}
export default reducer
// useNotification to get state from Redux, you can include them into same file if you prefer
import { NotificationStore } from './notification'
export function useNotification(): NotificationStore {
return useSelector<NotificationStore>(
(state) => state.notification
)
}
// Notification React-component - Notification.tsx
import React, { useState } from 'react'
import Button from '#mui/material/Button'
import Snackbar from '#mui/material/Snackbar'
import IconButton from '#mui/material/IconButton'
import CloseIcon from '#mui/icons-material/Close'
type Props = {
message: string
}
export function Notification({ message }: Props): JSX.Element | null {
const [notiOpen, setNotiOpen] = useState(true)
if (!message) {
return null
}
return (
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
open={notiOpen}
autoHideDuration={10000}
onClose={() => setNotiOpen(false)}
message={message}
action={
<React.Fragment>
<Button
color="secondary"
size="small"
onClick={() => setNotiOpen(false)}
>
Close
</Button>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={() => setNotiOpen(false)}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
}
/>
)
}
// Main App.tsx to run my application
import { Notification } from "./Notification.tsx"
import { useDispatch } from 'react-redux'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
const App: React.FC<AppProps> = () => {
const dispatch = useDispatch()
const { message, messageId } = useNotification()
return (
<ThemeProvider theme={appTheme}>
<Router>
<Switch>
<Route path="/public/:projectId" component={ProjectPage} />
<Route path="/login" component={LoginPage} />
<Route render={() => <PageNotFound />} />
</Switch>
</Router>
<Notification key={messageId} message={message} />
</ThemeProvider>
)
}
export default App
// Usage of hook in application - FileSomething.tsx
import { useDispatch } from 'react-redux'
import { useEffect } from 'react'
import { sendNewNotification } from 'src/redux/notification'
export function FileSomething(): JSX.Element {
function sendNotification() {
dispatch(
sendNewNotification({
message: 'Hey, im a notification'
})
)
}
useEffect(() => {
sendNotification()
}, [])
return (
<div>Component doing something</div>
)
}

React useContext returns default values

I'm trying to create a darkmode library (named react-goodnight) based on https://github.com/luisgserrano/react-dark-mode.
This is where the context is created.
import React from 'react'
const ThemeContext = React.createContext({
theme: '',
toggle: () => {}
})
export default ThemeContext
This is my useDarkMode hook that get/sets the theme to localStorage.
import { useState, useEffect } from 'react'
const useDarkMode = () => {
const [theme, setTheme] = useState('light')
const setMode = (mode) => {
window.localStorage.setItem('theme', mode)
setTheme(mode)
}
const toggle = () => (theme === 'light' ? setMode('dark') : setMode('light'))
useEffect(() => {
const localTheme = window.localStorage.getItem('theme')
localTheme && setTheme(localTheme)
}, [])
return [theme, toggle]
}
export default useDarkMode
This is the index of my library (react-goodnight).
import React, { useContext } from 'react'
import { ThemeProvider } from 'styled-components'
import { GlobalStyles } from './globalStyles'
import { lightTheme, darkTheme } from './settings'
import ThemeContext from './themeContext'
import useDarkMode from './useDarkMode'
const Provider = ({ children }) => {
const [theme, toggle] = useDarkMode()
return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<GlobalStyles />
<ThemeContext.Provider value={{ theme, toggle }}>
<button onClick={toggle}>Toggle</button>
{children}
</ThemeContext.Provider>
</ThemeProvider>
)
}
export const useDarkModeContext = () => useContext(ThemeContext)
export default Provider
And, in the end, this is my example app where I'm trying to use it.
import React from 'react'
import Provider, { useDarkModeContext } from 'react-goodnight'
const App = () => {
const { theme, toggle } = useDarkModeContext();
console.log(theme)
return (
<Provider>
<div>hey</div>
<button onClick={toggle}>Toggle</button>
</Provider>
)
}
export default App
The "Toggle" button in the library's index works fine but the one in my example app does not.
The useDarkModeContext() returns empty.
What could be the issue?
Thanks!
You are doing wrong
1st option
you can use react-goodnight provider with your index.js and use useDarkModeContext(), don't name your index.js Provider else you can not use Provider coming from react-goodnight
import Provider, { useDarkModeContext } from 'react-goodnight'
const Provider = ({ children }) => {
const [theme, toggle] = useDarkMode()
return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<GlobalStyles />
<Provider>
<ThemeContext.Provider value={{ theme, toggle }}>
<button onClick={toggle}>Toggle</button>
{children}
</ThemeContext.Provider>
</Provider>
</ThemeProvider>
)
}
2nd Option
you are passing ThemeContext in your index.js so you can also access that in app.js
import React, { useContext } from 'react'
import ThemeContext from './themeContext'
const App = () => {
const theme = useContext(ThemeContext);
console.log(theme)
return (
<Provider>
<div>hey</div>
<button onClick={toggle}>Toggle</button>
</Provider>
)
}
export default App
The reason it's not working is because you are calling useContext in the very same place where you print Provider.
Why is that wrong? Because useContext looks for parent context providers. By rendering Provider in the same place you call useContext, there is no parent to look for. The useContext in your example is actually part of App component, who is not a child of Provider.
All you have to do is move the button outside of that print, to its own component, and only there do useContext (or in your case the method called useDarkModeContext.
The only change would be:
import React from 'react'
import Provider, { useDarkModeContext } from 'react-goodnight'
const App = () => {
return (
<Provider>
<div>hey</div>
<ToggleThemeButton />
</Provider>
)
}
export default App
const ToggleThemeButton = () => {
const { theme, toggle } = useDarkModeContext();
return (
<button onClick={toggle}>Switch Theme outside</button>
);
};

React hooks and context api localstorage on refresh

In my SPA, I am utilizing react hooks and context API. I need to persist the current state of the component view rendered using the context API so that I can implement the global component conditional rendering through the application.
I have two views on a single dashboard page: overview & detail. The button triggers the global state change and the view should be fixed on the state value even on page refresh.
Here's my code snippets:
AppRoutes file
import React, { useState } from "react";
import { Router, Route, Switch } from "react-router-dom";
import history from "../utils/history";
import { PyramidProvider } from "../context/pyramidContext";
import Dashboard from "../pages/dashboard/Dashboard";
const AppRoutes = () => {
return (
<div>
<React.Suspense fallback={<span>Loading...</span>}>
<Router history={history}>
<Switch>
<PyramidProvider>
<Route path="/" component={Dashboard} />
</PyramidProvider>
</Switch>
</Router>
</React.Suspense>
</div>
);
};
export default AppRoutes;
Dashboard page
import React, { useState, useEffect, useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
import PyramidDetail from "../../components/pyramidUI/pyramidDetail";
import PyramidOverview from "../../components/pyramidUI/pyramidOverview";
const Dashboard = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<React.Fragment>
{info.uiname === "overview" ? <PyramidOverview /> : <PyramidDetail />}
</React.Fragment>
);
};
export default Dashboard;
Overview component
import React, { useState, useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
const Overview = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<div className="d-flex flex-column dashboard_wrap">
<main>
<div className="d-flex">
<button
onClick={() => setInfo({ uiname: "detail", pyramidvalue: 1 })}
>
change view
</button>
</div>
</main>
</div>
);
};
export default Overview;
Detail component
import React, { useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
// import axios from "axios";
const Detail = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<div className="d-flex flex-column dashboard_wrap">
<h2>Detail View</h2>
<div>
<button
type="button"
onClick={() => setInfo({ uiname: "overview", pyramidvalue: 0 })}
>
Back
</button>
</div>
</div>
);
};
export default Detail;
Context File
import React, { createContext, useEffect, useReducer } from "react";
let reducer = (info, newInfo) => {
return { ...info, ...newInfo };
};
const initialState = {
uiname: "overview",
pyramidvalue: 0,
};
const localState = JSON.parse(localStorage.getItem("pyramidcontent"));
const PyramidContext = createContext();
function PyramidProvider(props) {
const [info, setInfo] = useReducer(reducer, initialState || localState);
useEffect(() => {
localStorage.setItem("pyramidcontent", JSON.stringify(info));
}, [info]);
return (
<PyramidContext.Provider
value={{
info,
setInfo,
}}
>
{props.children}
</PyramidContext.Provider>
);
}
export { PyramidContext, PyramidProvider };
I click the button to render a detail view and soon as the page is refreshed, the component changes its view to overview instead of sticking around to detail. I checked the local storage values, and it is being updated properly, but still, the component view does not persist as per the value.
I am unable to understand where I am doing wrong, any help to resolve this issue, please? Thanks in advance.
You're never using the value of localStage in your info state,
you should replace your code with:
const [info, setInfo] = useReducer(reducer, localState || initialState);

Categories

Resources