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.
Related
So im trying to export the two constants latitude and longitude to another component, its not a child or parent of the component with the constants in so I cannot use context or props. I tried exporting as a named variable but because the constants are defined within the Header component they are out of scope for the export statements. Someone please fix this for me, Im going to cry if i spend anymore time trying to fix it.
import React, { useState } from 'react';
import axios from 'axios';
function Header() {
const [text, setText] = useState("");
const [latitude, setLatitude] = useState(0);
const [longitude, setLongitude] = useState(0);
function handleChange(event) {
setText(event.target.value);
}
function handleClick() {
const geoCoderURL = "http://api.openweathermap.org/geo/1.0/direct?q=" + text + "&limit=5&appid={apikey}"
function getCoordinates(url) {
axios.get(url).then((response) => {
setLatitude(response.data[0].lat);
setLongitude(response.data[0].lon);
});
}
getCoordinates(geoCoderURL);
}
return (
<div>
<h1>Five Day Forecast</h1>
<input onChange={handleChange} type="text" name="name" autoFocus placeholder="Enter location here."/>
<button type="submit" onClick={handleClick}>Forecast</button>
</div>
)
}
export const locationLat = latitude;
export const locationLon = longitude;
export default Header;
This is a basic setup of a Context. This particular example shows the creation of a context, a wrapper that holds the state inside the context, and a custom hook to access the context values from any component.
The provider component looks just like a regular component except for the Context.Provider part. This is the part of the context that exposes the values of the context to its descendants.
// coordinates-context.js
import { createContext, useContext, useState } from 'react';
/**
* Create a new context.
*/
const CoordinatesContext = createContext();
/**
* Create a provider wrapper which is responsible for the state
* and children of the context. In a lot of ways it works just like
* a normal component, except for the Provider part, which is special
* for the Context API.
*/
export const CoordinatesProvider = ({ children }) => {
const [coordinates, setCoordinates] = useState({ lat: null, lng: null });
return (
<CoordinatesContext.Provider value={[coordinates, setCoordinates]}>
{children}
</CoordinatesContext.Provider>
);
};
/**
* This custom hook will allow us you use the context in any component.
*/
export const useCoordinatesContext = useContext(CoordinatesContext);
The provider component should have the components that need the data as descendants, like the example below.
<App>
<CoordinatesProvider>
<Header/>
<OtherComponent/>
</CoordinatesProvider>
</App>
Now that all the descendants have access, we can use the custom hook to use and manipulate the exposed values.
Our context simply exposes a state, so the implementation works just like how you would use a useState hook.
The only difference now is that all components that use the context, will be updated whenever any component updates the state inside of the context.
import React, { useState } from 'react';
import { useCoordinatesContext } from '../your-path-to/coordinates-context.js';
import axios from 'axios';
function Header() {
const [text, setText] = useState("");
/**
* The hook exposes the useState values from the context provider.
*/
const [coordinates, setCoordinates] = useCoordinatesContext();
function handleChange(event) {
setText(event.target.value);
}
function handleClick() {
const geoCoderURL = "http://api.openweathermap.org/geo/1.0/direct?q=" + text + "&limit=5&appid={apikey}";
function getCoordinates(url) {
axios.get(url).then((response) => {
/**
* Update context state.
*/
setCoordinates({
lat: response.data[0].lat,
lng: response.data[0].lon
});
});
}
getCoordinates(geoCoderURL);
}
return (
<div>
<h1>Five Day Forecast</h1>
<input onChange={handleChange} type="text" name="name" autoFocus placeholder="Enter location here."/>
<button type="submit" onClick={handleClick}>Forecast</button>
</div>
);
}
You can't export the state outside the functional component as a constant, but there are lots of solutions you can adopt to solve your problem.
Using React.createContext and React.useContext hook. If you define the hook in the parent of all components that needs it, you won't have problems with the access (most of the time this parent is the App component).
Using a state manager, like Redux. This lets you access getters and setters anywhere in the application.
Note: Redux adds a bit of boilerplate in the application, so if you don't already use it, prefer the first solution.
Saving the coordinates in the localStorage.
IMO this is almost never a good solution because doesn't allow you to be notified when the coordinates are updated, but fits well in some scenarios.
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;
I'm trying to figure out how to set the initial state in my React app inside an arrow function. I've found the example here: https://reactjs.org/docs/hooks-state.html but it's not helping me a lot. I want to put tempOrders and cols into the state so my other components have access to them and can change them.
Here is my code:
// creating tempOrders array and cols array above this
const App = () => {
const [orders, setOrders] = useState(tempOrders);
const [columns, setColumns] = useState(cols);
return (
<div className={'App'}>
<Schedule
orders={orders}
setOrders={setOrders}
columns={columns}
setColumns={setColumns}
/>
</div>
);
};
export default App;
Now my other related question is if I don't pass in those 4 variables/functions into Schedule, ESLint complains to me about them being unused variables in the 2 const lines above. I wouldn't think I would need to pass them in because that is the whole point of state, you just have access to them without needing to pass them around.
You should always keep the state at the top-level component where it needs to be accessed. In this case you should define the state in the Schedule-Component since it's not used anywhere else.
If you have a more complex hierachy of components and want to create a shared state (or make a state globally accessible) I would suggest following thump rule:
For small to medium sized apps use the context-API with the useContext-hook (https://reactjs.org/docs/hooks-reference.html#usecontext). It's fairly enough for most cases.
For large apps use redux. Redux needs a lot of boilerplate and adds complexity to your app (especially with typescript), which is often not required for smaller apps. Keep in mind that redux is not a replacement for thecontext-API. They work well in conjunction and can/should be used together.
EDIT
Simple example for useContext:
ScheduleContext.js
import React from "react";
export const ScheduleContext = React.createContext();
App.jsx
import {ScheduleContext} from "./ScheduleContext";
const App = () => {
const [orders, setOrders] = useState(tempOrders);
const [columns, setColumns] = useState(cols);
const contextValue = {orders, setOrders, columsn, setColumns};
return (
<div className={'App'}>
<ScheduleContext.Provider value={contextValue}>
<Schedule/>
</ScheduleContext.Provider>
</div>
);
};
export default App;
You can now use the context in any component which is a child of the <ScheduleContext.Provider>.
Schedule.jsx
import React, {useContext} from "react";
import {ScheduleContext} from "./ScheduleContext";
const Schedule = () => {
const {orders, setOrders, columsn, setColumns} = useContext(ScheduleContext);
// now you can use it like
console.log(orders)
return (...)
}
Note that you could als provide the context inside the <Schedule>-component instead of <App>.
I wrote this from my head, but it should work. At least you should get the idea.
it seems you want the child component "Schedule" have to change the father's state...... is correct?
so you can try to write like this example:
import React, {useState} from 'react';
import './App.css';
function Test(props){
const{setCount,count}=props
return(
<div>
<h1>hello</h1>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
function App() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<Test
setCount={setCount}
count={count}
/>
{count}
</div>
);
}
export default App;
https://repl.it/#matteo1976/ImperfectYawningQuotes
Where my Test would work as your Schedule
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} />
}
}
I was experimenting with the new Hook feature in React. Considering I have the following two components (using React Hooks) -
const HookComponent = () => {
const [username, setUsername] = useState('Abrar');
const [count, setState] = useState();
const handleChange = (e) => {
setUsername(e.target.value);
}
return (
<div>
<input name="userName" value={username} onChange={handleChange}/>
<p>{username}</p>
<p>From HookComponent: {count}</p>
</div>
)
}
const HookComponent2 = () => {
const [count, setCount] = useState(999);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Hooks claim to solve the problem of sharing stateful logic between components but I found that the states between HookComponent and HookComponent2 are not sharable. For example the change of count in HookComponent2 does not render a change in the HookComponent.
Is it possible to share states between components using the useState() hook?
If you are referring to component state, then hooks will not help you share it between components. Component state is local to the component. If your state lives in context, then useContext hook would be helpful.
Fundamentally, I think you misunderstood the line "sharing stateful logic between components". Stateful logic is different from state. Stateful logic is stuff that you do that modifies state. For e.g., a component subscribing to a store in componentDidMount() and unsubscribing in componentWillUnmount(). This subscribing/unsubscribing behavior can be implemented in a hook and components which need this behavior can just use the hook.
If you want to share state between components, there are various ways to do so, each with its own merits:
1. Lift State Up
Lift state up to a common ancestor component of the two components.
function Ancestor() {
const [count, setCount] = useState(999);
return <>
<DescendantA count={count} onCountChange={setCount} />
<DescendantB count={count} onCountChange={setCount} />
</>;
}
This state sharing approach is not fundamentally different from the traditional way of using state, hooks just give us a different way to declare component state.
2. Context
If the descendants are too deep down in the component hierarchy and you don't want to pass the state down too many layers, you could use the Context API.
There's a useContext hook which you can leverage on within the child components.
3. External State Management Solution
State management libraries like Redux or Mobx. Your state will then live in a store outside of React and components can connect/subscribe to the store to receive updates.
It is possible without any external state management library. Just use a simple observable implementation:
function makeObservable(target) {
let listeners = []; // initial listeners can be passed an an argument aswell
let value = target;
function get() {
return value;
}
function set(newValue) {
if (value === newValue) return;
value = newValue;
listeners.forEach((l) => l(value));
}
function subscribe(listenerFunc) {
listeners.push(listenerFunc);
return () => unsubscribe(listenerFunc); // will be used inside React.useEffect
}
function unsubscribe(listenerFunc) {
listeners = listeners.filter((l) => l !== listenerFunc);
}
return {
get,
set,
subscribe,
};
}
And then create a store and hook it to react by using subscribe in useEffect:
const userStore = makeObservable({ name: "user", count: 0 });
const useUser = () => {
const [user, setUser] = React.useState(userStore.get());
React.useEffect(() => {
return userStore.subscribe(setUser);
}, []);
const actions = React.useMemo(() => {
return {
setName: (name) => userStore.set({ ...user, name }),
incrementCount: () => userStore.set({ ...user, count: user.count + 1 }),
decrementCount: () => userStore.set({ ...user, count: user.count - 1 }),
}
}, [user])
return {
state: user,
actions
}
}
And that should work. No need for React.Context or lifting state up
This is possible using the useBetween hook.
See in codesandbox
import React, { useState } from 'react';
import { useBetween } from 'use-between';
const useShareableState = () => {
const [username, setUsername] = useState('Abrar');
const [count, setCount] = useState(0);
return {
username,
setUsername,
count,
setCount
}
}
const HookComponent = () => {
const { username, setUsername, count } = useBetween(useShareableState);
const handleChange = (e) => {
setUsername(e.target.value);
}
return (
<div>
<input name="userName" value={username} onChange={handleChange}/>
<p>{username}</p>
<p>From HookComponent: {count}</p>
</div>
)
}
const HookComponent2 = () => {
const { count, setCount } = useBetween(useShareableState);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
We move React hooks stateful logic from HookComponent to useShareableState.
We call useShareableState using useBetween in each component.
useBetween is a way to call any hook. But so that the state will not be stored in the React component.
For the same hook, the result of the call will be the same. So we can call one hook in different components and work together on one state. When updating the shared state, each component using it will be updated too.
Disclaimer: I'm the author of the use-between package.
the doc states:
We import the useState Hook from React. It lets us keep local state in a function component.
it is not mentioned that the state could be shared across components, useState hook just give you a quicker way to declare a state field and its correspondent setter in one single instruction.
I've created hooksy that allows you to do exactly this - https://github.com/pie6k/hooksy
import { createStore } from 'hooksy';
interface UserData {
username: string;
}
const defaultUser: UserData = { username: 'Foo' };
export const [useUserStore] = createStore(defaultUser); // we've created store with initial value.
// useUserStore has the same signature like react useState hook, but the state will be shared across all components using it
And later in any component
import React from 'react';
import { useUserStore } from './userStore';
export function UserInfo() {
const [user, setUser] = useUserStore(); // use it the same way like useState, but have state shared across any component using it (eg. if any of them will call setUser - all other components using it will get re-rendered with new state)
function login() {
setUser({ username: 'Foo' })
}
return (
<div>
{!user && <strong>You're logged out<button onPress={login}>Login</button></strong>}
{user && <strong>Logged as <strong>{user.username}</strong></strong>}
</div>
);
}
With hooks its not directly possible.
I recommend you to take a look at react-easy-state.
https://github.com/solkimicreb/react-easy-state
I use it in big Apps and it works like a charm.
I'm going to hell for this:
// src/hooks/useMessagePipe.ts
import { useReducer } from 'react'
let message = undefined
export default function useMessagePipe() {
const triggerRender = useReducer((bool) => !bool, true)[1]
function update(term: string) {
message = term.length > 0 ? term : undefined
triggerRender()
}
return {message: message, sendMessage: update}
}
Full explanation over at: https://stackoverflow.com/a/72917627/1246547
Yes, this is the dirtiest and most concise way i could come up with for solving that specific use case. And yes, for a clean way, you probably want to learn how to useContext, or alternatively take a look at react-easy-state or useBetween for low-footprint solutions, and flux or redux for the real thing.
You will still need to lift your state up to an ancestor component of HookComponent1 and HookComponent2. That's how you share state before and the latest hook api doesnt change anything about it.