Access React context from regular function - javascript

Is it possible to access React context from a non-React helper function?
For example, I have a context provider that saves the root slug for a case study page when the app loads. I then need to build URLs, consuming the context provider at various places around my app. So I've created a helper function to do so:
components/caseStudies/index.jsx
import getCaseStudyPath from '../../lib/helpers/getCaseStudyPath';
const CaseStudies = ({ caseStudy }) => {
// caseStudy.slug === 'test-case-study'
const caseStudyPath = getCaseStudyPath(caseStudy.slug);
return (
<a href={caseStudyPath}>Test</a>
);
};
lib/helpers/getCaseStudySlug
export default (slug) => {
// Get caseStudyRootSlug from React context
return `/${caseStudyRootSlug}/${slug}`;
// For example, this returns '/case-studies/test-case-study'
};
I know I can just consumer the context provider in my React component, or even as a HOC, and just pass the value to the helper function, but I need to use this helper function throughout my app and don't want the overhead of setting up a consumer every time I want to use it.

Related

Code splitting in React causes API calls to be executed multiple times

I am building a web application using React. I have used React Lazy with Webpack for code splitting but there is one issue that I have encountered. If I am on page A and I visit another screen (B) and now I re-visit the last screen (A) the screen is loaded again and the API calls in the useEffect hook of the component (A) is executed again.
My app does not have data that will constantly change so I dont want to call it again and again. What I concluded was that when the users visits another screen React unmounts the previous component and when the user visits again it mounts the component and executes the useEffect hook.
So, there are unnecessary API calls that I want to avoid. I know that I can cache my API calls but that feels like a workaround. Is there a better alternative to API caching or have I made some mistake in my code?
My app.js file where code splitting is implemented:
const LoginContainer = lazy(()=> import('./Container/StudentDashboard.container'));
My StudentDashboard.container.js file:
const StudentDashboard = ()=>{
useEffect(()=>{
(async()=>{
const data = await fetch({...}) # this API call is executed every time user visits the screen
})
},[])
}
You can create a wrapper component where you fetch the data once. Then store this data in ReactContext and use the context in your screen components.
Here is the solution:
Create context for storing data
// DataProvider.js
const DataContext = React.createContext(null);
const useData = () => React.useContext(DataContext); // create custom hook to use data on different screens
const DataProvider = ({ children }) => (
<DataContext.Provider value={React.useState(null)}>
{children}
</DataContext.Provider>
);
Create wrapper component to fetch and store data in the context
// DataResolver.js
function DataResolver({children}) {
const [storedData, setStoredData] = useData(); // custom hook created in DataProvider.js
useEffect(() => {
const fetchAndStoreData = async () => {
const data = await fetch({...});
setStoredData(data);
};
if (!storedData) { // OR any your custom condition
fetchAndStoreData();
}
},[]);
return children;
}
Use wrapper component in your app component or the component which wraps your navigation screens
// App.js
function App() {
return (
<DataProvider>
<DataResolver>
{/* Your views or routing logic */}
<View>...</View>
</DataResolver>
</DataProvider>
);
}
Finally use the context in your screen component
// StudentDashboard.js
const StudentDashboard = ()=>{
const [data] = useData(); // using custom hook you created in DataProvider.js
...
}

Is it possible to use React Hooks outside of functional component, or i have to use mobx or redux?

