Gatsby StaticImage src value not accepted via dot notation - reactjs

I understand that you cannot pass the source of an image to the StaticImage component as the result of a function. However, can anyone explain to me the difference between these two examples.
import React from 'react'
import { StaticImage } from 'gatsby-plugin-image' // to take image data and render it
export default function Example() {
const imageSrc = '../images/jonathan-adams.jpg'
const data = {
imageSrc: '../images/jonathan-adams.jpg',
}
return (
<div>
<StaticImage src={imageSrc} alt="alt text" /> {/* works */}
<StaticImage src={data.imageSrc} alt="alt text" /> {/* doesn't work */}
</div>
)
}
One is a direct variable reference, and the latter is referencing the same value, but through dot notation. Is dot notation a function?

Is dot notation a function?
It's not a function but it's not a constant value (at least it can't be treated as constant in the scope of the StaticImage component) hence it can't be statically analyzed, since in the end, what it's constant is data, not its nested properties. That's why imageSrc works but data isn't.
Restrictions on using StaticImage
The images are loaded and processed at build time, so there are
restrictions on how you pass props to the component. The values need
to be statically-analyzed at build time, which means you can’t pass
them as props from outside the component, or use the results of
function calls, for example. You can either use static values, or
variables within the component’s local scope.
Source: https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image/#restrictions-on-using-staticimage
That said, you may want to use imageSrc approach or considering using GatsbyImage which accepts dynamic values (it will need a refactor and GraphQL-based approach).

Related

How to initialize and use context in the same component?

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>
)
}

How does React provide independent contexts for subtrees?

If, as a thought experiment, I were to write my own createElement implementation for JSX, what might support for implicit context look like?
In particular, I can't figure out how with the limited means of JSX's createElement signature, contexts can be independent for different subtrees. (It appears React's Context handling has become more elaborate in recent versions; I'm mostly interested in the seemingly more straightforward mechanisms of earlier versions.)
This might be used to automatically determine heading levels, for example:
<Section title="Hello World">
<Card title="Details" />
</Section>
<Card title="Example" />
Here Card would automatically generate <h3> and <h2>, respectively, by relying on something like context.headingLevel.
A very nice question, that shows how different is the concept of creating React Elements to actually executing the render functions (either the .render method of class components or simply the main body of a functional component).
In JSX itself (which is just React.createElement(…)) there‘s no concept of “context” at all. It comes into existance only when the components are rendered. It is indeed a duty of the React Renderer (such as React DOM or React Native) to actually implement Context APIs.
If you remove the ability to store states and to update the UI you are left with a minimal React implementation that only “renders once”, but perfectly fine to understand the problem at hand.
Everytime the React Renderer needs to render a React Elements tree (such as one built with JSX) it passes every single element and transforms it into a DOM structure, but when it encounters a component node (not a “native” element) it needs to render it to obtain its React Element sub tree, and swap the original node with it.
It’s in this specific moment that React can keep track of which Context values to pass to which components, since it is traversing the tree.
So, to answer directly your question, you can’t implement context in the “element creation phase”, inside your JSX implementation, you need to do it in a subsequent phase when you can traverse the tree.
If you were trying to build an “immediate JSX” you probably have something like this:
function createElement(type, props, ...children) {
props = { children, ...props };
if (typeof type === 'function') {
return type(props);
} else {
return { type, props };
}
}
In thise case you will not be able to implement an API similar to context, because the execution order is inner-then-outer:
const div = createElement('div', {}, createElement(Card, {}));
// identical to
const card = createElement(Card, {}); // inner, and then…
const div = createElement('div', {}, card); // outer

Can you explain this react native code (strange arrow function in render method)?

