how to consume multiple context in reactjs - javascript

Is it possible to consume multiple context in one component which is from two different component . I tried It but but I am not getting value
This is the component which consume two different context
import React from "react";
import { UserContext } from "../App";
import { NewContext } from "./CompY";
const CompX = () => {
return (
<div>
<UserContext.Consumer>
{(user) => {
return (
<>
<NewContext.Consumer>
{(username) => {
return (
<>
<h1>Hello {user.username} </h1>
<h2>This is CompX in the {user.name} component</h2>
<h3>This is {username} </h3>
</>
);
}}
</NewContext.Consumer>
</>
);
}}
</UserContext.Consumer>
</div>
);
};
export default CompX;
Here is the App.Js

You can use multiple contexts in functional component easily with useContext
Consumer and provider is bad practice in functional component
import { UserContext } from "../App";
import { NewContext } from "./CompY";
const CompX = () => {
const user = useContext(UserContext);
const newUser = useContext(NewContext);
....
}

Related

ReactJs: How to pass api data to components?

I call api in Home.js file with componentdidmount in class component and i want to render this data in child components with functional components.when i call api in every each child component, its work but when i try to call with props coming only empty array by console.log please help.
import React,{Component} from 'react'
import '../styles/home.css'
import axios from 'axios';
import Teaser from './Teaser'
import Second from './Second'
import Opening from './Opening'
import Menu from './Menu'
export default class Home extends React.Component {
state = {
posts: []
}
componentDidMount() {
axios.get("https://graph.instagram.com/me/media?fields=id,caption,media_url,permalink,username&access_token=IG")
.then(res => {
const posts = res.data.data;
this.setState({ posts });
})
}
render() {
return (
<>
<Teaser/>
<Second/>
<Opening/>
<Menu posts = {this.state.posts}/>
</>
)
}
}
import React from 'react'
import axios from 'axios';
function Menu(props) {
const {posts} = props;
console.log(props);
return (
<>
{posts && posts.map(
(post) =>
post.caption.includes('#apegustosa_menu') &&
post.children.data.map((x) => (
<div className="menu_item" key={x.id}>
<img className="menu_img" src={x.media_url} alt="image" />
</div>
)),
)}
</>
)
}
export default Menu
Here you go:
return (
<>
{
posts && posts.map(post => (
post.caption.includes("#apegustosa_menu") && post.children.data.map(x => {
return <img src={x.media_url} alt="img"></img>
})
))
}
</>
)

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

How to render all component after an async call?

I'm new to React and I'm currently setup my first project using Gatsby. Essentially I'm creating a website that use an API created with Strapi. So far, I would like to load the navbar items using an API call like that: http://localhost:3001/sections, where for sections, I mean the items of the navbar.
For doing so, I have defined an Index page like that:
import React from "react"
import Layout from "../components/layout/layout"
import SEO from "../components/layout/seo"
import BGTState from "../context/bgt/bgtState"
import "../styles/css/begreentannery.css"
const IndexPage = () => {
return (
<BGTState>
<Layout>
<SEO title="Home" />
</Layout>
</BGTState>
)
}
export default IndexPage
the BGTState contains the getSections() method that is used inside Layout:
import React, { useContext, useEffect } from "react"
import PropTypes from "prop-types"
import { injectIntl } from "gatsby-plugin-intl"
import BGTContext from "../../context/bgt/bgtContext"
import { Spinner } from "react-bootstrap"
import Footer from "./footer"
import SearchState from "../../context/search/SearchState"
import Search from "../../components/search"
import NavbarMobile from "../../components/layout/navbarMobile"
import NavbarDesktop from "../../components/layout/navbarDesktop"
const Layout = ({ children, intl }) => {
const bgtContext = useContext(BGTContext)
const { loading, getSections, sections } = bgtContext
useEffect(() => {
getSections()
//eslint-disable-next-line
}, [])
return !loading ? (
<>
<NavbarMobile sections={sections} />
<NavbarDesktop sections={sections} />
<SearchState>
<Search />
<div className="container-fluid">
<div className="main">
{children}
<Footer />
</div>
</div>
</SearchState>
</>
) : (
<div className="container" style={{ height: "100vh" }}>
<div className="row h-100 justify-content-center align-items-center">
<Spinner animation="grow" />
</div>
</div>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default injectIntl(Layout)
the problem is in the code above, essentially I call useEffect hook which grab the sections from the API. So, until the sections are downloaded, I stop the code like so:
return !loading ? (
this is the getSections() method inside BGTState:
const getSections = async () => {
try {
setLoading()
const res = await axios.get(
`${process.env.API_URL}/sections?_sort=order:ASC`
)
dispatch({
type: GET_SECTIONS,
payload: res.data,
})
} catch (err) {
dispatch({
type: GET_SECTIONS,
payload: err.response.msg,
})
}
}
in the Index page all works fine, but the problem is in the CollectionsPage, infact I have this structure:
import React from "react"
import { injectIntl } from "gatsby-plugin-intl"
import Layout from "../components/layout/layout"
import SEO from "../components/layout/seo"
import BGTState from "../context/bgt/bgtState"
import CollectionState from "../context/collection/collectionState"
import Collection from "../components/collection"
const CollectionsPage = ({ intl }) => {
return (
<BGTState>
<Layout>
<SEO
lang={intl.locale}
title={`${intl.formatMessage({ id: "collections" })}`}
/>
<CollectionState>
<Collection id={1} />
</CollectionState>
</Layout>
</BGTState>
)
}
export default injectIntl(CollectionsPage)
essentially, the component <CollectionState> isn't mounting 'cause in Layout there is the async call on getSections().
So in Collection component, I have:
import React, { useContext, useEffect } from "react"
import CollectionContext from "../context/collection/collectionContext"
import { Link } from "gatsby"
const Collection = ({ id }) => {
const collectionContext = useContext(CollectionContext)
const { loading, collection, getCollection } = collectionContext
useEffect(() => {
getCollection(id)
}, [])
if (loading) return React.Fragment
return (
<div className="container">
<div className="row">
{/*
<img
src={`${process.env.API_URL}${collection.feature_media.url}`}
className="w-100 mt-2 mb-2"
alt={""}
/>*/}
<Link to="#" className="bg-caption bg-no-underline">
fall/winter 20/21
</Link>
</div>
</div>
)
}
export default Collection
which generate that error:
and of course getCollection is not called and will generate other errors in the Collection component
How can I revisit this mechanism? Essentially I have to:
Load all the sections
Load all the components

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