Why Is React Context.Provider Necessary (Or useful)? - javascript

The reason React has contexts is to allow for multiple sibling components to share a piece of state-data. It is the go-to method for allowing two unrelated components to read/write in shared variables. The reason it is necessary is that React has no way to easily source a data value to multiple screens without actually passing that data between screens. Instead, it allows each screen access to the data when it needs it.
So... The implementation requires that a component be created, called a Context.Provider component, and then you have to wrap the components who need access to the shared data inside the Context.Provider. But why? Why on earth is that a requirement? Contexts are designed sharing data between components who aren't hierarchally related, and were required to put the components within a heirarchy to do so?
It would be 100 times more straight forward and just as effective to simply drop the requirement of using a Context.Provider, simple have the useContext function give access to a set variable by default:
// In ctx.js
import React from 'react';
export default CTX = React.createContext({val: "value"});
// In compA.js
import CTX from './ctx.js';
import {useContext} from 'react';
function A(props) {
var [context, setContext] = useContext(CTX);
console.log(context); //Logs {val: 'value'};
setContext({val: "newValue"});
}
Then later on, assuming component B renders after A:
import CTX from './ctx.js';
import {useContext} from 'react';
function B(props) {
var [context, setContext] = useContext(CTX);
console.log(context); //Logs {val: 'newValue'};
}
The above usage, if it actually worked, solves the task of "sharing data between unrelated components", and is much much simpler than requiring an entire new component be defined in the context file. This solution is better because:
1. No required restructuring of the application. You don't need to wrap components in a provider.
2. Any Components can just ask for any shared state easily, and they can set the shared state easily.
3. Easier to understand with much less code involved (One line of code for import and one line to initiate the context).
4. Doesn't sacrifice anything. This method allows for easy sharing of state between components, which is the entire reason of contexts in the first place.
Am I crazy? Is there a legitamate reason that we'd absolutely need to wrap our components up in a special component to share data?.. Why can't the shared state just exist independently? Its like they chose a bad solution... Why make every developer wrap there components in another component before using shared state, why not just let the developer use the damned shared state when they need to use it instead of jumping through a hoop? Someone please educate me.
Edit: One answer said that with my described method we wouldn't be able to access multiple contexts with a single component. That is false. It is actually easier with my described method:
// In context.js
export const CTX = React.createContext({val: "val"});
export const CTX2 = React.createContext({val2: "val2"});
// In app.js
function App(props) {
const [state, setState] = useContext(CTX);
const [state2, setState2] = userContext(CTX2);
return (<></>);
}
Easy. No need for Context.Provider. This is multiple contexts being used in one component, requiring just two calls to useContext versus wrapping your entire application in two nested contexts, which is what is what you have to do with current Context.Provider method...

Mate, answer is simple. React component only re-renders when it's props or state changes. Without Context.Provider component react will never understand when to re-render child components, thus you will have stale, render-blocked components.

The purpose for having a Context Provider wrap around children is to keep track of state and props, read on how state and props between parents and children affect each other. If there was no way for the Context Provider to keep track of its children, how would the components that use the Context be able to update(Changing parent state affects children, so there may be rerendering).
It's also important to understand React's philosophy and it's focus on components, it is a component-based library after all.
Important thing to remember:
Parent state change will affect children, so if state changes in parent, children components will be reevaluated and depending on how your components, state, and data are optimized (memo, callback, etc.) a rerender may occur, thus updating those children components as well.