I am new to React, and when I was reading about the docs, I found there were two ways to implement React components, functional-based and class-based. I know before React 16.8 it's not possible to manage state in functional components, but after that there is React Hooks.
The problem is, there seems to be one restriction for React Hooks, they can only be used inside functional components. Take a server-client as an example, which needs to change an isAuthenticated state while 401 received.
//client.js
import { useUserDispatch, signOut } from "auth";
export function request(url, args) {
var dispatch = useUserDispatch();
return fetch(url, args).then(response => {
if (response.status === 401) {
logout(dispatch);
}
}
);
//auth.js
import React from "react";
var UserStateContext = React.createContext();
var UserDispatchContext = React.createContext();
function userReducer(state, action) {
...
}
function UserProvider({ children }) {
var [state, dispatch] = React.useReducer(userReducer, {
isAuthenticated: false,
});
return (
<UserStateContext.Provider value={state}>
<UserDispatchContext.Provider value={dispatch}>
{children}
</UserDispatchContext.Provider>
</UserStateContext.Provider>
);
}
function useUserState() {
return React.useContext(UserStateContext);
}
function useUserDispatch() {
return React.useContext(UserDispatchContext);
}
function signOut(dispatch) {
dispatch({});
}
export { UserProvider, useUserState, useUserDispatch, loginUser, signOut };
The client code above will produce error "Hooks can only be called inside of the body of a function component".
So maybe I have to move line var dispatch = useUserDispatch() upward to the component where request is called, and pass dispatch as props to request.
I feel this is not right, no only request is forced to care about some meaningless(to it) dispatch, but also this dispatch will spread everywhere a component needs to request.
For class-based components, this.state doesn't solve this problem either, but at least I can use mobx.
So are there some other ideal ways to solve this problem?
I came at this point too. Long story short you need to use Redux and Thunk with Async Logic, as described in detail with examples in the link below [1] if you want to do all of the stuff by hand on your own.
[1] https://redux.js.org/tutorials/essentials/part-5-async-logic
There is another solution that gives out-of-the box experience with Asynchronous API (can work with OpenAPI and GraphQL, handles request, provides caching with lifecycle, etc) wrapping stuff from [1] and its called RTK Query [2].
[2] https://redux-toolkit.js.org/rtk-query/overview
Diagram below explains [1] process visually.. but I think RTK Query [2] wraps everything in one place and could be better solution. There is a Quick Start Guide [3]. I will give it a try :-)
[3] https://redux-toolkit.js.org/tutorials/rtk-query/
Mobx and hooks are very similar in implementation. Both use a render context that is in a sense "global". React ties that render context to the component render context, but Mobx keeps that render context separate. Therefore that means that hooks have to be created within a component render lifecycle (but can sometimes be called outside that context). Mobx-react ties the Mobx render lifecycle to the react lifecycle, triggering a react re-render when observed objects change. So Mobx-react nests the react render context within the Mobx render context.
React internally keeps tracks of hooks by the number of times and order the hook is called within a component render cycle. Mobx, on the other hand, wraps any "observable" object with a proxy that lets the Mobx context know if any of its properties were referenced during a Mobx "run context" (an autorun call, essentially). Then when a property is changed, Mobx knows what "run contexts" care about that property, and re-runs those contexts. This means that anywhere you have access to an observable object you can change a property on it and Mobx will react to it.
For react state hooks, react provides a custom setter function for a state object. React then uses calls to that setter to know when it needs to re-render a component. That setter can be used anywhere, even outside a React render, but you can only create that hook inside a render call, because otherwise react has no way to tell what component to tie that hook to. Creating a hook implicitly connects it to the current render context, and that's why hooks have to be created inside render calls: hook builders have no meaning outside a render call, because they have no way to know what component they are connected to -- but once tied to a component, then they need to be available anywhere. In fact, actions like onClick or a fetch callback don't occur within a render context, although the callback is often created within that context - the action callback happens after react finishes rendering (because javascript is single threaded, so the render function must complete before anything else happens).
Hooks comes as an alternatively to class based components, you should pick up one to your project and stick to it, don't mix it up. there are some motivation for the creation of hooks, as it's better stated at docs: hook motivation.
you can create hook functions apart, but they are meant to be consumed by components. it's something like using HOC (high order component) with class based components.
const myHook = () => {
[foo, setFoo] = useState('john')
// use effect for example if you need to run something at state updates
useEffect(() => {
// do something on foo changes
}, [foo])
return [foo, setFoo] // returning state and setState you can use them by your component
}
now you have a reusable hook and you can consume at your components:
const myComponent = (props) => {
[foo, setFoo] = myHook()
const handleFoo = () => {
// some logic
setFoo(someValue)
}
return (
<div>
<span>{foo}<span>
<button onClick={handleFoo}>click</button>
</div>
)
}
obs: you should avoid declare variables as var nowadays, pick const for most, and if it's a value variable (like number) that needs update use let.
When you are creating a hooks you must refer to the Rules of Hooks
You can only call hooks from a react functions.
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (learn about them on this page).
If you want to create a reusable hooks then you can create a custom hooks for your functions.
You can call as many functions inside a hooks.
For example, here I'm refactoring the request function as a hook.
export function useRequest(url, args) {
var userDispatch = useUserDispatch();
const fetcher = React.useCallback(() => {
return new Promise((resolve, reject) =>
fetch(url, args)
.then((response) => {
if (response.status === 401) {
logout();
reject();
}
resolve(response);
})
.catch(reject)
);
}, [url, args]);
return [fetcher, userDispatch];
}
and then consumes it.
function App() {
const [fetch, userDispatch] = useRequest("/url", {});
React.useEffect(() => {
fetch().then((response) => {
userDispatch({ type: "USER_REQUEST", payload: response });
});
}, []);
return <div>Hello world</div>;
}
Yes, you have to use Redux or MobX to solve this problem. You have to maintain isAuthenticated state in the global state of Redux or MobX. Then make an action that could be named like, toggleAuthState and pass is to the child component and toggle the state from there.
Also you can use functional components for this case. Class based components is not mandatory to use MobX or Redux. If you maintain a HOC as a Container then you can pass the actions and states to the child.
I am showing an example of using a container as a HOC:
// Container
import React from "react"
import * as actions from "../actions"
import ChildComponent from "../components/ChildComponent"
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
const Container = props => <ChildComponent { ...props } />
const mapStateToProps = state => ({ ...state })
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Container)
Then in ChildComponent you can use your states and dispatch actions whenever you need.

