How to pass variables between Relay Containers - react-router

I'm looking for the good syntax to pass a variable from a parent container to a child container.
Let's say I have theses routes, with a global widget list on / and specific widget lists on /widgets/:WidgetListID.
Note: I use react-router-relay
<Route
path='/' component={Layout}
>
<IndexRoute
component={WidgetListContainer}
queries={ViewerQueries}
/>
<Route
path='/widgets/:WidgetListID'
component={WidgetListContainer}
queries={ViewerQueries}
/>
</Route>
It's the same <WidgetList/> component, rendered inside <WidgetListContainer/> inside <Layout/> and here is how I try to pass the WidgetListID variable:
Layout.js
class Layout extends React.Component {
render() {
return (
<div>
...
{children}
...
</div>
);
}
}
WidgetListContainer.js
class WidgetListContainer extends React.Component {
render () {
return (
<div>
...
<WidgetList
viewer={viewer}
/>
</div>
)
}
}
export default Relay.createContainer(WidgetListContainer, {
initialVariables: {
WidgetListID: null
},
fragments: {
viewer: ($WidgetListID) => Relay.QL`
fragment on User {
${WidgetList.getFragment('viewer', $WidgetListID)}
}
`,
},
})
WidgetList.js
class WidgetList extends React.Component {
render () {
return (
<div>
<ul>
{viewer.widgets.edges.map(edge =>
<li key={edge.node.id}>{edge.node.widget.name}</li>
)}
</ul>
</div>
)
}
}
export default Relay.createContainer(WidgetList, {
initialVariables: {
WidgetListID: null
},
fragments: {
viewer: () => Relay.QL`
fragment on User {
widgets(first: 10, WidgetListID:$WidgetListID) {
edges {
node {
id,
name
}
}
}
}
`,
},
})
I have no problem setting the WidgetListID variable directly inside the WidgetList relay container, it works perfectly fine, but as soon as I try to pass it from the WidgetListContainer relay container I have an empty data object {__dataID__: "VXNlcjo="}. Though, the variable is well printed in my getWidget() function. So something doesn't work at some point but I can't figure out what?
What would be the good syntax to pass the WidgetListID variable from the parent container to the child container?

In the WidgetListContainer, change this:
fragments: {
viewer: ($WidgetListID) => Relay.QL`
fragment on User {
${WidgetList.getFragment('viewer', $WidgetListID)}
}
`,
},
to
fragments: {
viewer: ({WidgetListID}) => Relay.QL`
fragment on User {
${WidgetList.getFragment('viewer', {WidgetListID})}
}
`,
},
The first argument to the fragment builder is the Relay variables. So first you need to pull the WidgetListID variable out of the WidgetListContainer's variables, and then you can pass it into WidgetList.getFragment().
Note that the $ symbol is only used inside the Relay.QL template string. Inside the variables object, you refer to the variable by name, without the $.

Hi I also struggled with this for a bit.
Change this:
class WidgetListContainer extends React.Component {
render () {
return (
<div>
...
<WidgetList
viewer={viewer}
/>
</div>
)
}
}
to this:
class WidgetListContainer extends React.Component {
render () {
return (
<div>
...
<WidgetList
WidgetListID={this.props.relay.variables.WidgetListID}
viewer={viewer}
/>
</div>
)
}
}
also in your WidgetList component's relay container don't forget to set initial variables to
initialVariables: {
WidgetListID: null
},
this setup will make your WidgetListID variable available for your WidgetList component's relay container.

Related

Passing props through a "template" component

