dispatch is not accessible from useContext - reactjs

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.

Related

React Context value never updates

I have the following code as a component that returns a context. For some reason when I call the updateUser function it is not setting the state, I keep getting a blank object. Is there something different I have to do to a function like that when it has parameters?
import React, { useState } from "react";
const UserContext = React.createContext({
user: {},
updateUser: (incomingUser) => {},
});
const UserData = (props) => {
const [user, setUser] = useState({});
const updateUser = (incomingUser) => {
setUser(incomingUser);
console.log(`user=${JSON.stringify(incomingUser)}`);
};
return (
<UserContext.Provider value={{ user, updateUser }}>
{props.children}
</UserContext.Provider>
);
};
export { UserData, UserContext };
Get rid of the default value for UserContext, or initialise it as null:
const UserContext = React.createContext(null);
Per the React Docs:
When React renders a component that subscribes to this Context object it will read the current context value from the closest matching Provider above it in the tree.
The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. This default value can be helpful for testing components in isolation without wrapping them.

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.

How to access state from another function

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;

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>
);
}

React.js / Redux: Functional Components, legacy state/props and do I have to connect every component that uses the store?

some questions about React.js and Redux:
Can functional components also take advantage of the store and the states saved therein? e.g maybe in combination with React hooks like useEffect()?
In general, I can combine multiple reducers to one rootReducer and createStore(rootReducer) with it, and then pass it to a Provider Component that wraps my Component with it, this way, the store should be globally available in my whole app, correct?
For every component that want to use the store / states, do I always have to import the 2 methods mapStateToProps() and mapDispatchToProps() from react-redux for every Component and then connect them? Or can I also do this on some top-level component and make the usage of redux available in all my components globally, like in question 2) with the store provider?
last question: Can I still use the this.state property in my Components or use them in parallel as an addition (e.g for this Component isolated states) and then get the props from this state as usual with this.state.someState or is this not possible anymore when I already use Redux? And in the same way, can I still use / pass props to my components and read them from my Components as well, or is everything managed by state now only? (Or has the passing of props to my children nothing to do with Redux)?
1) Yes functional components can take advantage of the store. Its arguably much cleaner to read since props can be destructured right away.
const MyComponent = ({ auth }) => {
const [display, setDisplay] = useState(false)
useEffect(() => {
if(auth.user){
setDisplay(true)
}
}, [auth.user])
return(
<div>
{ display ? "Content": "Please sign in" }
</div>
)
}
const mapStateToProps = (state) => {
return{
auth: state.auth
}
}
export default connect(mapStateToProps)(MyComponent)
2) That is correct. You can also use combineReducers() which in some ways is cleaner to read.
import { createStore, combineReducers } from "redux"
import authReducer from "./reducers/authReducer"
import postReducer from "./reducers/postReducer"
const store = createStore(combineReducers({
auth: authReducer,
post: postReducer
}))
export default store
Then import store, wrap your App.js in a Provider and give it a prop of that store.
3) Generally, if you want your component to have direct access to the store it is a recognized pattern to use connect() in each one. Whether you decide to use mapStateToProps() or mapDispatchToProps() is entirely dependent on what that component needs to do. It does not required that you use both, you can just define one or the other in the connect().
import React, { useState } from "react"
import { addPost } from "/actions/postActions"
import { connect } from "react-redux"
const Form = ({ addPost }) => {
const [text, setText] = useState("")
const handleSubmit = (e) => {
e.preventDefault()
addPost(text)
}
return(
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)}/>
</form>
)
}
const mapDispatchToProps = (dispatch) => {
return {
addPost: (text) => dispatch(addPost(text))
}
}
export default connect(null, mapDispatchToProps)(Form)
4) You might have noticed by now that in the context of components, redux-state is stored as props. They are entirely different and isolated streams of data. So state remains untouched and controlled by the component itself. You can still freely use methods like this.state.dog even when your component is connected to the store. This is the isolation between component-state and redux-state.
import React, { useState } from "react"
import { connect } from "react-redux"
class MyDogs extends React.Component{
state = {
dog: "Tucker"
}
render(){
return(
<div>
Component State Value: {this.state.dog} //Tucker
Redux State Value: {this.props.dog} //Buddy
</div>
)
}
const mapStateToProps = (state) => {
return {
dog: state.dog
}
}
export default connect(mapStateToProps)(MyDogs)

Resources