I'm learning Relay now and I was wondering if there is a way to access the state without having to pass down props. I thought that Relay used React Context, however, in all the examples they seem to be using props to pass down state instead of directly accessing the Context store. Is it possible to access state through Context? If so, is it considered bad practice?
My concern is that I will start to have a lot of props being passed down to components. In addition, it is difficult to pass down props to certain components in my application.
It is a bad practice to pass down Relay state using context, redux, or some other kind of external store. One of the core features of the library is making sure all components who need a piece of data get that piece, and no other pieces. This is one of the reasons Relay uses a compiler.
With Relay, developers use a declarative language called GraphQL to specify what data each component needs, but not how to get it.
[The compiler] allows components to be reasoned about in isolation, making large classes of bugs impossible
Subcomponents should use fragments. For example:
// User.tsx
interface UserProps {
initialQueryRef: PreloadedQuery<UserQuery>
}
export default function User({ initialQueryRef }: UserProps) {
const { user } = usePreloadedQuery(
graphql`
query UserQuery($uid: String!) {
user(uid: $uid) {
uid
...Avatar_fragment
...Biography_fragment
# ...
}
}
`,
initialQueryRef
)
return (
<div>
<Avatar user={user} />
<Biography user={user} />
{/* ... */}
</div>
)
}
// Biography.tsx
interface BiographyProps {
user: Biography_fragment$key // compiler generated type
}
export default function Biography({ user }: BiographyProps) {
const { shortBio, longBio } = useFragment(
graphql`
fragment Biography_fragment on User {
shortBio
longBio
}
`,
user
)
// ...
}
Relay will take care of updating the components that use fragments whenever the fragment data changes due to mutations, subscriptions, etc. You should never have to think about managing their state programmatically.
Related
I have a menu component that appears globally. What is the best practice for getting data into that component?
I'm trying to take advantage of static generation that Next.js offers but all data fetching guidance from the Next.js team relates to pages. getStaticProps and getStaticPaths seem to pertain to page generation, not data for components. Is their SWR package the right answer, or Apollo Client?
Typically in hooks-based React, I'd just put my data call into useEffect. I'm not sure how to reason this out being that everything is rendered at build time with Next.
This is such a tricky problem, I think we need to lay out some background before a solution comes into focus. I'm focusing in the React.js world but a lot of this would apply to Vue/Nuxt I'd imagine.
Background / Static Generation Benefits:
Gatsby and Next are focused on generating static pages, which vastly improves performance and SEO in React.js sites. There is a lot of technical overhead to both platforms beyond this simple insight but let's start with this idea of a digital machine pumping out fancy HTML pages for the browser.
Data Fetching for Pages
In the case of Next.js (as of v9.5), their data fetching mechanism getStaticProps does most of the heavy lifting for you but it's sandboxed to the /pages/ directory. The idea is that it does the data fetching for you and tells the Next.js page generator in Node about it during build time (instead of doing it component-side in a useEffect hook - or componentDidMount). Gatsby does much the same with their gatsby-node.js file, which orchestrates the data fetching for page building in concert with a Node server.
What about Global Components that need data?
You can use both Gatsby and Next to produce any kind of website but a huge use case are CMS-driven websites, because so much of that content is static. These tools are an ideal fit to that use case.
In typical CMS sites, you will have elements that are global - header, footer, search, menu, etc. This is where static generation faces a big challenge: how do I get data into dynamic global components at build time? The answer to this question is... you don't. And if you think about this for a minute it makes sense. If you had a 10K page site, would you want to trigger a site-wide rebuild if someone adds a new nav item to a menu?
Data Fetching for Global Components
So how do we get around this? The best answer I have is apollo-client and to do the fetch client side. This helps us for a number of reasons:
For small size queries, the performance impact is negligible.
If we need to rebuild pages for changes at the CMS layer, this slides by Next/Gatsby's detection mechanisms, so we can make global changes without triggering gigantic site-wide rebuilds.
So what does this actually look like? At the component level, it looks just like a regular Apollo-enhanced component would. I usually use styled-components but I tried to strip that out so you can could better see what's going on.
import React from 'react'
import { useQuery, gql } from '#apollo/client'
import close from '../public/close.svg'
/**
* <NavMenu>
*
* Just a typical menu you might see on a CMS-driven site. It takes in a couple of props to move state around.
*
* #param { boolean } menuState - lifted state true/false toggle for menu opening/closing
* #param { function } handleMenu - lifted state changer for menuState, handles click event
*/
const NAV_MENU_DATA = gql`
query NavMenu($uid: String!, $lang: String!) {
nav_menu(uid: $uid, lang: $lang) {
main_menu_items {
item {
... on Landing_page {
title
_linkType
_meta {
uid
id
}
}
}
}
}
}
`
const NavMenu = ({ menuState, handleMenu }) => {
// Query for nav menu from Apollo, this is where you pass in your GraphQL variables
const { loading, error, data } = useQuery(NAV_MENU_DATA, {
variables: {
"uid": "nav-menu",
"lang": "en-us"
}
})
if (loading) return `<p>Loading...</p>`;
if (error) return `Error! ${error}`;
// Destructuring the data object
const { nav_menu: { main_menu_items } } = data
// `menuState` checks just make sure out menu was turned on
if (data) return(
<>
<section menuState={ menuState }>
<div>
{ menuState === true && (
<div>Explore</div>
)}
<div onClick={ handleMenu }>
{ menuState === true && (
<svg src={ close } />
)}
</div>
</div>
{ menuState === true && (
<ul>
{ data.map( (item) => {
return (
<li link={ item }>
{ item.title }
</li>
)
})}
</ul>
)}
</section>
</>
)
}
export default NavMenu
Set Up for Next to Use Apollo
This is actually really well documented by the Next.js team, which makes me feel like I'm not totally hacking the way this tool is supposed to work. You can find great examples of using Apollo in their repo.
Steps to get Apollo into a Next app:
Make a custom useApollo hook that sets up the connection to your data source (I put mine in /lib/apollo/apolloClient.js within Next's hierarchy but I'm sure it could go elsewhere).
import { useMemo } from 'react'
import { ApolloClient, InMemoryCache, SchemaLink, HttpLink } from '#apollo/client'
let apolloClient
// This is mostly from next.js official repo on how best to integrate Next and Apollo
function createIsomorphLink() {
// only if you need to do auth
if (typeof window === 'undefined') {
// return new SchemaLink({ schema })
return null
}
// This sets up the connection to your endpoint, will vary widely.
else {
return new HttpLink({
uri: `https://yourendpoint.io/graphql`
})
}
}
// Function that leverages ApolloClient setup, you could just use this and skip the above function if you aren't doing any authenticated routes
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(),
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
// Restore the cache using the data passed from getStaticProps/getServerSideProps
// combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState })
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
// This is goal, now we have a custom hook we can use to set up Apollo across our app. Make sure to export this!
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
Modify _app.js in the /pages/ directory of Next. This is basically the wrapper that goes around every page in Next. We're going to add the Apollo provider to this, and now we can globally access Apollo from any component.
import { ApolloProvider } from '#apollo/react-hooks'
import { useApollo } from '../lib/apollo/apolloClient'
/**
* <MyApp>
*
* This is an override of the default _app.js setup Next.js uses
*
* <ApolloProvider> gives components global access to GraphQL data fetched in the components (like menus)
*
*/
const MyApp = ({ Component, pageProps }) => {
// Instantiates Apollo client, reads Next.js props and initialized Apollo with them - this caches data into Apollo.
const apolloClient = useApollo(pageProps.initialApolloState)
return (
<ApolloProvider client={ apolloClient }>
<Component {...pageProps} />
</ApolloProvider>
)
}
export default MyApp
And now you can get dynamic data inside of your components using Apollo! So easy right ;) HA!
For global data fetching in NextJS, I use react-query and there is no need for a global state because it lets you to cache the data. Let's say you have a blog with categories and you want to put the category names in the navbar as a dropdown menu. In this case you can call the API to fetch the data with react-query from the navbar component and cache it. The navbar data will be available for all pages.
Can't find any recent official info if any of the three options below is allowed?
constructor(props) {
this.state = {
item: <SomeItem />,
item1: () => <SomeItem />,
item2: SomeItem,
};
}
I found this answer but it references an old link from web archive which says:
What Shouldn’t Go in State?
...
React components: Build them in render()
based on underlying props and state.
But that link doesn't say why that is a bad idea, if it will introduce bugs, etc.
This is a really good question.
The reason that putting components in state is advised against is just that it goes fundamentally against the React model, which is that a component provides a render method (which is a pure function) that the React engine uses to automatically update the DOM to reflect the values of the component's props and state.
The output of that render, i.e. the React Element, is supposed to be used directly by the React engine. The contract is that your app, and all its components, generate a bunch of Elements in a pure way for the React engine to manage.
By doing things like introducing side effects in render, or putting the Elements in state, you're essentially breaking the 'pure' contract and it may give unpredictable results, which may or may not be considered bugs in your application. The specifics of the bugs may even change with different versions of React, with different engine implementations. The point is that you're breaking the React contract, so whilst it may work in some cases, it also may not in others or even the same cases as React itself changes. The behaviour is not guaranteed.
React has built-in ways to cache renders based on prop values, like React.memo, that the engine provides and understands, and are part of the contract. If you want to cache render output for performance reasons, this is the way to do it.
Indeed, this is exactly why such functions are provided by the React API rather than just letting you do it yourself.
At the end of the day, React component instances are just objects, and you can store objects in state, so it shouldn't cause any trouble if you avoid pitfalls. One such pitfall is that if you're creating handlers to put on their props, those handlers will close over the context in which they're created, which may lead to some unexpected outcomes. Here's an example of that:
const {useState, Fragment} = React;
function Thingy({onClick}) {
return <div onClick={onClick}>A</div>;
}
// STALE CLOSURE
function Example() {
const [value, setValue] = useState(0);
const [comp, setComp] = useState(
<Thingy onClick={() => { console.log("A: value = " + value); }} />
);
const handler = () => {
setValue(v => {
++v;
console.log("B: value = " + v);
return v;
});
};
return <Fragment>
{comp}
<div onClick={handler}>B</div>
</Fragment>;
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
This is the classic stale closure thing. It's probably a bit easier to do accidentally using functional components and hooks (as I did there) rather than class components, but it's definitely possible to do with class components as well.
But if you're not doing that (either not creating functions for the component you're storing, or creating ones that don't use anything they close over that may change), it should be fine.
But look at React.memo, which may be a better answer depending on what your reason for wanting to put component instances in state is.
You can do something like this, if I understand you right
const Title = () => {
return <h1>Hello CodeSandbox</h1>;
};
class App extends React.Component {
state = {}
constructor(props) {
super(props)
this.state = {
item: function() {
return <Title />;
}
};
}
render() {
return (
<div className="App">
{this.state.item()}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
export default App;
You can do it, but it's a bit strange. A React element is an object like any other. In this case, it will be the result of a method call:
// `<SomeItem/>` compiles to
React.createElement(SomeItem, null);
// A similar `React.createElement("div", null)` becomes
const el = {
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: "div",
_owner: null,
}
It's strange because it's unnecessary (and a little confusing). You can just generate the element (including any state or props updates) whenever you need it.
There's also a risk that you break one of the core guarantees of React: elements are immutable. Storing the element like this gives you a chance to mutate it and thus confuse React.
If you need many copies of the same element then it may be slightly more performant to keep it like this, especially if it is expensive to generate.
there! The App component is a container for three different components:
Map renders a map with visual marks on it representing addresses the user has provide.
List component contains all added addresses as list items.
Input allows a user to add a new address (in my terms that is called LocationPoint).
Right now, the App keeps locations array in state with all those addresses (LocationPoints) and passes that array into all child components.
Manipulations with LocationPoints (add/move/update/deleteLocationPoint) are taken out to separate function as they are quite generic and may be reused somewhere else later.
But because those functions do not know about state existence I have to create some kind of "provider" functions that calls those actions (addLocationPoint, deleteLocationPoint, etc). E.g. addLocationPoint func has to be called inside App.addLocationPoint.
The following example should explain what I was talking about better. Note: snippet doesn't work as it's not a real implementation.
// Adds a new location point
const addLocactionPoint = (locations: array, address: string) => {
// ...
return updatedLocations;
}
class App extends React.Component {
constructor() {
this.state = {
locations: [],
}
// bind addLocPoint, etc.
}
addLocPoint(address) {
this.setState(state => {
addLocactionPoint(state.locations, address);
});
}
// ...
render() {
return (
<Input onSubmit={ this.addLocPoint } />
<List
onDrag={ this.moveLocPoint }
onDelete={ this.deleteLocPoint }
/>
<Map data={ this.state.locations } />
);
}
}
Can my approach be considered as a good practice? Or there are other ways to reduce amount of logic in App component and to avoid creating those "providers" without using state management libs (MobX, Redux, etc). Maybe the case I consider is a right time to introduce a Redux or MobX?
I'll be really grateful for advice or recommendations or links to explore on this question.
It's already good enough, this is how global state is usually maintained in vanilla React with no state management libraries. Context API may additionally be used to pass the state to nested components.
The thing that can be changed is that functions that update the state can actually be extracted and used as setState higher-order updater functions:
const addLocationPoint = (address) => ({ location }) => {
// ...
return updatedLocations;
}
class App extends React.Component {
...
addLocPoint(address) {
this.setState(addLocationPoint(address));
}
...
A similar idea is used in Redux action creators.
In order for state updaters to provide real improvements, they have to be moved to another module. In this case they can be tested separately from a component that uses them and mocked with jest.mock in a component that uses them.
I've been trying relay-modern for some time, and I'm wondering what are the purposes of createFragmentContainer other than just for describing the fragment that should beloing to the Component.
e.g. this is how documentation show how it is supposed to be
Parent.js
<QueryRenderer
render={({error, props}) => {
if (error || props) {
return <Child someData={someData}>
}
return <div>Loading</div>
}}
query={graphql`
query SomeQuery($id: ID!) {
endpoint(id: $id) {
...Child_someData
}
}
`}
/>
Child.js
export default createFragmentContainer(
({someData}) => <header>{someData.title} - {someData.name}</header>,
graphql`
fragment Child_someData on EndPoint {
title
name
}
`
)
But instead doing Child.js in that way, I can just rewrite or splitting the component with query to 2 different files like this:
ChildComponent.js
export default ({someData}) => <header>{someData.title} - {someData.name}</header>
Child.js
export default graphql`
fragment Child_someData on EndPoint {
title
name
}
`
and it is still going to work (Parent.js will still recognize the fragment). So this makes me wondering if createFragmentContainer just for syntactic sugar to make things tidier.
If anyone can shed a light with this one, that would be awesome! can't find so much in the documentation about this
Your example if a fairly static implementation... I think what you need to consider is that these are containers that provide additional functionality, fragmentContainer being one of the more abstract layers.
I would look more at how the refetchContainer and paginationContainer expand upon the idea of a fragmentContainer and also look at the Github repository itself,
https://github.com/facebook/relay/blob/master/packages/react-relay/modern/ReactRelayFragmentContainer.js
So certainly child containers could simply be a file filled with fragments that you are exporting out, but ideally you should think of them as containers which are extensions of React components. They are container fragments that bubble up into a compositional query which afford you conveniences for managing state in the context of React.
Relay compiler will find your Child.js file so the fragment will be created and your query will be fetched.
However, the difference is createFragmentContainer is a HOC that Relay uses to guarantee that the component won't render until all necessary data is available. This is the purpose of FragmentContainer and that's why you should use it.
How do people typically approach having "global" data in a React application?
For example, say I have the following data for a user once they're logged into my app.
user: {
email: 'test#user.com',
name: 'John Doe'
}
This is data that almost any component in my app might like to know about - so it could either render in a logged in or logged out state, or perhaps display the users email address if logged in.
From my understanding, the React way of accessing this data in a child component is for a top level component to own the data, and pass it to child components using properties, for example:
<App>
<Page1/>
<Page2>
<Widget1/>
<Widget2 user={user}/>
</Page2>
</App>
But this seems unwieldy to me, as that would mean I'd have to pass the data through each composite, just to get it to the child that needed it.
Is there a React way of managing this type of data?
Note: This example is very simplified - I like to wrap intents up as composites so implementation details of entire UI features can be drastically changed as I see fit.
EDIT: I'm aware that by default, calling setState on my top level component would cause all child components to be re-rendered, and that in each child component I can render using whatever data I like (e.g. global data, not just state or props). But how are people choosing to notify only certain child components that they should be rendered?
Since I originally answered this question, it's become apparent to me that React itself doesn't support "global" data in any sense - it is truly meant to manage the UI and that's it. The data of your app needs to live somewhere else. Having said that, it does now support accessing global context data as detailed in this other answer on this page. Here's a good article by Kent Dodds on how the context api has evolved, and is now officially supported in React.
The context approach should only be used for truly global data. If your data falls into any other category, then you should do as follows:
Facebook themselves solve this problem using their own Flux library.
Mobx and Redux are similar to Flux, but seem to have more popular appeal. They do the same thing, but in a cleaner, more intuitive way.
I'm leaving my original edits to this answer below, for some history.
OLD ANSWER:
The best answer I've found for this so far are these 2 React mixins, which I haven't had a chance to try, but they sound like they'll address this problem:
https://github.com/dustingetz/react-cursor
and this similar library:
https://github.com/mquan/cortex
MAJOR NOTE: I think this is a job for Facebook's Flux, or something similar (which the above are). When the data flow gets too complex, another mechanism is required to communicate between components other than callbacks, and Flux and it's clones seem to be it....
Use the React Context Property This is specifically for passing global data sets down the chain without explicitly forwarding them. It does complicate your Component lifecycle functions though, and note the cautions offered on the page I've linked.
You can use the React Context API for passing global data down to deeply nested child components. Kent C. Dodds wrote an extensive article on it on Medium React’s ⚛️ new Context API. It'll help in getting a better understanding of how to use the API.
I think React.createContext() is perfect solution for your purpose.
React will re-render only components, that listen context changes with useContext hook.
Here is a simple snippet for your code:
export const CurrentUser = React.createContext({})
const App = () =>
{
const User = getUser() // any authorisation method
return <>
<CurrentUser.Provider value={User}>
<App>
<Page1/>
<Page2>
<Widget1/>
<Widget2/>
</Page2>
</App>
</CurrentUser.Provider>
</>
}
const Widget2 = () =>
{
const User = useContext(CurrentUser)
return <>{User?.name}</>
}
In case if you want to control re-renders directly, you can use React.memo in nested components. For example, if you need re-render component only after specific attribute change.
Also, with nesting context values, you can reach good flexibility of your app. You can pass different context values for different part of your application.
export const CurrentUser = React.createContext({})
const App = () =>
{
const User = getUser() // any authorisation method
const AnotherUser = getAnotherUser() // any authorisation method
return <>
<CurrentUser.Provider value={User}>
<App>
<Page1/>
<CurrentUser.Provider value={AnotherUser}>
<Page2>
<Widget1/>
<Widget2/>
</Page2>
</CurrentUser.Provider>
</App>
</CurrentUser.Provider>
</>
}
const Widget2 = () =>
{
const User = useContext(CurrentUser)
return <>{User?.name}</>
}
What's wrong with just passing data all the way down the component chain via rendering all children with {...restOfProps}?
render(){
const {propIKnowAbout1, propIKnowAbout2, ...restOfProps} = this.props;
return <ChildComponent foo={propIKnowAbout1} bar={propIKnowAbout2} {...restOfProps}/>
}
There is Reactn https://www.npmjs.com/package/reactn
You use this.global and this.setGlobal to get and set the global state same as you do with the local state.
To be able to do so you only need to
import React from 'reactn';