How to initialize and use context in the same component? - reactjs

I have a context called SortContext. What I would like to do is initialize this context (create its provider) and then use that context in the same component. Is this possible?
For example:
export default function MyComponent ({children}) {
const mySortValue = useContext(SortContext)
return (
<SortContext.Provider value={'exampleValue'}>
{children}
</SortContext.Provider>
)
}
In this component, the variable mySortContext will not have access to the value 'exampleValue', as this context is not created until after the useContext hook.
Alternatively:
export default function MyComponent ({children}) {
return (
<SortContext.Provider value={'exampleValue'}>
<SortContext.Consumer>
{context => {
const mySortValue = useContext(SortContext)
return children
}}
</SortContext.Consumer>
</SortContext.Provider>
)
}
Something like this doesnt work, as the function cannot use react hooks.
I could obviously just create a new component, put it within the <SortContext.Provider> tags, and access the context there, but is there any way to do it all in one component?
//Sidenote
To give some background on why I want this, I use the context to establish some information about how data should be sorted. I would like any components within the context to have access to this data. In the case where I want a simple button, list of data, and sort function in a single component, it seems like overkill to create two components; one to feed it the sort context, the other to host the sort buttons and data.

This is not possible without creating another component.
This is mentioned in the "Pitfall" box in the react js beta docs here (scroll down a bit) :
https://beta.reactjs.org/apis/usecontext#passing-data-deeply-into-the-tree

What I would like to do is initialize this context (create its provider) and then use that context in the same component. Is this possible?
In a sense, yes, but you'd do it through local variables, not context.
You're already writing code to generate the value that you'll pass into the provider. In your example, that's just value={'exampleValue'}, but even in a more complicated realworld case, it's entirely under your control what value you pass in. Simply save that value in a local variable, and use it anywhere else you need it.
export default function MyComponent ({children}) {
// Replace the following with whatever complicated logic you need to generate the value
const mySortValue = 'exampleValue';
// Do stuff with mySortValue here
return (
<SortContext.Provider value={mySortValue}>
{children}
</SortContext.Provider>
)
}

Related

Should React Context be used instead of props drilling for non global data?

