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.
Related
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;
How to make and get props in all components with the funcional component and hooks?
at example iam making a js file with class with functions inside like getBooks(), getNotes(), then i making a context file and importing this in index.js and use a class for provider value, like below.
import {BookStoreContext} from "./components/bookstore-service-context";
import {BookStoreService} from "./services";
const bookStoreService = new BookStoreService();
ReactDOM.render(
<Provider store={store}>
<ErrorBoundry>
<BookStoreContext.Provider value={BookStoreService}>
<BrowserRouter>
<App/>
</BrowserRouter>
</BookStoreContext.Provider>
</ErrorBoundry>
</Provider>,
document.getElementById('root')
);
Once our Context is ready ( A global data to be used in the project).We can use useContext() Hook to consume our context in React-Hooks. This way we avoid class Based ContextName.Consumer Component in our Application.
The will provide step by step guide about how to work with Context API in Hooks with the example below.
1. First we create our Context File (Which will have our Global Data)
import React, { useState, createContext } from "react";
const books = [
{ id: 1, name: "The way of the kings" },
{ id: 2, name: "The people of Paradise" },
{ id: 3, name: "Protest of the Farmers" }
];
// First we need to create our context
const BookContext = createContext();
// createContext returns 2 things. Provider and Consumer. We will only need Provider
const BookContextProvider = ({ children }) => {
const [books, setBooks] = useState(books);
const removeBook = (id) => {
setBooks(books.filter((book) => book.id !== id));
};
return (
<BookContext.Provider value={{ books, removeBook }}>
{children}
</BookContext.Provider>
);
};
export default BookContextProvider;
We used createContext() method to create our Context. This returns (as before Hooks also) 2 things. Consumer and Provider. Since we will work with Hooks we only need Provider and in place of Consumer, we'll use useContext() in our components to consume this context.
2. Our Context is ready now. (Note:BookContext is our Context and BookContextProvider is simply a component in which we have our Context data). We will need to wrap our entire App aroundBookContextProvder Component so that all the Child Components used in the Application will have access to the Global Context.
import React, { createContext, useState, useEffect } from "react";
import "./styles.css";
import BookContextProvider from "./BookContext";
export default function App() {
return (
<div className="App">
<BookContextProvider>
<BookList/>
</BookContextProvider>
</div>
);
}
If you notice, I have Used BookList Component within BookContextProvider, that is to do with the setup we did in our Context file, where we used {children}. So, BookList Component is passed as children prop to the BookContextProvider Component in our BookContext.js file. (This may take some time for newbies to grasp the concept).
3. Once All the setup is ready we can consume context in our Child Components:
So in my BookList Component, I want to access books and also have the access to the removeBook handler. *We make use of useContext() Hook to do that.*
import React, { useContext } from "react";
import { BookContext } from "./BookContext";
const BookList = () => {
const { books, removeBook } = useContext(BookContext);
console.log(books); // We have our Books available now
console.log(removeBook); // We have our removeBook Handler as well
return (
<div>
<h1>BookList Component</h1>
{books.length > 0 &&
books.map((book) => {
return <div key={book.id}>{book.name}</div>;
})}
</div>
);
};
export default BookList;
In the above BookList Component we are now consuming our Context
using useContext() Hook.
COMPLETE CODESANDBOX DEMO: https://codesandbox.io/s/react-context-and-hooks-vcsfn?file=/src/App.js
See useContext.
Just do
// functional component
const someComponent = () => {
const BookStoreService = useContext(BookStoreContext);
// calling a method in the BookStoreService class
BookStoreService.getBooks();
return <></>
}
Note that if your methods like getBooks() are asynchronous (i.e. fetch data from a server), it's probably best to call them within a side effect hook like useEffect.
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'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'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