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
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 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 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.
I've been getting started with react-redux and finding it a very interesting way to simplify the front end code for an application using many objects that it acquires from a back end service where the objects need to be updated on the front end in approximately real time.
Using a container class largely automates the watching (which updates the objects in the store when they change). Here's an example:
const MethodListContainer = React.createClass({
render(){
return <MethodList {...this.props} />},
componentDidMount(){
this.fetchAndWatch('/list/method')},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(oId){
this.props.fetchObject(oId).then((obj) => {
this._unwatch = this.props.watchObject(oId);
return obj})}});
In trying to supply the rest of the application with as simple and clear separation as possible, I tried to supply an alternative 'connect' which would automatically supply an appropriate container thus:
const connect = (mapStateToProps, watchObjectId) => (component) => {
const ContainerComponent = React.createClass({
render(){
return <component {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
this.props.fetchObject(watchObjectId).then((obj) => {
this._unwatch = this.props.watchObject(watchObjectId);
return obj})}
});
return reduxConnect(mapStateToProps, actions)(ContainerComponent)
};
This is then used thus:
module.exports = connect(mapStateToProps, '/list/method')(MethodList)
However, component does not get rendered. The container is rendered except that the component does not get instantiated or rendered. The component renders (and updates) as expected if I don't pass it as a parameter and reference it directly instead.
No errors or warnings are generated.
What am I doing wrong?
This is my workaround rather than an explanation for the error:
In connect_obj.js:
"use strict";
import React from 'react';
import {connect} from 'react-redux';
import {actions} from 'redux/main';
import {gets} from 'redux/main';
import {isFunction, omit} from 'lodash';
/*
A connected wrapper that expects an oId property for an object it can get in the store.
It fetches the object and places it on the 'obj' property for its children (this prop will start as null
because the fetch is async). It also ensures that the object is watched while the children are mounted.
*/
const mapStateToProps = (state, ownProps) => ({obj: gets.getObject(state, ownProps.oId)});
function connectObj(Wrapped){
const HOC = React.createClass({
render(){
return <Wrapped {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
const {fetchObject, watchObject, oId} = this.props;
fetchObject(oId).then((obj) => {
this._unwatch = watchObject(oId);
return obj})}});
return connect(mapStateToProps, actions)(HOC)}
export default connectObj;
Then I can use it anywhere thus:
"use strict";
import React from 'react';
import connectObj from 'redux/connect_obj';
const Method = connectObj(React.createClass({
render(){
const {obj, oId} = this.props;
return (obj) ? <p>{obj.id}: {obj.name}/{obj.function}</p> : <p>Fetching {oId}</p>}}));
So connectObj achieves my goal of creating a project wide replacement for setting up the connect explicitly along with a container component to watch/unwatch the objects. This saves quite a lot of boiler plate and gives us a single place to maintain the setup and connection of the store to the components whose job is just to present the objects that may change over time (through updates from the service).
I still don't understand why my first attempt does not work and this workaround does not support injecting other state props (as all the actions are available there is no need to worry about the dispatches).
Try using a different variable name for the component parameter.
const connect = (mapStateToProps, watchObjectId) => (MyComponent) => {
const ContainerComponent = React.createClass({
render() {
return <MyComponent {...this.props} obj={this.state.obj} />
}
...
fetchAndWatch() {
fetchObject(watchObjectId).then(obj => {
this._unwatch = watchObject(watchObjectId);
this.setState({obj});
})
}
});
...
}
I think the problem might be because the component is in lower case (<component {...this.props} />). JSX treats lowercase elements as DOM element and capitalized as React element.
Edit:
If you need to access the obj data, you'll have to pass it as props to the component. Updated the code snippet