I have the following query:
query {
viewer{
place(id: "1a4871311fe990d6d94cf1eed9fd65008856e118b790e7dcf728d86bc3aef7ec"){
name
}
}
}
which of course works correctly on GraphiQL. I would like to use this query in a Relay container so I've created a RootContainer:
<RootContainer
Component={PlaceDetailsComponent}
route={new PlaceDetailsRoute({placeID: this.props.id})}
renderFetched={(data) => {
console.log('data: ', data);
return (
<PlaceDetailsComponent {...this.props} {...data}/>
)
} }
renderLoading={() => <ProgressBar visible={true} />}/>
which successfully fetches the data but all I can see on console is this:
data: { placeID: '1a4871311fe990d6d94cf1eed9fd65008856e118b790e7dcf728d86bc3aef7ec',
viewer:
{ __dataID__: 'Vmlld2VyLXs6aWQ9PiJ2aWV3ZXIifQ==',
__fragments__: { '1::client': [ {} ] } } }
So I checked out what's actually sent to server and what's received and all seems right to me:
Here is my route:
import Relay, {
Route
} from 'react-relay';
class PlaceDetailsRoute extends Route {
static queries = {
viewer: () => Relay.QL`
query {
viewer
}
`
}
static routeName = 'PlaceDetailsRoute'
}
export default PlaceDetailsRoute;
and here is my fragment:
Relay.createContainer(PlaceDetailsContainer, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
place(id: "1a4871311fe990d6d94cf1eed9fd65008856e118b790e7dcf728d86bc3aef7ec") {
name,
}
}
`
}
});
Any suggestions what should I change? Thanks in advance for any help!
That's actually expected behavior. The Relay documentation of renderFetched has a note:
Even though we have access to the data object in renderFetched, the actual data is intentionally opaque. This prevents the renderFetched from creating an implicit dependency on the fragments declared by Component.
Hope this clears up your confusion.
Related
Im calling a service from one of my component, via the assignGirdle function. While this service is being executed, I get the above error, but when I check in the network tab and click on the API call, in response in can see the data.
Note girdleNew is of type any. Also this function I'm calling on ngOnInit()
assignGirdle() {
this.diamondSearchService.getDistinctValues()
.subscribe((data) => {
this.girdleNew = data;
}, error => {
this.alertify.error(error);
});
}
The service:
getDistinctValues() {
return this.authHttp
.get(this.baseUrl + 'distinct/girdle')
.map(response => response.json())
.catch(this.handleError);
}
Check if you have imported service in your component. Below is my data service import.
import { DataService } from '../data.service';
Create object of the same in your component:
constructor(public data: DataService) { }
In my component I have called this getUser() method which is defined in DataService.
this.data.getUser(email).subscribe( data => {
if(data.length > 0){
console.log(data);
}
});
Below is my service:
getUser(email){
// definition
}
This works for me.
My async actions tend to look something like this:
anAsyncAction: process(function* anAsyncAction() {
self.isLoading = true;
const service = getEnv<IMyMarksPageStoreEnv>(self).myService;
try
{
yield service.doSomething();
}
finally
{
self.isLoading = false;
}
}),
Then I let the view handle what toasts to show:
toaster = Toaster.create({
position: Position.TOP
});
render() {
return <button disabled={this.props.store.isLoading} onClick={this.handleButtonClicked}>Do Async Thing</button>
}
handleButtonClicked = () => {
const store = this.props.store;
try
{
await store.anAsyncAction();
toaster.show({ message: "Saved!", intent: Intent.SUCCESS });
}
catch(e)
{
toaster.show({ message: "Whoops an error occured: "+e, intent: Intent.DANGER });
}
}
But im starting to think that the toasts handling should live in the async try-catch of the store and not the view, but then its mixing business logic with view, so im not sure.
Any suggestions?
I'd argue that messages are part of the application.
In my app I have an array at root level
export default types.model('AppStore', {
....
flashMessages: types.optional(types.array(FlashMessage), []),
})
.actions((self) => {
/* eslint-disable no-param-reassign */
const addFlashMessage = (message) => {
self.flashMessages.push(FlashMessage.create({
...message,
details: message.details.toString(),
}));
};
function addErrorMessage(text, details = '') {
addFlashMessage({ type: 'error', text, details });
}
function addInfoMessage(text, details = '') {
addFlashMessage({ type: 'info', text, details });
}
function addSuccessMessage(text, details = '') {
addFlashMessage({ type: 'success', text, details });
}
Then
#inject('appStore')
#observer
class App extends Component {
render() {
const app = this.props.appStore;
return (
<BlockUI tag="div" blocking={app.blockUI}>
<Notifications messages={app.unseenFlashMessages} />
...
And in a component
this.props.appStore.addSuccessMessage(`User ${name} saved`);
This will also allow you to implement a 'last 5 messages' sort of thing which might be useful if you've missed a to
Guess that's not specific to mobx or mobx-state-tree, but I'd probably consider adding a dedicated NotificationStore to the picture. Service try/catch/finally would be one producer of notifications with a source of service, another might be a fetch/xhr wrapper with a source of transport.
It would be up to the business logic to decide how to present/handle those.
I'm trying to find out how/if it is possible to trigger a refresh in a Relay Modern RefreshContainer without passing (new) variables?
I’m looking for the best way to implement the good ol’ pull-to-refresh on a React Native list, that should simply refetch the original query - no variables needed?
According to docs (https://facebook.github.io/relay/docs/api-cheatsheet.html) this should be possible using
this.props.relay.refetch({}, callback, {force: true})
but I get an error saying "undefined is not an object ('evaluating taggedNode.modern')"
The query works just fine if I use a plain old FragmentContainer instead, but I'd just like a simple pull-to-refresh functionality :-)
EDIT
Adding more code for clarity. Also updated call to reflect change to API that includes render variables, passing null
class HistoryList extends React.PureComponent<void, Props, State> {
state = { refreshing: false };
_renderRow = ({ item }) => {
return <HistoryListItem item={item.node} />;
};
_renderHeader = ({ section }) => {
return (
<Text style={[cs.breadText, _styles.sectionHeader]}>
{section.title}
</Text>
);
};
_onRefresh = () => {
this.setState({ refreshing: true });
this.props.relay.refetch({}, null, this._onRefreshDone(), { force: true });
};
_onRefreshDone = () => {
this.setState({ refreshing: false });
};
_sortDataIntoSections = (edges: Array<Node>) => {
return _.chain(edges)
.groupBy(element => {
return moment(element.node.huntDate).format('MMMM YYYY');
})
.map((data, key) => {
return { title: key, data: data };
})
.value();
};
render() {
return (
<View style={_styles.container}>
<SectionList
renderSectionHeader={this._renderHeader}
sections={this._sortDataIntoSections(
this.props.entries.allJournalEntries.edges
)}
renderItem={this._renderRow}
keyExtractor={(item, index) => item.node.__id}
onRefresh={this._onRefresh}
refreshing={this.state.refreshing}
/>
</View>
);
}
}
export default createRefetchContainer(
HistoryList,
graphql`
fragment HistoryList_entries on Viewer {
allJournalEntries(orderBy: huntDate_DESC) {
count
edges {
node {
huntDate
...HistoryListItem_item
}
}
}
}
`
);
It seems that the arguments of this.props.relay.refetch has been change to refetch(refetchVariables, renderVariables, callback, options) (in https://facebook.github.io/relay/docs/refetch-container.html) and the cheetsheet has been out-of-date.
I'm not sure that in which version that this has change, but you can give it a try and change your code to:
this.props.relay.refetch({}, null, callback, {force: true})
A solution has been found by robrichard at github.
I was missing the third argument for the RefetchContainer, which is the query to execute on refetch. This, combined with the suggestion from #zetavg was what was needed.
The exported module now looks like this:
export default createRefetchContainer(
HistoryList,
{
entries: graphql`
fragment HistoryList_entries on Viewer {
allJournalEntries(orderBy: huntDate_DESC) {
count
edges {
node {
huntDate
...HistoryListItem_item
}
}
}
}
`
},
graphql`
query HistoryListRefetchQuery {
viewer {
...HistoryList_entries
}
}
`
);
I have both solution applied (query for refetch and relay refetch call).
Refetch query
(do not pay attention at fact, that I didn't specify a component for container, there is special decorator in our code base for it):
{
viewer: graphql`
fragment RatingSchoolsTableContainer_viewer on Viewer {
rating {
schools {
uid
masterUrl
paidOrderSum
paidOrderCount
averageReceipt
}
}
}
`,
},
graphql`
query RatingSchoolsTableContainer_RefetchQuery {
viewer {
...RatingSchoolsTableContainer_viewer
}
}
`,
And relay call:
this.props.relay?.refetch({}, null, () => {}, {force: true})
There is no re-render anyway, but I have new response from server in network.
Here is my code on my mutation
class CreateUserMutation extends Relay.Mutation {
static fragments = {
user: () => Relay.QL`
fragment on User{
id
email
}
`
}
getMutation () {
return Relay.QL`mutation{ createUser }`
}
getVariables() {
return {
email: 'bondan#something.com'
}
}
getFatQuery() {
return Relay.QL`
fragment on CreateUserPayload {
user{
email
}
}
`
}
getConfigs () {
// console.log('getConfigs props', this.props);
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
user: this.props.user.id
}
}]
}
}
and this is my implementation on UI
componentWillMount () {
let mutation = new CreateUserMutation();
Relay.Store.commitUpdate(mutation);
}
its a simple experiment apps just to check if my mutation is working when the app load
however the i keep getting this error
user is not defined
Please help me what is wrong with the mutation code?
I have a component defined like this. fetchBrands is a redux action.
class Brands extends Component {
componentWillMount() {
this.props.fetchBrands();
}
render() {
return (
// jsx omitted for brevity
);
}
}
function mapStateToProps(state) {
return { brands: state.brands.brands }
}
export default connect(mapStateToProps, { fetchBrands: fetchBrands })(Brands);
This component is wrapped in a Higher Order Component that looks like this:
export default function(ComposedComponent) {
class Authentication extends Component {
// kind if like dependency injection
static contextTypes = {
router: React.PropTypes.object
}
componentWillMount() {
if (!this.props.authenticated) {
this.context.router.push('/');
}
}
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) {
this.context.router.push('/');
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return { authenticated: state.auth.authenticated };
}
return connect(mapStateToProps)(Authentication);
}
Then, in my router config, I am doing the following:
<Route path="brands" component={requireAuth(Brands)} />
If the auth token doesn't exist in local storage, I redirect to a public page. However, the fetchBrands action is still being called which is firing off an ajax request. The server is rejecting it because of the lack of an auth token, but I don't want the call to even be made.
export function fetchBrands() {
return function(dispatch) {
// ajax request here
}
}
I could wrap the ajax request with an if/else check, but that isn't very DRY considering all the action functions I'd need to do this in. How can I implement something DRY to prevent the calls if auth fails at the browser level?
You should write a middleware for that. http://redux.js.org/docs/advanced/Middleware.html
Since you are using axios I would really recommend using something like https://github.com/svrcekmichal/redux-axios-middleware
Then you can use a middleware like
const tokenMiddleware = store => next => action => {
if (action.payload && action.payload.request && action.payload.request.secure) {
const { auth: { isAuthenticated } } = store.getState()
const secure = action.payload.request.secure
if (!isAuthenticated && secure) {
history.push({ pathname: 'login', query: { next: history.getCurrentLocation().pathname } })
return Promise.reject(next({ type: 'LOGIN_ERROR', ...omit(action, 'payload') }))
}
}
return next(action)
}
action.payload.request.secure is a custom prop I use to indicate the request needs authentication.
I this case I also redirect using history from react-router but you can handle this to dispatch another action (store.dispatch({ whatever })) and react as you need