I have found myself in a situation where the child components odf my app take some props with the only purpose of passing them down to their own child components, props drilling. It is clear that Context is a good alternative for data that is mean to be globally accessed in the application. However, is it a good practice to create sub-contexts for subsections of the application? In this case, the data stored in a section´s context is only going to be used for that particular section. My worry is that context may be intended to be used globally only.
//This context provider wrapps the whole application. The data stored there will be //accessible down to the last child of App.js.
<AppContextProvider>
<App/>
<AppContextProvider/>
Now inside App.js
const App ()=>{
return (
<div>
<subcompoenet1/>
<subcomponent2/>
<div/>
)
Now subcmpoenet2 is a complex section of the app. The data flowing in this section will only be accessible to its children. So is it a good practice to create a context for this section? Just like so:
const subcomponent2 ()=>{
return (
<AsectionContextProvider>
<child1/>
<chile2/>
<chile3/>
<AsectionContextProvider/>
)
}
<AppContextProvider>

React / Context API / TypeScript : How to init web app and avoid UI flickering at statup?

When a user navigate to my site, I need to initialize the React web app as follow:
If the incoming user has never requested the web app, I want to set the UI language to browser default (navigator.language).
If the incoming user has already visited the site and chosen a prefered language (lang stored in the localStorage), I want to init the UI with this language.
If the incoming user has an account and is already connected (token available in localStorage), I want to auto-connect him and render the app accordingly : login button transformed into a welcome message, UI language set to user preference.
To do so, I'm using React Context API and a defaultUser object.
defaultUser: init a default user
const defaultUser = {
language: 'en_EN',
isConnected: false
}
Context: create a default context
export const AppContext = createContext({
connectedUser: defaultUser,
})
Provider: create the provider with default context
export function AppProvider({ children }: any) {
[...]
const provider = {
connectedUser
}
return (
<AppContext.Provider value={provider}>
{children}
</AppContext.Provider>
)
}
App: init the provider during app start up
export class App extends Component {
static contextType = AppContext
render() {
return (
<AppProvider>
<AppContainer />
</AppProvider>
)
}
}
AppContainer: render the app
export class AppContainer extends Component {
static contextType = AppContext
componentDidMount() {
/** If user is not connected, verify if a stored session exists and use it to connect user */
if (!this.context.connectedUser.isConnected) {
[...do things...]
}
}
The whole mecanism works well except an annoying thing : the web app is systematically initialized with default user values, until the AppContainer::componentDidMount() do the real init job.
This is causing a sort of flickering effect.
I'm struggeling for 2 days on how to fix that, trying to perform Context init before <AppContainer /> rendering, and I'm stuck.
Any recommandations?
EDIT :
For clarity, I'm adding a diagram. Currently :
React App is rendered at start.
Context is initialized at start with default value.
Context is updated when end is reached.
React App is rendered again when end.
Any layout change during these two steps (UI language, UI modification based on user permissions) are clearly visible to the user and generate a sort of flickering.
I found sort of a solution by simply conditionning <AppContainer/> loading, postponing it to the end of the sequence. However instead of having flickering I have now a lag and other unwanted side effects.
The goal would be to differ all the sequence before React Act is rendered, and after Window is available. Then dynamically create the Context, then render all.
I think the point would be resolved if I could dynamically create the AppContext and pass a variable to createContext() during App constructor() or maybe componentWillMount() (not sure when Window is available), but then TypeScript get into play with types issues and I'm still stuck.
You didn't share the code that initializes the context, but I suspect you put the default value to be either a hardcoded value, or navigator.language and therefore experience the flickering. I'd like to suggest two ways to solve this:
Solution 1
Perhaps instead of having a hardcoded default context you could generate the default context programmatically by accessing localStorage.get('lang') or similar? There is a slight drawback to this solution though: You will be mixing concerns of react and the browser, but I think in this case it's an alternative to consider, because it's very simple and obvious to the reader.
Solution 2
Alternatively, when calling ReactDOM.render you could pass down whatever you need from localStorage as a prop to your application and so you keep the browser related logic separate from the pure React stuff.
I hope this makes sense.
Here's my follow-up after Amit suggestions, in case it can help anyone else.
Init Context with functions
Instead of initializing defaultUser with hard-coded values and update it later, I set directly it with a function returning navigator.lang as suggested. This solved the flickering issue on UI labels.
Init data before RectDOM.render
However I still had flickering on UI components for which I have to get the appropriate state from an API call.
Eg, if the incoming user has a valid session token stored in localStorage, the Login button must be disabled. Before doing so, I need to make sure the session token is valid by an async call to the API. I didn't find a way to have it «awaited» by the Context init which seems to be synchronous.
That's where Amit second suggestion get into play. Instead of struggling finding a solution inside React, I did necessary processing before ReactDOM.render, then passing stuffs as props to <Apps/>.
This works pretty well to get and pass the data...
Except that Context API didn't setSate anymore as soon as any of its data was refering to an object from outside the Context. In other word using function calls is ok to init (probably by val), but reference to external objects breaks setState.
Conclusion
As my project is still in early stage, this gave me the chance to get rid of Context API, do the proper init as required, and code the props/states progagation with basic React.

How to pass functions to different components react

Sorry for the noob question in advance. I have got a button, and I have a function in a different folder which I am trying to execute... I have tried to import the function from my other folder however it does not seem to be exceuting...How do I call this correctly?
ModalTopBar.js
import LineageContent from '../../ReusableComponents/Lineage/LineageContent'
<Button text='Get SQL' onClick={LineageContent.onHandle} />
LineageContent:
const onHandle = () => {
console.log('clicked')
}
File path to where I am importing lineage content if it helps:
First of all lets clear that the Components in reactjs is reusable so you can't simply import a new instance then use a nested function from this component
if the Component that you want to use a nested function is parent or even ancestor to the current component then you should props and pass the function address throw it
else you should use something like redux and save this function address in a store then use wherever you want this is a bad practice to use redux

What is the scope of React Context?

I am not clear on where Context can be created in a React app. Can I create context anywhere in the component tree, and if so, is that context only scoped to children of where it is created? Or is Context inherently global?
Where in the documentation can I find this?
Case: I'm reusing a component multiple times on a page and would like to use context to handle data for sub-components, but that context needs to be unique to each sub-tree.
There are 2 separate things: The context object, and the context provider.
The context object is created once globally (with an optional default value, which is global if no provider was passed from a component parent):
const FontContext = createContext('Courier');
While a context provider actually passes down its own local override of the context, which only applies to its children:
<FontContext.Provider value='Consolas'>
{children}
</FontContext.Provider>
The interesting part is that contexts cascade:
<>
<MyFontConsumer /> // value is 'Courier', the default
<FontContext.Provider value='Consolas'>
<MyFontConsumer /> // value is 'Consolas'
<FontContext.Provider value='TimesRoman'>
<MyFontConsumer /> // value is 'TimesRoman'
</FontContext.Provider>
<MyFontConsumer /> // value is 'Consolas'
</FontContext.Provider>
</>
To consume the context in your component you can use the useContext() hook, (you can use the special <FontContext.Consumer> component or MyClassComponent.contextType instead) which requires you to pass the same object.
My preferred way to avoid having to pass around the context object is to keep them all in the same component and export a custom hook:
const FontContext = createContext('Courier')
export function FontProvider({value, children}) {
return <FontContext.Provider value={value}>
{children}
</FontContext.Provider>
}
export function useFont() {
return useContext(FontContext)
}
Now you can use it in your component:
import {FontProvider, useFont} from './FontProvider'
function App() {
const font = useFont() // value is 'Courier', the default
return <FontProvider value='TimesRoman'>
<MyChild />
</FontProvider>
}
function MyChild() {
const font = useFont() // value is 'TimesRoman'
...
}
I am not clear on where Context can be created in a React app. Can I create context anywhere in the component tree?
A context object can technically be created anywhere with createContext, but to actually set the value to something other than the default you need a provider component. The context will be available via its consumer or the useContext hook in any child components of the provider.
Is that context only scoped to children of where it is created?
If by "created" you mean where the provider is located, yes. However with the new context API you can set a default value, even without a provider.
Or is Context inherently global?
No not inherently, though contexts are often placed within or above the app's root component to become global for all practical purposes.
Where in the documentation can I find this?
Context
useContext hook
Case: I'm reusing a component multiple times on a page and would like to use context to handle data for sub-components, but that context needs to be unique to each sub-tree.
As far as I understand, you have two options here:
Use a single context with different providers overriding it in sub-trees
Have a different context for each sub-tree
This is a good question. I'd like to share something about the scope of the context especially.
Context
Context actually is a "global" idea, because at the time you create it, it's not attached to any fiber/component.
const UserContext = React.createContext()
export default UserContext
That's why it's normally exported right away. Why? Since all providers and consumers need to access it in different files under normal conditions. Although you can put them in a same file, that really doesn't show off the context nature. It's made to hold a "global" value that is persisted for the entire application. The underlying value is only storing a temporary one as the scope input argument described below. The real value is hidden behind the scene, because it keeps changing depending on the scope.
Scope
Context might be created long before the hook. There's no other way for React to send a data into certain distance deeper without showing up in the props. And a context is designed similar to a function, internally React uses a stack to pop/push context as it enters or leaves a provider. Therefore you'll see similar behavior when the same context provider is nested like below.
const Branch = ({ theme1, theme2 }) => {
return (
<ThemeContext.Provider value={theme1}>
// A. value = theme1
<ThemeContext.Provider value={theme2}>
// B. value = theme2
</ThemeContext.Provider>
// C. value = theme1
</ThemeContext.Provider>
)
}
If you treat each ThemeContext as a function with input argument value, then it's clear what are the values for locations A, B and C. In a way, a context is no different as a function :) This is also a good example to show you the prop value isn't the same value that the context ThemeContext is tracking, because otherwise you'll end up with a fixed value, either theme1 or theme2. Behind the scene, there's a global mechanism :)
NOTES:
Context is designed to be very powerful, however due to the over-rendering issue, it's never reached to that level. But i guess there's no other replacement in React to share variables, therefore there's tendency that it'll become popular again, especially with the introduction of useContextSelector under proposal. I put some explanation to above in a blog here, https://windmaomao.medium.com/react-context-is-a-global-variable-b4b049812028

Correct way to share one query result throughout the app

Let's say at the top of the app, we retrieve some basic information about the app or user before rendering the rest of the application:
const getUser = gql`
query getUser(id: Int!) {
user(id: $id) {
id
name
}
}
`)
function App({ data }) {
return (
<div>
{!data.loading && !data.error && (
// the application
)}
</div>
)
}
export default graphql(getUser, {
options: (props) => ({ variables: { id: props.id }})
})(App)
Now anywhere in the application, it is safe to assume that the user has been loaded and is stored. What is the proper way for another deeply nested component to the retrieve the user data without having to redo the querying and loading logic?
This is the very basic use of a store-based library like Redux. This is not the purpose to guide every step of the way here but you are looking for a single source of truth as described here: http://redux.js.org/docs/introduction/ThreePrinciples.html
In short:
Receiving getUser response should trigger a 'LOGGED_IN' action dispatching user Data, this would be catched by a reducer updating the user object in your store (as much nested as you want), a container would then connect to this user in the store and have all its data using connect()
As of now, I'm not certain there is a proper way, but these are the options I think are reasonable
Manually pass down data via props
Wrap your deeply nested component with the same query
Manual pass down ensures your components rerender correctly, but it can be a pain to refactor. Wrapping your nested component would just hit the cache. Yes, you probably need to redo the loading logic, but that's not a show stopper.
My advice is to manually pass down props for shallow nested components and rewrap deeply nested components. Unfortunately, react-apollo doesn't provide a convenient way to access the apollo-store for nested components the same way that redux's connect container does.

Resources