I'm building a template component using React with TypeScript but i'm facing an issue i'm unable to solve. I'm posting this in case anyone knows how to approach it.
My project has a MyComp component that invokes TemplateComp using a subcomponent GraphComp and the data that Graph requires.
TemplateComp invokes and stylises the Graph subcomponent plus adds some props that are needed (such as customPropertyA) next to graphData.
GraphComp is requiring certain parameters that graphData needs to render properly.
The issue i'm facing is related to the types definition from GraphComp to MyComp while passing through TemplateComp. It may seem that (because TemplateComp defines graphData as any, as it is unknown to it) MyComp understands that graphData can also be any, but in reality it should be equal to the properties that Graph is requiring as Props (but not all of them).
Is there any way to let MyComp and TemplateComp infer the types that GraphComp is asking for?
Here is my code:
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={GraphComp}
graphData={{
value: 0
}}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: ElementType
graphData: any
},
{}
> {
customPropertyA = 'hello'
render() {
const { graphData, Graph } = this.props
return (
<div>
<Graph {...graphData} customPropertyA={this.customPropertyA} />
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}
I'm perfectly fine with modifying how these components work. However, i still need the 3 layer approach and to be able to define GraphComp's props from within MyComp and TemplateComp separately.
For those wondering how to approach this, i managed to fix it by calling the mounted JSX instead of mounting it in TemplateComp and removed properties from it as they can also be described in MyComp.
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={<GraphComp value={0} customPropertyA={"hello"} />}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: JSX.Element
},
{}
> {
render() {
const { Graph } = this.props
return (
<div>
{Graph}
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}

React how to get wrapped component's height in HOC?

Is there any way to get the wrapped component's DOM height?
I tried adding an ref but the console errors me Function components cannot be given refs.
And I set the forward ref, but it seems not the case.
export default function withInfiniteScroll(Component) {
return class extends React.Component {
componentDidMount() {
window.addEventListener('scroll', this.onScroll, true);
}
onScroll = () => {
// here
console.log(
'window.innerHeight👉', window.innerHeight,
'\ndocument.body.offsetHeight👉', document.body.offsetHeight,
);
}
render() {
return <Component {...this.props} />;
}
};
}
I want to log the height of Component, but these logs are meaningless, they are html-body's height instead of Component's.
window.innerHeight👉 767
document.body.offsetHeight👉 767
But when I in chrome console:
console.log(document.getElementsByClassName('home-container')[0].clientHeight)
> 1484
Which the 'home-container' is a wrapped component:
withInfiniteScroll(HomeContainer);
Wrapped component should either expose a ref to underlying DOM element with forwardRef:
function withInfiniteScroll(Component) {
return class extends React.Component {
ref = React.createRef();
componentDidMount() {
window.addEventListener('scroll', this.onScroll, true);
}
onScroll = () => {
console.log(this.ref.current.clientHeight);
}
render() {
return <Component ref={this.ref} {...this.props} />;
}
};
}
const Foo = React.forwardRef((props, ref) => (
<div ref={ref}>Foo</div>
));
const FooWithScroll = withInfiniteScroll(Foo);
Or wrapper component should add container DOM element:
function withInfiniteScroll(Component) {
return class extends React.Component {
// ...same as above
render() {
return <div ref={this.ref}><Component {...this.props} /></div>
}
};
}

How to get and pass object(JSON API) in React/Redux?

i have a problem with getting and passing JSON object (info) from Store in React "Container" to his child component (InfoPage) via props..
Also i had Action's and Reducer's methods, all of there works without mistakes.
Could somebody help me with this.
This is my code from "Container".
thanks.
function mapStateToProps(state) {
return {
user: state.auth.user,
info: state.info,
ui: state.ui
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Object.assign({}, uiActions,
authActions, infoActions), dispatch);
}
class Info extends Component {
static propTypes = {
user: PropTypes.objectOf(PropTypes.any).isRequired,
ui: PropTypes.objectOf(PropTypes.any).isRequired,
info: PropTypes.objectOf(PropTypes.any).isRequired,
switchProfileMenu: PropTypes.func.isRequired,
logOut: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.handleClickOption = this.handleClickOption.bind(this);
}
handleClickOption() {
return this.props;
}
render() {
const { user, logOut, info, ui: { showProfileMenu },
switchProfileMenu } = this.props;
return (
<div className={b()}>
<Header
user={user}
logOut={logOut}
switchMenu={switchProfileMenu}
showMenu={showProfileMenu}
/>
<div className="container">
<div className="content">
<div>
<InfoPage data={info} />
sadasd
</div>
</div>
</div>
<Footer />
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I gave a look to your code and there are some missing parts.
First, the function getInfo looks to be a thunk (redux-thunk, which allows you to execute asynchronous operations). You have to import it inside your index and initialize it is, not an array
import thunk from 'redux-thunk'
// ...
createStore(rooReducer, applyMiddlewares(thunk))
You can define you mapDispatchToProps in a simple way, for now:
import getInfo from './actions'
// ....
function mapDispatchToProps (dispatch) {
return {
dispatchGetInfo(id) {
dispatch(getInfo(id))
}
}
}
Inside your component Info, implement like this:
componentDidMount () {
const { dispatchGetInfo, params: { id } } = this.props
// I PRESUME your `id` is inside the params of react router, feel free to change if not
dispatchGetInfo(id) // => this should call your thunk which will call your consecutive dispatcher
}
EDIT
Your index file should contain this modified line
<Route name="app:info" path="information/:id" component={Info} />

React Native, Navigating a prop from one component to another

handleShowMatchFacts = id => {
// console.log('match', id)
return fetch(`http://api.football-api.com/2.0/matches/${id}?Authorization=565ec012251f932ea4000001fa542ae9d994470e73fdb314a8a56d76`)
.then(res => {
// console.log('match facts', matchFacts)
this.props.navigator.push({
title: 'Match',
component: MatchPage,
passProps: {matchInfo: res}
})
// console.log(res)
})
}
I have this function above, that i want to send matchInfo to matchPage.
I take in that prop as follows below.
'use strict'
import React from 'react'
import { StyleSheet, View, Component, Text, TabBarIOS } from 'react-native'
import Welcome from './welcome.js'
import More from './more.js'
export default class MatchPage extends React.Component {
constructor(props) {
super(props);
}
componentWillMount(){
console.log('mathc facts ' + this.props.matchInfo._bodyInit)
}
render(){
return (
<View>
</View>
)
}
}
All the info I need is in that object - 'this.props.matchInfo._bodyInit'. My problem is that after '._bodyInt', I'm not sure what to put after that. I've tried .id, .venue, and .events, they all console logged as undefined...
You never change props directly in React. You must always change the state via setState and pass state to components as props. This allows React to manage state for you rather than calling things manually.
In the result of your api call, set the component state:
this.setState({
title: 'Match',
component: MatchPage,
matchInfo: res
}
Then pass the state as needed into child components.
render() {
return(
<FooComponent title={this.state.title} matchInfo={this.state.matchInfo} />
);
}
These can then be referenced in the child component as props:
class FooComponent extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
console.log(this.props.title);
console.log(this.props.matchInfo);
// Etc.
}
}
If you need to reference these values inside the component itself, reference state rather than props.
this.state.title;
this.state.matchInfo;
Remember components manage their own state and pass that state as props to children as needed.
assuming you are receiving json object as response , you would need to parse the response before fetching the values.
var resp = JSON.parse(matchInfo);
body = resp['_bodyInit'];

How to access JSON in a React Component?

I have a JSON object from a script tag like so:
<script type="text/json" id="json-data">
{'someData': 'Lorem ipsum...'}
</script>
I would like to be able to pull this information and use it within a React component in my render method.
The issue seems to be that I need to set this to a variable within componentWillMount:
export default MyReactComponent extends Component {
componentWillMount() {
const test = document.getElementById('json-data').innerHTML;
}
render() {
return (
<div>
// This is where I would like to use this data.
</div>
);
}
}
Is this the best way to handle passing this data? If so, how can I access this data within the render method of my component?
Thanks!
Store it in the component's state. The render method should only depend this.state and this.props
At the risk of oversimplifying:
this.props are passed from parent components
this.state is state that is internal to the component
Example
export default MyReactComponent extends Component {
componentDidMount() {
this.setState({
test: JSON.parse(document.getElementById('json-data').innerHTML)
});
}
render() {
return <div>{this.state.test}</div>;
},
getInitialState: function() {
return {test: {}}
}
}