Contexts Are Made To Handle All Use Cases
I've since spent more time using Contexts in my applications and have come to realize that Context.Provider is quite useful in a variety of situations. My initial complaint has merit in that often times when using Context we are simply wanting a variant of state that can be shared between components. In this common use case, Context.Provider does indeed requires us to write a bit of unnecessary boilerplate code and requires us to wrap elements in the provider so that they have access to the context.
However any time our shared state becomes a little more complicated having a dedicated Context.Provider component can make our lives a lot easier. Here is a use case to consider
Shared Data From External Sources (Post, Get)
Contexts may allow us to store any code related to the initialization of the shared state within the context itself, resulting in more easily readable and maintainable code. For example, lets say we have some user text posts on our server that are displayed by multiple components within our application, and we would also like for our users to be able to add new posts. All of this can be handled quite neatly within the Context.Provider:
import React, {useContext, useEffect, useState} from 'react';
export const PostsContext = React.createContext([]);
export default PostsContextProvider({children}) {
const [posts, setPosts] = useState([]);
function fetchPosts() {
// Here we will fetch the posts from our API, and then set the state
// stored within the Context.Provider equal to the fetched posts.
fetch('https://www.fakewebsite.com/api/posts/get', {
method: 'GET',
headers: {'Content-Type': 'application/json'}
}).then((response)=>{
// Convert response to json
return response.json();
}).then((json)=>{
// json here is the posts we fetched from the server, so we set the state
// equal to this value. This will update the state within all components
// that are using the context.
setPosts(json.posts);
})
}
useEffect(function(){
// This function will run a single time when the application is started
fetchPosts();
},[])
function addNewPost(post) {
// This is the function that will be used by the components.
// First, we will send the new post to the server so that it can store it.
fetch('https://www.fakewebsite.com/api/posts/post', {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({post: post})
}).then((response)=>{
if(response.ok) {
// The server has updated its database with our new post.
// Now we just need to fetch the posts from the server again to get the updated data.
fetchPosts();
}
})
}
return (
<PostsContext.Provider
value={[posts, addNewPost]}
>
{children}
<PostsContext.Provider />
)
}
Notice that the value prop we are passing does not actually pass the state setter function directly. Instead, we pass the addNewPost function. So, when a component calls useContext(PostsContext) they will get the addNewPost function. This is extremely useful, it will allow our components to easily add a single post to the shared state, while also handling the server update! Very cool. With the solution I originally proposed, this would be impossible, because we would only ever get a simple state setting function from our useContext call.
Now, we must wrap our application in the provider to make it available to all components:
// App.js
import React from 'react';
import PostsContextProvider from './posts_context';
import MyComponent from './my_component';
import MyOtherComponent from './my_other_component';
export default function App() {
return (
<PostsContextProvider>
<MyComponent/>
<MyOtherComponent/>
</PostsContextProvider>
)
}
At this point, MyComponent and MyOtherComponent now have access to the context using the useContext hook. It is now extremely simple for the components to access the posts data and also update it with a new post.
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyComponent() {
const [posts, addPost] = useContext(PostsContext); // 'posts' will always be up to date with the latest data thanks to the context.
...
}
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyOtherComponent() {
const [posts, addPost] = useContext(PostsContext);
...
function handleAddPost(title, text) {
// Now when this component wants to add a new post,
// we just use the `addPost` function from the context.
addPost({title, text});
}
...
}
The beauty of this is that all the code related to the fetching and posting of data can be neatly contained within the provider, separated from the UI code. Each component has easy access to the posts data, and when either component adds a new post the other component will be updated with the new data.
Final Thoughts
This is just scratching the surface of the usefulness of Context.Provider. It's easy to imagine using a Context.Provider to handle persistent data storage using a method very similar to the above, replacing the fetch calls with function that store/fetch persistent data. Or even, some combination of persistent data and fetched data.
Upon revisiting my original question, it actually made me laugh. I was sort of right, there should perhaps be a way to handle simple shared state between components that does not require wrapping components in a provider and does not require any provider code at all. However, providers are just so dang useful in any kind of state management within an application that it is actually probably a good thing to force people to use them for simple shared state, because then they will have to learn about this wonderful tool.

Related

Why would I use 'useReduce' in react while implementing global scoped states?

