How to access state from another function - reactjs

Basically I have a state variable in one component which is in a seperate folder
State.js
const [data, setData] = useState([])
folder structure
Components
State > State js
Manipulate > Manipulate js
Basically I want to be able to pass state from the state file in the state folder and be able to access state from the state js in the manipulate js folder.
Is this possible? What would be the best way to do so?
Thanks!

Generally, the useState hook is used to add state to a functional component. This hook does not create a global state that can be accessed by other components (unless you explicitly pass the state item as a property to child components).
If the Manipulate.js component has been imported in State.js, you could pass the state to Manipulate in the below way:
<Manipulate data={data} />
The above would give you access to data in the manipulate component. You would need to ensure that data has been added as a property in the Manipulate.js file like below:
const Manipulate = ({data}) => {
// Your code here
}
If you also needed the state setter in Manipulate.js, you would need to repeat the above steps and pass down the setData function as a property.
The above is fine in some cases; however, if you need global state (i.e state that's available in many different components) you may want to consider using the useContext hook (docs here) or Redux.

It sounds to me that you are trying to create State that is accessible throughout you application. You have a few options, namely a system like Redux, but React ships with a built-in Context system you can utilize to share state throughout your application.
State.js
import React, { useState, createContext } from "react";
export const StateContext = createContext();
export const StateProvider = props => {
const [data, setData] = useState();
return (
<StateContext.Provider value={[data, setData]}>
{props.children}
</StateContext.Provider>
);
};
Manipulate.js
import React, { useContext } from "react";
import { StateProvider, StateContext } from "../State";
const Manipulate = ()=> {
const [data, setData] = useContext(StateContext);
return (
<div>
<StateProvider>
<div>{props.data}</div>
</StateProvider>
</div>
);
}
export default App;

Related

dispatch is not accessible from useContext

i have simple store
import React, { createContext, useReducer } from "react";
import Reducer from "./UserReducer";
const initialState = {
user: {},
error: null
};
const Store = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Context.Provider value={[state, dispatch]}>
{children}
</Context.Provider>
);
};
export const Context = createContext(initialState);
export default Store;
i have wrapped my app with it like
<Store>
<ThemeProvider theme={Theme}>
<CssBaseline />
<Navbar />
<Wrapper>
<Profile />
</Wrapper>
</ThemeProvider>{" "}
</Store>
There is additional setup as well where my authentication pages are located in separate wrapper so i wrapped that with store as well.
here is code for that wrapper (extra removed).
import Store from "../../../context/Store";
export default function Wrapper({ children }) {
const classes = useStyles();
return (
<Store>
//different dumb containers opening
{children}
//different dumb containers closing
</Store>
);
}
Now when i try to access context within child component like
import React, { useContext } from "react";
import { Context } from "../../../context/Store";
import { SET_USER } from "../../../context/UserTypes";
function SignUp(props) {
const [state, setState] = React.useState({ ...initialState });
const [userData, dispatch] = useContext(Context);
console.log(userData, dispatch, "check");
// rest of component
i get following error
TypeError: undefined is not a function
i tried to log result of useContext without destructuring it but all it had was global state but no dispatch function with it.
Reactjs version = 17.0.1
Update: dispatch is accessible outside withAuthenticator HOC but not within that so it might be amplify issue.
i have opened issue on amplify repo.
Unable to access dispatch from useContext from components wrapped withAuthenticator
There a few things I see that could be potential issues.
The major problem is the value of your Context. When you create the context, its value is a state (createContext(initialState)). But when you pass a value to the Context.Provider you are giving it an array which contains both a state and a dispatch function (value={[state, dispatch]}). You need to be consistent about the value that is contained in the context. Is it a state object or a tuple?
The error that you are describing seems like what would happen if the Context is accessed outside of a Provider. It would fall back the initial context value which is just a state rather than a [state, dispatch] tuple.
You need to change the initial value to something that matches the value that you are expecting.
const Context = createContext([
initialState, // state
() => console.error("Cannot dispatch because Context was used outside of a provider") // dispatch
]);
But you also need to figure out why the useContext is getting the default value instead of one from a Provider. I'm not sure if these next two issues will fix that or not.
It looks like the Profile component is inside of two different Store providers. One which is outside of everything and one which is inside of the Wrapper component. These will have two separate instances of state. When there are two providers for the same context React uses the inner one. So any actions that you dispatch from Profile won't update the outer version of Store.
You create the Context variable after you use it in your Store component. You should switch the order.

React Native: where to place global state variables

I am developing my first React Native, and I need again some help.
My application has one state - sport, which will be important for components, screens and etc. Accordingly the chosen sport, I will load different styles, images, and api information too.
There will be one modal, from which the user can change the sport. The modal now is part of the Header component, which is part of the Screen component.
So my question is how or where to place this sport state variable, so I can access it everywhere and on a change to update the new styles and etc.
The overview of the application is like this:
App.js
import AppContext from './utility/context';
export default function App() {
const [sport, setSport] = React.useState('soccer');
const state = {sport, setSport};
return (
<AppContext.Provider sport={state}>
<OfflineNotice />
<Screen />
</AppContext.Provider>
);
}
context.js
import React from "react";
export const AppContext = React.createContext({
sport: 'soccer',
setSport: () =>{}
});
Screen.js
export default function Screen ({children}) {
return (
<>
<Header />
<SafeAreaView style={styles.screen}>
<View style={styles.container}>{ children }</View>
</SafeAreaView>
<TabNavigator i18n={i18n}/>
</>
);
}
In Header.js I will also use that future state, but at the moment there is nothing interesting.
But here will be the View, from which the user will change the sport state variable.
HomeScreen.js - it is the first screen of the TabNavigator
export default function HomeScreen({ navigation }) {
const today = Moment(new Date().getTime()).format('YYYY-MM-DD');
const [predictions, setPredictions] = useState([]);
const [loading, setLoading] = useState(true);
const params = {
lang: 'en',
date: today,
sport: 'soccer',
};
...
}
Here the sport state is hardcoded because I don't know yet how to proceed.
I've heard about Redux, but I haven't used it yet, so I will appreciate if there is any solution not using Redux.
Thank you in advance!
You can achieve this using React-Context
You can simply have a state in the app.js and use context to access it anywhere you need in the app.
First you need to create the context. Its better if its a separate file
const AppContext = React.createContext({sport:'value',setSport=()=>{}});
Here the default values are options but preferred specially when you use typescript to avoid warnings.
Now you have to use this in your app.js like below
export default function App() {
const [sport,setSport] = React.useState('value');
const state={sport,setSport};
...
return (
<AppContext.Provider value={state}>
<OfflineNotice />
<Screen />
</AppContext.Provider>
);
}
You will have to import the context and use the provider as the wrapper setting the value from the local state that you have. Now you can access this anywhere in the tree and modify it if required.
// Accessing the context using the useContext hook, this component should be in the tree and you should import AppContext
const {sport,setSport} = useContext(AppContext);
You can show it like below
<Text>{sport}</Text>
Or set it like below
<Button title="Set Value" onPress={()=>{setSport('value')}}>
This example is just on a string but you can even have an object.

Can I use the React Context API inside a Context API or do I have to merge them?

I am just curios about if it would be possible to use the Context API inside a Context API. Like for example I would have a Context API for an AppState and want to use that in another Context API which handles a WebSocket connection?
Inspired by Joseph's answer I am thinking about just using those both context api's in a custom hook together.
useMultipleContexts(){
const contextOne = useContext(ContextOne);
const contextTwo = useContext(ContextTwo);
/**
* Do something with both contexts
* in a custom hook that can be used
* multiple times with the same state
*/
}
This is a good scenario to use hooks instead of context.
// custom hook
function useAppState() {
//add handlers here
return appState;
}
function WebSocket() {
const appState = useAppState();
// do something (i.e reconnect) every time appState changes
useEffect(() => { /* do something */, [appState])
}
function App() {
return <WebSocket />
}
Let me explain how to use two different Contexts at the same time.
First step:
You need to create two different context
const AppContext = React.createContext(null);
const SocketContext = React.createContext(null);
Second step:
You need to implement your custom hook.
const UseSharedLogic = () => {
// your common logic
}
Then share it using the context API.
<AppContext.Provider value={state}>
<SocketContext.Provider value={UseSharedLogic}>
<App />
</DispatchContext.Provider>
</StateContext.Provider>
Third step:
You need to consume these contexts at the component that you need to use them inside it.
const state = React.useContext(AppContext);
const socket = React.useContext(SocketContext);
Here you can use both contexts together and you use one value from one context in another one.
Let's assume that socket context has a function called connect and it depends on value from the app context, you can do something like this.
socket.connect(state.anyValue);
I would create a new functional component that would wrap the components
Say you had two components written as follows.
import React from 'react';
const ContextA = React.createContext({});
export default ContextA;
import React from 'react';
const ContextB = React.createContext({});
export default ContextB;
I generally avoid the above pattern because people have to guess what you're trying to put in the context. Instead I write a functional component that provides the context as follows
import { createContext, useContext } from 'react'
import ContextA from './contexta'
import ContextB from './contextb'
// The ! is needed in order to allow undefined to be set as the initial value.
const MyContext = createContext<IMyContextInfo>(undefined!);
export default ({children}) => {
const { somethingFromA } = useContext(ContextA);
const { somethingFromB }= useContext(ContextB);
return (
<MyContext.Provider value={{ a: somethingFromA, b: somethingFromB }}>
{children}
</MyContext.Provider>
);
}

Can I replace context with hooks?

Is there a way with new react hooks API to replace a context data fetch?
If you need to load user profile and use it almost everywhere, first you create context and export it:
export const ProfileContext = React.createContext()
Then you import in top component, load data and use provider, like this:
import { ProfileContext } from 'src/shared/ProfileContext'
<ProfileContext.Provider
value={{ profile: profile, reloadProfile: reloadProfile }}
>
<Site />
</ProfileContext.Provider>
Then in some other components you import profile data like this:
import { ProfileContext } from 'src/shared/ProfileContext'
const context = useContext(profile);
But there is a way to export some function with hooks that will have state and share profile with any component that want to get data?
React provides a useContext hook to make use of Context, which has a signature like
const context = useContext(Context);
useContext accepts a context object (the value returned from
React.createContext) and returns the current context value, as given
by the nearest context provider for the given context.
When the provider updates, this Hook will trigger a rerender with the
latest context value.
You can make use of it in your component like
import { ProfileContext } from 'src/shared/ProfileContext'
const Site = () => {
const context = useContext(ProfileContext);
// make use of context values here
}
However if you want to make use of the same context in every component and don't want to import the ProfileContext everywhere you could simply write a custom hook like
import { ProfileContext } from 'src/shared/ProfileContext'
const useProfileContext = () => {
const context = useContext(ProfileContext);
return context;
}
and use it in the components like
const Site = () => {
const context = useProfileContext();
}
However as far a creating a hook which shares data among different component is concerned, Hooks have an instance of the data for them self and don'tshare it unless you make use of Context;
updated:
My previous answer was - You can use custom-hooks with useState for that purpose, but it was wrong because of this fact:
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
The right answer how to do it with useContext() provided #ShubhamKhatri
Now i use it like this.
Contexts.js - all context export from one place
export { ClickEventContextProvider,ClickEventContext} from '../contexts/ClickEventContext'
export { PopupContextProvider, PopupContext } from '../contexts/PopupContext'
export { ThemeContextProvider, ThemeContext } from '../contexts/ThemeContext'
export { ProfileContextProvider, ProfileContext } from '../contexts/ProfileContext'
export { WindowSizeContextProvider, WindowSizeContext } from '../contexts/WindowSizeContext'
ClickEventContext.js - one of context examples:
import React, { useState, useEffect } from 'react'
export const ClickEventContext = React.createContext(null)
export const ClickEventContextProvider = props => {
const [clickEvent, clickEventSet] = useState(false)
const handleClick = e => clickEventSet(e)
useEffect(() => {
window.addEventListener('click', handleClick)
return () => {
window.removeEventListener('click', handleClick)
}
}, [])
return (
<ClickEventContext.Provider value={{ clickEvent }}>
{props.children}
</ClickEventContext.Provider>
)
}
import and use:
import React, { useContext, useEffect } from 'react'
import { ClickEventContext } from 'shared/Contexts'
export function Modal({ show, children }) {
const { clickEvent } = useContext(ClickEventContext)
useEffect(() => {
console.log(clickEvent.target)
}, [clickEvent])
return <DivModal show={show}>{children}</DivModal>
}

Grab redux snapshot of redux state without connecting

I have a background saga that is watching the location and submits an action with the new location every time it changes and updates the location state. However, I have a component that I just want to grab the current state.user.location on a user interaction, but I don't want to use mapStateToProps because the component keeps re-rendering and I only want to grab the state when the user requests it and avoid constantly re-rendering.
I need the state in the background for other parts of the app.
reducer:
export const updateLocation = (state, { location }) => state.merge({ location })
selector:
getLocation : state => state.user.location && state.user.location.coords || null
Component:
class SearchScreen extends PureComponent {
render(){
const {location} = this.props
return(
<Button onPress={()=>searchWithCurrentLocation(location)} />
)}
}
const mapStateToProps = (state) => {
return {
location: UserSelectors.getLocation(state),
}
}
this is my current setup, but I'd like to not pass in the location and keep re-rendering as it's not used to display the component.
You can make the store accessible from anywhere like this:
file: storeProvider.js
var store = undefined;
export default {
init(configureStore){
store = configureStore();
},
getStore(){
return store;
}
};
file: App.js
import { createStore } from 'redux';
import rootReducer from './rootReducer';
import storeProvider from './storeProvider';
const configureStore = () => createStore(rootReducer);
storeProvider.init(configureStore);
const store = storeProvider.getStore();
const App = () =>
<Provider store={store} >
<Stuff/>
</Provider>
file: Component.js
import storeProvider from './storeProvider';
class SearchScreen extends PureComponent {
render(){
return(
<Button onPress={()=> searchWithCurrentLocation(UserSelectors.getLocation(storeProvider.getStore().getState())} />
)}
}
I don't think you need to be troubled about re-rendering if the data that has changed isn't directly affecting the components inside your render method. Let us remember that ReactDOM watches only those changed state and only update the DOM based on what's different. The render method maybe called but if nothing has really changed, it won't affect render performance at all.
After all that is what react is selling: reactive elements that updates changes if data changes in a smart way that it is optimized by updating only updated elements by using a virtual DOM which is ReactDOM.
Changes are first compared between the virtual DOM and the real DOM before updates are committed to the real DOM. You can read how ReactDOM does this here.

Resources