import { useState } from 'react';
export default function usePrivacyMode() {
const [isPrivacyOn, setIsPrivacyOn] = useState(false);
return {
isPrivacyOn,
setIsPrivacyOn
};
}
This is my custom hook. I set the state in PrivacyIcons component, and then I use isPrivacyOn for show/hide values from a table based on the value. But in a different component the isPrivacyOn is not changed, it's changed only in PrivacyIcons? Why I can't change it in one component and then use the value across all components? Thanks.
states are not meant to be shared across components. You are looking for useContext. This allows you to share a function and a state between components. React has an excellent tutorial on how to do it in the official documentation: https://reactjs.org/docs/hooks-reference.html#usecontext
For your specific example it would look something like this:
Your App.js
import { useState } from 'react';
export const PrivacyContext = createContext([]);
const App = (props) => {
const [isPrivacyOn, setIsPrivacyOn] = useState(false);
return (
<PrivacyContext.Provider value={[isPrivacyOn, setIsPrivacyOn]}>
<ComponentUsingPrivacyContext />
{props.children}
</PrivacyContext.Provider>
);
};
export default App;
Keep in mind that any component that wants access to that context must be a child of PrivacyContext
Any component that wants to use PrivacyContext:
import React, { useContext } from "react";
import {PrivacyContext} from "...your route";
const ComponentUsingPrivacyContext = (props) => {
const [isPrivacyOn, setIsPrivacyOn] = useContext(PageContext);
return (
<button onclick={setIsPrivacyOn}>
Turn Privacy On
</button>
<span>Privacy is: {isPrivacyOn}</span>
);
};
export default ComponentUsingPrivacyContext;
Related
How to make and get props in all components with the funcional component and hooks?
at example iam making a js file with class with functions inside like getBooks(), getNotes(), then i making a context file and importing this in index.js and use a class for provider value, like below.
import {BookStoreContext} from "./components/bookstore-service-context";
import {BookStoreService} from "./services";
const bookStoreService = new BookStoreService();
ReactDOM.render(
<Provider store={store}>
<ErrorBoundry>
<BookStoreContext.Provider value={BookStoreService}>
<BrowserRouter>
<App/>
</BrowserRouter>
</BookStoreContext.Provider>
</ErrorBoundry>
</Provider>,
document.getElementById('root')
);
Once our Context is ready ( A global data to be used in the project).We can use useContext() Hook to consume our context in React-Hooks. This way we avoid class Based ContextName.Consumer Component in our Application.
The will provide step by step guide about how to work with Context API in Hooks with the example below.
1. First we create our Context File (Which will have our Global Data)
import React, { useState, createContext } from "react";
const books = [
{ id: 1, name: "The way of the kings" },
{ id: 2, name: "The people of Paradise" },
{ id: 3, name: "Protest of the Farmers" }
];
// First we need to create our context
const BookContext = createContext();
// createContext returns 2 things. Provider and Consumer. We will only need Provider
const BookContextProvider = ({ children }) => {
const [books, setBooks] = useState(books);
const removeBook = (id) => {
setBooks(books.filter((book) => book.id !== id));
};
return (
<BookContext.Provider value={{ books, removeBook }}>
{children}
</BookContext.Provider>
);
};
export default BookContextProvider;
We used createContext() method to create our Context. This returns (as before Hooks also) 2 things. Consumer and Provider. Since we will work with Hooks we only need Provider and in place of Consumer, we'll use useContext() in our components to consume this context.
2. Our Context is ready now. (Note:BookContext is our Context and BookContextProvider is simply a component in which we have our Context data). We will need to wrap our entire App aroundBookContextProvder Component so that all the Child Components used in the Application will have access to the Global Context.
import React, { createContext, useState, useEffect } from "react";
import "./styles.css";
import BookContextProvider from "./BookContext";
export default function App() {
return (
<div className="App">
<BookContextProvider>
<BookList/>
</BookContextProvider>
</div>
);
}
If you notice, I have Used BookList Component within BookContextProvider, that is to do with the setup we did in our Context file, where we used {children}. So, BookList Component is passed as children prop to the BookContextProvider Component in our BookContext.js file. (This may take some time for newbies to grasp the concept).
3. Once All the setup is ready we can consume context in our Child Components:
So in my BookList Component, I want to access books and also have the access to the removeBook handler. *We make use of useContext() Hook to do that.*
import React, { useContext } from "react";
import { BookContext } from "./BookContext";
const BookList = () => {
const { books, removeBook } = useContext(BookContext);
console.log(books); // We have our Books available now
console.log(removeBook); // We have our removeBook Handler as well
return (
<div>
<h1>BookList Component</h1>
{books.length > 0 &&
books.map((book) => {
return <div key={book.id}>{book.name}</div>;
})}
</div>
);
};
export default BookList;
In the above BookList Component we are now consuming our Context
using useContext() Hook.
COMPLETE CODESANDBOX DEMO: https://codesandbox.io/s/react-context-and-hooks-vcsfn?file=/src/App.js
See useContext.
Just do
// functional component
const someComponent = () => {
const BookStoreService = useContext(BookStoreContext);
// calling a method in the BookStoreService class
BookStoreService.getBooks();
return <></>
}
Note that if your methods like getBooks() are asynchronous (i.e. fetch data from a server), it's probably best to call them within a side effect hook like useEffect.
What I want:
I'm trying to add a dynamic theme option to a react-styleguidist project I'm working on. Following the idea laid out in this unfinished and closed pr, I added a custom ThemeSwitcher component, which is a select menu that is rendered in the table of contents sidebar. Selecting an option should update the brand context, which renders the corresponding theme using styled-components' BrandProvider. It should function like the demo included with the closed pr: https://fancy-sg.surge.sh/.
What's not working:
I can't access the same context in my ThemedWrapper as is provided and updated in the StyleguideWrapper and ThemeSwitcher. Examining the tree in the React Components console, it looks like react-styleguidist may render ReactExample outside of the StyleguideRenderer, which means it loses the context from the provider in that component.
Assuming I'm correct about the context not updating in ThemedWrapper due to it being located outside of StyleGuideRenderer, two high level ideas I have (but haven't been able to figure out how to do) are:
Find the correct component that is an ancestor of both StyleGuideRenderer and ReactExample in the react-styleguidist library and add the BrandProvider there so that ThemedWrapper now has context access
Some other context configuration that I haven't found yet that will allow two components to consume the same context without having a provider as an ancestor (is this possible??)
What I have:
Here are the condensed versions of the relevant code I'm using.
brand-context.js (exports context and provider, inspired by Kent C Dodds
import React, { createContext, useState, useContext } from 'react';
const BrandStateContext = createContext();
const BrandSetContext = createContext();
function BrandProvider({ children, theme }) {
const [brand, setBrand] = useState(theme);
return (
<BrandStateContext.Provider value={brand}>
<BrandSetContext.Provider value={(val) => setBrand(val)}>
{children}
</BrandSetContext.Provider>
</BrandStateContext.Provider>
);
}
function useBrandState() {
return useContext(BrandStateContext);
}
function useBrandSet() {
return useContext(BrandSetContext);
}
export { BrandProvider, useBrandState, useBrandSet };
StyleGuideWrapper.jsx (Copy of rsg-components/StyleguideRenderer, with addition of ThemeSwitcher component to toggle theme from ui; passed in styleguide config as StyleGuideRenderer)
import React from 'react';
import cx from 'clsx';
import Styled from 'rsg-components/Styled';
import ThemeSwitcher from './ThemeSwitcher';
import { BrandProvider } from './brand-context';
export function StyleGuideRenderer({ children, classes, hasSidebar, toc }) {
return (
<BrandProvider>
<div className={cx(classes.root, hasSidebar && classes.hasSidebar)}>
<main className={classes.content}>
{children}
</main>
{hasSidebar && (
<div className={classes.sidebar} data-testid="sidebar">
<section className={classes.sidebarSection}>
<ThemeSwitcher classes={classes} />
</section>
{toc}
</div>
)}
</div>
</BrandProvider>
);
}
StyleGuideRenderer.propTypes = propTypes;
export default Styled(styles)(StyleGuideRenderer);
ThemeSwitcher.jsx
import React from 'react';
import Styled from 'rsg-components/Styled';
import { useBrandSet, useBrandState } from './brand-context';
const ThemeSwitcher = ({ classes }) => {
const brand = useBrandState();
const setBrand = useBrandSet();
const onBrandChange = (e) => setBrand(e.target.value);
const brands = ['foo', 'bar'];
return (
<label className={classes.root}>
Brand
<select value={brand} onChange={onBrandChange}>
{brands.map((brand) => (
<option key={brand} value={brand}>{brand}</option>
))}
</select>
</label>
);
};
export default Styled(styles)(ThemeSwitcher);
ThemedWrapper.jsx (passed in styleguide config as Wrapper, and wraps each example component to provide them to styled-components)
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { BrandStateContext } from './brand-context';
const LibraryProvider = ({ brand, children }) => {
return (
<ThemeProvider theme={brand}>{children}</ThemeProvider>
);
};
function ThemedWrapper({ children }) {
return (
<BrandStateContext.Consumer>
{brand => (
<LibraryProvider brand={brand}>{children}</LibraryProvider>
)}
</BrandStateContext.Consumer>
);
}
export default ThemedWrapper;
I create a context and a provider as below. As you can see, I use useState() within my provider (for state) along with functions (all passed within an object as the value prop, allows for easy destructuring whatever I need in child components).
import React, { useState, createContext } from "react";
const CountContext = createContext(null);
export const CountProvider = ({ children }) => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
const decrementCount = () => {
setCount(count - 1);
};
return (
<CountContext.Provider value={{ count, incrementCount, decrementCount }}>
{children}
</CountContext.Provider>
);
};
export default CountContext;
I wrap my app within such a provider(s) at a higher location such as at index.js.
And consume the state using useContext() as below.
import React, { useContext } from "react";
import CountContext from "../contexts/CountContext";
import Incrementer from "./Incrementer";
import Decrementer from "./Decrementer";
const Counter = () => {
const { count } = useContext(CountContext);
return (
<div className="counter">
<div className="count">{count}</div>
<div className="controls">
<Decrementer />
<Incrementer />
</div>
</div>
);
};
export default Counter;
Everything is working just fine, and I find it easier to maintain things this way as compared to some of the other methods of (shared) state management.
CodeSandbox: https://codesandbox.io/s/react-usecontext-simplified-consumption-hhfz6
I am wondering if there is a fault or flaw here that I haven't noticed yet?
One of the key differences with other state management tools like Redux is performance.
Any child that uses a Context needs to be nested inside the ContextProvider component. Every time the ContextProvider state changes it will render, and all its (non-memoized) children will render too.
In contrast, when using Redux we connect each Component to the store, so each component will render only if the part of the state it is connect to changes.
I'm struggling to understand how to proceed with a small React app I am making.
I have a budget tracker, where you can add costs (mortgage, bills etc.) and they have a cost value. Each time you add, edit or delete one of these, I want the global state to change, which is stored in a context.
I basically have a 'remaining balance' value, that I want to recalculate each time something changes.
I figured I'd use a life cycle method or useEffect, but when I use that in my App.js (so that it watches for changes in all subcomponents), I can't get it to work, because the life cycle method is calling a method from my Context, but because it's not wrapped in the provider, it can't access the method in the Context.
Is this a common problem and is there are recommended way to fix it? I can't seem to find a similar problem on the GoOgLe.
App.js:
import React, { useState, useContext, useEffect } from "react";
import "./css/main.css";
import Header from "./layout/Header";
import BudgetInfo from "./components/BudgetInfo";
import PaymentForm from "./components/PaymentForm";
import CostToolbar from "./components/CostToolbar";
import Costs from "./components/Costs";
import BudgetContext from "./context/budgetContext";
import BudgetState from "./context/BudgetState";
const App = () => {
const budgetContext = useContext(BudgetContext);
const { updateBalance } = budgetContext;
useEffect(() => {
updateBalance();
});
return (
<BudgetState>
<Header darkModeToggle={toggleDarkMode} />
<main
className={"main-content" + (darkMode.darkMode ? " dm-active" : "")}
>
<div className="wrap content-wrap">
<BudgetInfo />
<PaymentForm />
<CostToolbar />
<Costs />
</div>
</main>
</BudgetState>
);
};
export default App;
You need to wrap the App component. Try the simple example.
import React, { useEffect, useContext } from 'react';
import ThemeContext from './../context/context';
const Sample = () => {
const context = useContext(ThemeContext);
useEffect(() => {
console.log(context,'--')
},[])
return(
<ThemeContext.Consumer>
{color => (
<p style={{ color }}>
Hello World
</p>
)}
</ThemeContext.Consumer>
)
}
export default Sample;
I'm trying to port from class component to react hooks with Context API, and I can't figure out what is the specific reason of getting the error.
First, my Codes:
// contexts/sample.jsx
import React, { createContext, useState, useContext } from 'react'
const SampleCtx = createContext()
const SampleProvider = (props) => {
const [ value, setValue ] = useState('Default Value')
const sampleContext = { value, setValue }
return (
<SampleCtx.Provider value={sampleContext}>
{props.children}
</SampleCtx.Provider>
)
}
const useSample = (WrappedComponent) => {
const sampleCtx = useContext(SampleCtx)
return (
<SampleProvider>
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} />
</SampleProvider>
)
}
export {
useSample
}
// Sends.jsx
import React, { Component, useState, useEffect } from 'react'
import { useSample } from '../contexts/sample.jsx'
const Sends = (props) => {
const [input, setInput ] = useState('')
const handleChange = (e) => {
setInput(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault()
props.setValue(input)
}
useEffect(() => {
setInput(props.value)
}, props.value)
return (
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleChange} />
<button type="submit">Submit</button>
</form>
)
}
Error I got:
Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
Explanation for my code:
I used Context API to manage the states, and previously I used class components to make the views. I hope the structure is straightforward that it doesn't need any more details.
I thought it should work as well, the <Sends /> component gets passed into useSample HoC function, and it gets wrapped with <SampleProvider> component of sample.jsx, so that <Sends /> can use the props provided by the SampleCtx context. But the result is failure.
Is it not valid to use the HoC pattern with React hooks? Or is it invalid to hand the mutation function(i.e. setValue made by useState()) to other components through props? Or, is it not valid to put 2 or more function components using hooks in a single file? Please correct me what is the specific reason.
So HOCs and Context are different React concepts. Thus, let's break this into two.
Provider
Main responsibility of the provider is to provide the context values. The context values are consumed via useContext()
const SampleCtx = createContext({});
export const SampleProvider = props => {
const [value, setValue] = useState("Default Value");
const sampleContext = { value, setValue };
useEffect(() => console.log("Context Value: ", value)); // only log when value changes
return (
<SampleCtx.Provider value={sampleContext}>
{props.children}
</SampleCtx.Provider>
);
};
HOC
The consumer. Uses useContext() hook and adds additional props. Returns a new component.
const withSample = WrappedComponent => props => { // curry
const sampleCtx = useContext(SampleCtx);
return (
<WrappedComponent
{...props}
value={sampleCtx.value}
setValue={sampleCtx.setValue}
/>
);
};
Then using the HOC:
export default withSample(Send)
Composing the provider and the consumers (HOC), we have:
import { SampleProvider } from "./provider";
import SampleHOCWithHooks from "./send";
import "./styles.css";
function App() {
return (
<div className="App">
<SampleProvider>
<SampleHOCWithHooks />
</SampleProvider>
</div>
);
}
See Code Sandbox for full code.
Higher order Components are functions that takes a Component and returns another Component, and the returning Components can be class component, a Functional Component with hooks or it can have no statefull logic.
In your example you're returning jsx from useSample.
const useSample = (WrappedComponent) => {
const sampleCtx = useContext(SampleCtx)
return ( // <-- here
<SampleProvider>
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} />
</SampleProvider>
)
}
if you want to make a HOC what you can do is something like this
const withSample = (WrappedComponent) => {
return props => {
const sampleCtx = useContext(SampleCtx)
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} {...props} />
}
}