So after coding a complete web application with react with props-drilling so deep like you've never seen before, you shouldn't, I've decided to give react 'contexts' a go. I read the react documentation, and I've decided to make my states(which seriously need help because they're so deeply rooted into components after components) in a separate file:
-create a 'context.js' filee
-Inside I'm gonna have: [and notice I have two states inside]
import { createContext } from 'react'
export const context = createContext();
export default function ContextProvider (props) {
const [state1, setState1] = useState();
const [state2, setState2] = useState();
return (
<context.provider value={[state1, setState1, state2, setState2]}>
props.children
</context.provider>
)
}
Now all I have to do is import this component where I want to make these states visible, use destructuring like so:
//inside a functional component
...
const [state1, setState1] = useContext(context); //assuming I only need state1 in this component
...
Now my question is how efficient this approach is. And in some blogs I read about using context with hooks, every one I read used 'useReduce' hook. Could someone explain why 'useReduce' was used, that is what problem does it solve and how. That'll be appreciated.
This is not efficient at all: any time you call setState1() it will cause re-render of <ContextProvider>, and thus of entire app, which is presumably wrapped into <ContextProvider>.
Consider to check my library for global state management with context: https://www.npmjs.com/package/#dr.pogodin/react-global-state :)
I feel like this this question is pretty relevant here: Fast changing states without Redux (at the end of the day, useReducer and useState should be equally efficient in terms of performance? I'm not an expert here though).
Also, useReducer is better for handling complex state, which usually happens when you start using Contexts or Redux.
If you want to have an object like this in your state
user: {
options: { ... },
settings: { ... }
}
You REALLY should use useReducer (especially if you're in a situation where changing in the options relies on knowing what the settings are) and have the complicated logic be handled through the dispatch, rather than jumping through hoops and trying to get it to work with useState. At the end of the day, they do pretty much the same thing in that they "handle state", if you want to know more about the nuances, best way to do it is to read the docs: https://reactjs.org/docs/hooks-reference.html#usereducer.
Again, I'm not an expert but I've made a fair few attempts at looking into "performance in React" and from what I've gathered, rerenders due to state change are rarely the problem. Usually you'll have something in a child component that relies on performing an expensive calculation on a prop, and the correct solution isn't to "prevent the rerender" but instead to do something like "move the expensive calculation up" so it only needs to happen once per render rather than n children per render.

Update react context outside of a consumer?

I am trying to understand how the new react context API works.
In redux, it is possible for a component to have knowledge of dispatch actions without knowing state. This allows updates to redux state without causing a rerender of components that don't care about that state.
For example I could have
<Updater onClick={updateCount}/>
and
<Consumer value={count}/>
Updater is connected to dispatch(updateCount()) and Consumer is connected to count's current value via state.count. When state.count is updated, only the Consumer rerenders. To me, that's a crucial behavior.
In react context, it seems very difficult to duplicate this behavior. I'd like to be able to update state without causing unnecessary rerenders of components that want to alter the context but don't actually care about the state.
How would it be possible for components to trigger updates to context if they are not inside a consumer? And I definitely don't want to trigger an update to the entire tree by setting state at the provider level.
interesting question. Not sure you can without at least an extra layer (but happy to be shown wrong).
Maybe using Memo or PureComponent to minimise the re-rendering?
import React, { memo } from 'react';
function Widget({ setContext }) {
return <button onClick={setContext}/>Click Me</button>;
}
export default memo(Widget);
...
function Wrap() {
const { setSession } = useContext(SessionContext);
return <Widget setSession={setSession} />;
}
One possible solution is to transform your consumer components into pure components and check against the values each component really cares about.
This can be easily done using the onlyUpdateForKeys HOC from recompose.
you can try this library react-hooks-in-callback to isolate the context from your component and pick only desired state values from it,
check this example

React Hooks - What's happening under the hood?

I've been trying out React Hooks and they do seem to simplify things like storing state. However, they seem to do a lot of things by magic and I can't find a good article about how they actually work.
The first thing that seems to be magic is how calling a function like useState() causes a re-render of your functional component each time you call the setXXX method it returns?
How does something like useEffect() fake a componentDidMount when functional components don't even have the ability to run code on Mount/Unmount?
How does useContext() actually get access to the context and how does it even know which component is calling it?
And that doesn't even begin to cover all of the 3rd party hooks that are already springing up like useDataLoader which allows you to use the following...
const { data, error, loading, retry } = useDataLoader(getData, id)
How do data, error, loading and retry re-render your component when they change?
Sorry, lots of questions but I guess most of them can be summed up in one question, which is:
How does the function behind the hook actually get access to the functional/stateless component that is calling it so that it can remember things between re-renders and initiate a re-render with new data?
React hook makes use of hidden state of a component, it's stored inside a fiber, a fiber is an entity that corresponds to component instance (in a broader sense, because functional components don't create instances as class components).
It's React renderer that gives a hook the access to respective context, state, etc. and incidentally, it's React renderer that calls component function. So it can associate component instance with hook functions that are called inside of component function.
This snippet explains how it works:
let currentlyRenderedCompInstance;
const compStates = new Map(); // maps component instances to their states
const compInstances = new Map(); // maps component functions to instances
function useState(initialState) {
if (!compStates.has(currentlyRenderedCompInstance))
compStates.set(currentlyRenderedCompInstance, initialState);
return [
compStates.get(currentlyRenderedCompInstance) // state
val => compStates.set(currentlyRenderedCompInstance, val) // state setter
];
}
function render(comp, props) {
const compInstanceToken = Symbol('Renderer token for ' + comp.name);
if (!compInstances.has(comp))
compInstances.set(comp, new Set());
compInstances.get(comp).add(compInstanceToken);
currentlyRenderedCompInstance = compInstanceToken;
return {
instance: compInstanceToken,
children: comp(props)
};
}
Similarly to how useState can access currently rendered component instance token through currentlyRenderedCompInstance, other built-in hooks can do this as well and maintain state for this component instance.
Dan Abramov created a blog post just a couple days ago that covers this:
https://overreacted.io/how-does-setstate-know-what-to-do/
The second half specifically goes into details regarding hooks like useState.
For those interested in a deep dive into some of the implementation details, I have a related answer here: How do react hooks determine the component that they are for?
I would recommend reading https://eliav2.github.io/how-react-hooks-work/
It includes detailed explanations about what is going on when using react hooks and demonstrate it with many interactive examples.
Note - the article does not explain in technical terms how React schedule calls for later phases, but rather demonstrates what are the rules that react uses to schedule calls for later phases.
The URL in another answer given by Eliav Louski is so far the best React explaination I have come across. This page should replace React's official tutorial as it removes all the magic from hooks and friends.

React Context Folder hiarchy / architecture?

I'm currently looking at implementing Context into one of our apps over Redux, but, I can't seem to find any information on what would be the best structure for large scale apps?
Redux has a defined way to create reducers, actions, etc. With Context, all I've found are the generic "create a provider, put state and methods all on the same file, and then use a consumer".
TL;DR Is there a way to build a hiarchy that is beneficial for long term, and large scale applications with React Context?
Edit: I guess this is incorrect to think of them having a similar structured relationship. Unfortunately, I'm not able to use Redux because of AEM's limitations. Context does work however, so I wanted to hopefully be able to build some structure with that.
First of all, I don't think there is necessarily a right or wrong answer to this question, but I will just give you my two cents.
I am currently refactoring a web application which serves several millions of sessions per month and am testing a redux and context version on internal stage servers.
Important notices:
I am using a mono-store approach
It's not an app which constantly has global store updates
To the folder structure. I like to keep my store in the root of the project. For a react app based on react-create-react-app that would be the /src and it basically consists of the following files:
index.js // everything gets "bundled" here
initialState.js // provides the store with intial state e.g. from server, cache etc.
methods/*.js // contains split methods based on the part of the app that they are used in (if it can be split into separate parts)
Ergo my index.js is as simple as:
import React from 'react';
import storeMethods from './methods';
import initialState from './initialState';
// to start of experimenting with context
// i would keep all read and write key value
// pairs right here and split as the codebase
// grows and you realize you need more space
export const store = {
...initialState,
...storeMethods
}
export const StoreContext = React.createContext(store)
storeMethods is a bundled export from all methods in the methods/ folder. Basically it's just another object of containing keys which values are functions like so:
export const methods = {
showNavBar: function() {
this.setState({ navBarOpen: true })
}
}
initialState is as much as the representation of key value pairs that are required to render the base content of the app and or never change. Basically some global settings. Initialstate coming from the server, is being added to the store in the constructor of my App, right before I bind the lexical scope.
The store get's thrown into the state of the relevant outermost React Component and is used as the app state, where I bind the store's scope to the React Components lexical scope.
Then I have a higher order component withContextConsumer which is used to wrap any React component which needs access to the state. The HOC distributes the subscribed keys down as props to the wrapped component and can be consumed as read only or write.
No matter how you end up using Context, don't forget, that any Consumer will have it's render method automatically called if the Context Store is being updated. To avoid that on a simple oldProps !== newProps level, you can use PureComponents. For more complex diffs you can use the lifecyclemethod shouldComponentUpdate
edit
Basic App Structure
App.js:
import React, { PureComponent } from 'react'
import { StoreContext, store } from './store'
import { bindScopeToFunction } from './helpers'
class App extends PureComponent {
constructor(props) {
super(props)
const { initialState = {} } = props
const boundStore = bindScopeToFunction(store, this)
this.state = {...boundStore, ...initialState}
}
render () {
return(
<StoreContext.Provider value={this.state}>
// in here you render all your app
// routing, childcomponents etc
// in any component where you need access
// to the global store
// wrap it in <StoreContext.Consumer> it has
// the whole store as render prop
</StoreContext.Provider>
)
}
}
Working basic example can be found here https://codesandbox.io/s/pm85w4y6xm

Sharing global/singleton data in react app

I'm rewriting a small app to try and better understand React. I'm trying to determine the "correct"/most efficient method of sharing "singleton" data - for example, a user who's been properly authenticated upon login.
Right now the parent "application" component has a user property in its state, which I pass to child components as a prop:
<Toolbar user={this.state.user} />
<RouteHandler user={this.state.user}/>
(I'm using react-router). This works, and in read-only cases like this, isn't terrible. However, my actual login form component (which is a route, and would be inside RouteHandler), needs some way to "set" the new user data, so I also need to pass in some callback:
<RouteHandler onAuthenticated={this.setUser} user={this.state.user}/>
Not a big problem, except for the fact that now this method is available to every "route" handled by RouteHandler.
I've been reading up and it seems like the only alternative is an EventEmitter or Dispatch-style system.
Is there a better way I'm missing? Is an event emitter/dispatcher system worth using when there's really only one or two uses in an app this small?
React Context provides a way to pass data through the component tree without having to pass props down manually at every level. With context, every component nested under a Provider has access to the data, but you need to explicitly read the value.
I recommend using React Hooks with useContext. One way to do this would be to set the value of the context to be an object with setter and getter functions.
import React, { useState, useContext } from "react"
export const UserContext = React.createContext({}); //Initialise
//Wrapper with getter and setter
const App = () => {
const [user, setUser] = useState();
const value = {user, setUser}
return (
<div>
<UserContext.Provider value={value}>
<RouteHandler/>
<AnotherComponent/>
</UserContext>
<ComponentWithoutAccessToUserContext/>
</div>
)
}
const RouteHandler = (props)=> {
const { user, setUser } = useContext(UserContext)
// This component now has access to read 'user' and modify it with 'setUser'
}
const AnotherComponent = () => {
return (<div>Component which could be get access to the UserContext</div>)
}
For singleton - you can just create separate module for user service and import it into module where you define components that need it it.
Other quite similar, but more powerful option, is to use DI container - define your react components as a services in DI container, with dependencies to other services like one for user data. This would be more suitable for universal(isomorphic) app - because, you will be able to easily replace dependencies with specific implementations, or for case when you need to create separate instances for separate scopes(like for user sessions server-side).
Also if using this approach, I would recommend to separate pure react components from logic - you can create separate pure component that receives all data, and callbacks as a props, and than create HoC component in DI container that will wrap it and will pass needed data and callbacks.
If you need DI container - there is a plenty of them, but I will recommend to look at angular 2 di container, or if you would like something simpler - below I referenced my project, it has very simple but yet powerful DI inspired by angular 2 DI(it is easy to pull from that project - just one file + test)).
About notifying components about changes, and organising async logic - you still will need something like EventEmitter to notify components about changes, and you will need to write life cycle callbacks for components to subscribe/unsubscribe from updates… You can do this by hand or creating mixin or HoC to shorten that.
But from my perspective, there is better approach - try reactive programming, and RxJS in particular. It plays very well with react.
If you are interested about options connecting Rx with React - take a look at gist https://gist.github.com/zxbodya/20c63681d45a049df3fc, also it can be helpful about implementing HoC component with subscription to EventEmitter mentioned above.
I have a project that is intended for creating isomorphic(rendered server side, and than same html reused client side) widgets with react.
It has DI container to pass dependencies, and it uses RxJS to manage async logic:
https://github.com/zxbodya/reactive-widgets
One way is to subscribe to an Observable emitted from your data model.
Router.run(routes, Handler =>
Model.subject.subscribe(appState =>
React.render(
<Handler {...appState}/>,
document.getElementById('app')
)
)
);
...appState being the data coming from observable (in this case model), making these your props so you can then feed them to the app like below
<RouteHandler {...this.props} />
and any child component can pick them up with this.props
the answer is more complex that this but if you look at RxJS+React you will get a full working examples of simple data flows

Categories

Resources