So this is some React Native code from a textbook that I'm going through, specifically it is from the render method of App.js. Of course the /* ...*/ would be filled in with actual code but it's irrelevant to my question.
<MeasureLayout>
{layout => (
<KeyboardState layout={layout}>
{keyboardInfo => /* … */}
</KeyboardState>
)}
</MeasureLayout>
What I don't understand is what is happening with {layout => (.... So I take it that layout is an arrow function that returns this keyboardState component. So how does layout then pass itself into keyboardState's layout prop at this part <KeyboardState layout={layout}>? And why would I want to do that exactly? This whole part here is really baffling me.
React components have props and children properties. The children property is usually a React node, but it can also be a function that returns a React node.
So how does layout then pass itself into keyboardState's layout prop at this part ?
The MeasureLayout component was created so that its children property was defined as a function instead of a React node.
And why would I want to do that exactly?
For dependency injection and as a pattern that allows for a more declarative style of programming with class-based components.
Some more in depth reading:
Topic: Functions as children
https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9
https://codedaily.io/tutorials/6/Using-Functions-as-Children-and-Render-Props-in-React-Components
See that {} inside render method is used for some javascript statements.
For eg.
<Text>
{personFirstNam +" " +personLastName}
</Text>
But now that in your code there is again JSX elements inside {}, it is used inside unnamed function.
i.e.
{layout => (
...// here you can use JSX element which will be returned into render method for UI.
)}
alternatively, if you want some operations there,
{layout =>{
let extractData = fromSomeWhere;
let calculatePosition = getPosition();
return (<KeyboardState layout={layout}>
{keyboardInfo => /* … */}
</KeyboardState>)
}}
All of these was to just do some JS statement executions/operations inside one JSX element.
The <MeasureLayout> is passing an argument to its children as a function. and to recieve it an arrow function is used.
so, basically the code of <MeasureLayout> will be,
function MesauseLayout(props){
//Do things
// layout = some result.
return <div>{props.children(layout)}</div>
}
So, in order to receive this the child will have to be inside a function that accepts this value. So, an arrow function is used to receive this value.
<MeasureLayout>
{layout => (
<KeyboardState layout={layout}>
{keyboardInfo => /* … */}
</KeyboardState>
)}
</MeasureLayout>
But in my opinion, using a Context/Provider with a hook will be a better option if that is possible. This is generally only used in extreme cases. There is also another option to use React.cloneElement and passing additional props. But there are tradeoffs if you have to choose between these two. Plus, There is a concept called render props which is commonly used in new libraries.

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

React.forwardRef is already possible without it, so what's the use of it?

I'm confused on the point of React.forwardRef. As explained in its documentation, I understand that its main use is for a Parent Component to gain access to DOM elements of the Child Component. But I can already do that without even having to use it.
Here is a code example that you can plug into CodeSandbox and see that it works:
import React, {useRef, useEffect} from "react";
import "./styles.css";
const ChildComponent = (props) => {
useEffect( ()=> {
props.callbackFunction()
})
return(
<div ref={props.fRef}>
{"hello"}
</div>
)
}
export default function App() {
const callbackFunction = () => {
console.log("The parent is now holding the forwarded ref to the child div: ")
console.log(forwardedRef)
}
const forwardedRef = useRef(null)
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<ChildComponent name="gravy" callbackFunction={callbackFunction} fRef={forwardedRef}/>
</div>
);
}
Or here's the embed of this example. Honestly, I'm kind of new to this and I don't know exactly how embeds work and whether someone fiddling with the embed changes my original Sandbox or not, so I was hesitant to put it. But here it is.
Example Forwarding Ref
In the example, the parent App() component successfully passes a ref to the child which the child attaches to its rendered div. After it renders, it calls a callback function to the parent. The parent then does a console log where it proves that its forwarded ref now has a hold of the child's div. And this is all done without React.forwardRef.
So what then is the use for React.forwardRef?
You're absolutely right that you can do what you've described. The downside is that you're forced to expose an API (ie: the fRef prop) for it to work. Not a huge deal if you're a solo developer building an app, but it can be more problematic eg. if you're maintaining an open-source library with a public API.
In that case, consumers of the library won't have access to the internals of a component, meaning you'd have to expose it for them somehow. You could simply do what you're suggesting in your example and add a named prop. In fact, that's what libraries did before React 16.3. Not a huge deal, but you'd have to document it so people know how to use it. Ideally, you'd also want some kind of standard that everyone used so it wasn't confusing (many libraries used the innerRef naming convention), but there'd have to be some consensus around that. So all doable, but perhaps not the ideal solution.
Using forwardRef, passing a ref to a component just works as expected. The ref prop is already standardized in React, so you don't need to go look at docs to figure out how to pass the ref down or how it works. However, the approach you describe is totally fine and if it meets your needs, by all means go with that.
As mentioned in the docs , it's useful for highly reusable components, meaning components that tend to be used like regular HTML DOM elements.
This is useful for component libraries where you have lots of "leaf" components. You've probably used one like Material UI.
Example:
Let's say you're maintaining a component library.
You create a <Button/> and <Input/> component that maybe just adds some default styling.
Notice how these components literally are just like regular HTML DOM elements with extra steps.
If these components were made to be used like regular HTML DOM elements, then I expect all the props to be the same, including ref, no?
Wouldn't it be tedious if to get the button ref from your <Button/> component I'd have to get it through something like fRef or buttonRef ?
Same with your <Input/>, do I have to go to the documentation just to find out what ref to use and it's something like inputRef ? Now I have to memorize?
Getting the ref should be as simple as <Button ref={}/>
Problem
As you might know, ref will not get passed through props because, like key, it is handled differently by React.
Solution
React.forwardRef() solves this so I can use <Button ref={}/> or <Input ref={}/>.

Resources