I am going to use reacts context api plus reducers. Because I found many ways to implement this and a lack of documentation I don't know if I am doing right.
I order to have a global state I have done this:
import {createContext, useReducer} from "react";
import {appReducer} from "../reducers/appReducer";
function lazyInitializer() {
return {db: 1}
}
export const AppContext = createContext(); //Invalid number of arguments, expected 1 (defaultValue)
const AppContextProvider = (props) => {
const [globalState, appDispatch] = useReducer(appReducer, null, lazyInitializer);
return (
<AppContext.Provider value={{globalState, appDispatch}}>
{props.children}
</AppContext.Provider>
)
}
export default AppContextProvider;
Do I have to supply createContext a default value? Is this the right way to use lazy initializes? It does work how it is above. If there is a better solution realizing a global state please tell me.
Btw I dont wanna use Redux in order to keep the project simple.
Related
I am trying to make a dark/light theme system in my project, but I am having some problems with the code.
This line of code works fine in javascript:
const [darktheme, setDarkTheme] = useContext(ThemeContext);
But when I write it into typescript, I get 6 errors.
I know that some of these variables need to have their type declared, but I only know the type of the darkTheme variable, which is a boolean.
After I declare the types, 2 errors go away, but there is still 4 errors!
const [darktheme: boolean, setDarkTheme: any] = useContext(ThemeContext);
I used any after dark theme, which is not good practice but I didn't know the type
Now I just get these errors:
I think that the main problem with my project is that I am trying to integrate javascript with typescript. I don't know if that is normal or not, but I am doing it because some components are much easier to write with typescript, and some more basic components are better written in javascript.
Here is part of my app.js:
// Context
export const ThemeContext = React.createContext();
function App() {
const [darkTheme, setDarkTheme] = useState(false);
return (
<ThemeContext.Provider value={[darkTheme, setDarkTheme]}>
,and when I use the function in this component, it works just fine:
import React, { useContext } from 'react';
import { ThemeContext } from '../App';
import Button from 'react-bootstrap/Button';
export default function DarkThemeTest() {
const [darktheme, setDarkTheme] = useContext(ThemeContext);
return (
<Button onClick={() => {
setDarkTheme(!darktheme);
}}>
Theme: {darktheme && "Dark" || "Light"}
</Button>
)
}
First, define a type for your context value
import { createContext, Dispatch, SetStateAction } from "react";
interface ThemeContextType {
darkTheme: boolean;
// this is the type for state setters
setDarkTheme: Dispatch<SetStateAction<boolean>>;
}
Then, create your context with this type and initialise it with a default value. This might seem unnecessary but it will avoid checking for null or undefined context later on
export const ThemeContext = createContext<ThemeContextType>({
darkTheme: false,
setDarkTheme: () => {}, // no-op default setter
});
Once you have created your state value and setter, set them into the context provider value
<ThemeContext.Provider value={{ darkTheme, setDarkTheme }}>
Now you can destructure the context value easily via useContext with full type support
const { darkTheme, setDarkTheme } = useContext(ThemeContext);
You could continue to use your array format though I wouldn't recommend it.
type ThemeContextType = [boolean, Dispatch<SetStateAction<boolean>>];
export const ThemeContext = createContext<ThemeContextType>([false, () => {}]);
and
<ThemeContext.Provider value={[darkTheme, setDarkTheme]}>
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 having a bit of difficulty implementing redux in a simple react project that I'm creating. For clarification, it's a react 360 webvr project but I've seen many similarities with react native that I'm sure this can work.
The project that I'm trying to do is simply changing the background color of a component on the click of a button. Below is my code:
constants.js
export const PICK_COLOR = 'PICK_COLOR';
actions.js
import { PICK_COLOR } from './constants'
export const pickColor = (color) => ({
type: PICK_COLOR,
payload: color
})
reducers.js
import { PICK_COLOR } from './constants';
const initialColor = {
backgroundColor: 'white'
}
export const chooseColor = (state = initialColor, action={}) => {
switch (action.type) {
case PICK_COLOR:
return Object.assign({}, state, {backgroundColor: action.payload})
default:
return state
}
}
index.js
import React from 'react';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
import { chooseColor } from './reducers';
import { pickColor } from './actions';
import {
AppRegistry,
StyleSheet,
Text,
View,
VrButton
} from 'react-360';
const store = createStore(chooseColor);
const mapStateToProps = state => {
return {
backgroundColor: state.chooseColor.backgroundColor
}
}
const mapDisptachToProps = (dispatch) => {
return {
onChooseColor: (event) => dispatch(pickColor(event.target.value))
}
}
class App extends React.Component {
render() {
const { backgroundColor, onChooseColor } = this.props;
return (
<Provider store={store}>
###########################################
I want this to change background color with
the click of a button.
<View style={[styles.panel, backgroundColor: this.props.backgroundColor]}>
###########################################
<VrButton style={styles.greetingBox} onClick={onChooseColor('blue')}>
<Text style={[styles.greeting, {color: 'blue'}]}>
Blue
</Text>
</VrButton>
</View>
</Provider>
);
}
};
const connectedApp = connect(mapStateToProps, mapDisptachToProps)(App);
AppRegistry.registerComponent('App', () => App);
The problem I'm having is getting over the finish line. I think I have everything set up almost correctly, but I'm unable to trigger any state change. The part where I'm getting confused is how do I connect my onClick prop handler to a state change and pass an argument? I've mixed and matched so many tutorials and videos that my head is spinning at the moment and I'm not entirely wrapping my head about setting up redux yet to troubleshoot effectively.
From what I've gathered, I don't think I have my mapDispatchToProps correctly because in the console I get the error that OnChooseColor is not a function. But how am I supposed to trigger the change? Can someone help pinpoint where I am going wrong? The help would be appreciated.
Could it be that in your mapStateToProps you are reading from state.chooseColor.backgroundColor, but it looks like your store has the shape state.backgroundColor (from what I can tell by the reducers.js)?
It's a bit late for me, so I'll probably have a look at this again tomorrow! (I'll try being more hands-on than just staring at the code!). But I'd definitively try to debug your store in your browser, by setting some breakpoints and having a look at what the store contains. There's also some handy browser extensions for react and redux that I would try out as well! (they should in theory make it easier to see what's going on with redux & react).
I can at least vouch for the react extension myself, I use it heavily just for the feature of being able to tell me which React component I'm looking at (as the DOM renders into <div> and not <MyComponent>!)
Edit: I made a small example that's very similar to this one here!
Two things I can spot by scanning your code.
1. backgroundColor is on the state in reducer.
const mapStateToProps = state => {
return {
backgroundColor: state.backgroundColor
}
}
The function for onClick should be passed instead of calling it.
onClick={() => onChooseColor('blue')}
I am developing a React Native application.
I want to save the user id of the person who is logged in and then check if the user is logged in in every single component.
So what I am looking for is something like cookies, sessions or global states.
I have read that I should use Redux, but this seems to be overly complicated and it is very difficult to make it work with react-navigation. It forces me to define actions and reducers for almost everything although the only thing I want is to be able to access a single global state/variable in all components.
Are there any alternatives or should I really re-structure my entire app to use Redux?
I usually create a global.js containing:
module.exports = {
screen1: null,
};
And get the value of the state on the screen
import GLOBAL from './global.js'
constructor() {
GLOBAL.screen1 = this;
}
Now you can use it anywhere like so:
GLOBAL.screen1.setState({
var: value
});
Update since React 16.8.0 (February 6, 2019) introduce Hooks.
it is not mandatory to use external library like Mobx or Redux. (Before Hook was introduce I used both of this state management solutions)
you can create global state just with 10 line Source
import React, {createContext, useContext, useReducer} from 'react';
export const StateContext = createContext();
export const StateProvider = ({reducer, initialState, children}) =>(
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
extend your app with global state:
import { StateProvider } from '../state';
const App = () => {
const initialState = {
theme: { primary: 'green' }
};
const reducer = (state, action) => {
switch (action.type) {
case 'changeTheme':
return {
...state,
theme: action.newTheme
};
default:
return state;
}
};
return (
<StateProvider initialState={initialState} reducer={reducer}>
// App content ...
</StateProvider>
);
}
For details explanation I recommend to read this wonderful medium
There are some alternatives to Redux in terms of state management. I would recommend you to look at Jumpsuit and Mobx. However do not expect them to be easier than Redux. State management is mostly a magical thing and most of the gizmo happens behind the scenes.
But anyways if you feel that you need some global state management, it worths your time to master one of the solutions no matter Redux or Mobx or etc. I would not recommend using AsyncStorage or anything hacky for this purpose.
I usually do globals like this:
I creat an globals.js
module.exports = {
USERNAME: '',
};
Something like that to store the username then you just need to import :
GLOBAL = require('./globals');
And if you wanna store the Data, lets say you want to save the username just do :
var username = 'test';
GLOBAL.USERNAME = username;
And there you go , you just need to import GLOBAL on the pages you want and use it, just use if (GLOBAL.username == 'teste').
If you are new to react (as me) and got confused by the first answer.
First, use a component Class
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
walk: true
};
GLOBAL.screen1 = this;
}
render() {
return (
<NavigationContainer>
<Stack.Navigator>
{this.state.walk ? (
<>
<Stack.Screen name="WalkThrough" component={WalkThroughScreen} />
</>
) : (
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
<StatusBar style="auto" />
</NavigationContainer>
)
}
Then you can do in any other component (My components are on /components, global is on root):
import GLOBAL from '../global.js'
GLOBAL.screen1.setState({walk:false})
There appears to be a GLOBAL object. If set in app.js as GLOBAL.user = user, it appears to be available in other components, such as the drawer navigation.
this is an old question but I have a solution that helps me.
To accomplish this, I use what is called a GlobalProvider, essentially provides global data to all components. A lot of this code was learned through YouTube Tutorials so I can not take credit for the ideas. Here is the code,
export const GlobalContext = createContext({});
const GlobalProvider = ({children}) => {
//authInitialState can be whatever you want, ex: {rand: {}, rand2: null}
const [authState, authDispatch] = useReducer(auth, authInitialState);
return (
<GlobalContext.Provider
value={{authState, authDispatch}}>
{children}
</GlobalContext.Provider>
);
};
export default GlobalProvider;
Then you would simply wrap your entire application (usually app.js) with GlobalProvider as so. Ignore my AppNavContainer, that just contains code that routes my pages.
import GlobalProvider from "./src/Context/Provider";
const App: () => Node = () => {
return (
<GlobalProvider>
<AppNavContainer/>
</GlobalProvider>
);
};
From here on you are able to change the authState with a reducer of some sort, I will not provide that code since it is huge, but look at Soullivaneuh's example on the reducer above.
NOW to the good part, of how to access your state. It is simple, in any component you wish, simply follow a similar structure like this. Notice that I have {data} as it will allow you to see the state.
const {
authState: {data},
} = useContext(GlobalContext);
console.log("Data:", data)
If anyone can correct me where I went wrong, I'd appreciate it as well.
Same as #Brunaine suggested, but I import it only in the App.js and can use it in all the screens.