Alternative to getState for recurring variables in Redux

I use getState to get a clientId that I need to include in every api call right now. Problem is that this interrupts data flow as the app doesn't rerender when clientId changes. Do I have to manually get the clientId in every component that I need to include it in or is there a better alternative? (clientId is also in store and is fetched first when the user logs in)
Sounds like a good candidate for the use of Context.
Here's a fictitious example of how you can set the client ID at a high level but reference it in nested components without having to query the Redux store each time using Hooks:
App
const ClientContext = React.createContext(null);
function App(props) {
return (
<ClientContext.Provider value={props.clientId}>
<MyApiComponent />
</ClientContext>
)
}
const mapStateToProps = getState => ({
clientId: getState().clientId
})
export default connect(mapStateToProps, {})(App);
So we only need to connect the App to the store to retrieve the client ID, then using Context we can make this value accessible to nested components. We can make use of useContext to pull that value in to each component
function MyApiComponent() {
const clientId = useContext(ClientContext);
...
return <MyNestedApiComponent />;
}
function MyNestedApiComponent() {
const clientId = useContext(ClientContext);
...
}
Whether it's function or class components you are using, the principle is the same - Context is used to share global state down to nested components.

How to get updated React Context with static variable?

So, I have ClientContext with default value:
export const defaultContext = {
test: "Hello"
};
export const UserApplicationContext = React.createContext(defaultContext);
And then in my child component I am updating this value:
contextDefaultData = this.context,
contextData = {
...contextDefaultData,
test: "New"
}
};
<UserApplicationContext.Provider value={contextData}>
<App/>
</UserApplicationContext.Provider>
Now, question: In App I can access updated value via UserApplicationContext.Consumer component but I can't access updated value via static like this:
import UserApplicationContext from './UserApplicationContext'
static contextType = UserApplicationContext
So, this.context will point to default value, but not to updated one.
How may I access updated value without exporting new Context?
Thanks!
I guess that's a limitation of the context api?
In order to access the most recent version of the context, you have to be inside of a component that is rendered inside of the provider's component tree and you either have to consume the context like <UserApplicationContext.Consumer> or if you wanna use hooks/functional components, you can do it in a nicer way like const mostRecentContext = useContext(UserApplicationContext).
If you try to import the context like that and access it without a consuming it via a .Consumer or the useContext hook, it will always be whatever the value use passed to React.createContext().

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

Categories

Resources