I've been working on this bug for a few hours now, and I think I've narrowed it down to these pieces of code:
// my track detail container:
import { connect } from 'react-redux';
import TrackDetail from './track_detail';
import { selectTracksFromPlaylist } from '../../reducers/selectors';
const mapStateToProps = (state, { playlistId }) => ({
tracks: selectTracksFromPlaylist(state, state.entities.playlists[playlistId])
});
export default connect(mapStateToProps)(TrackDetail);
// my track detail presentational component:
import React from 'react';
import TrackIndexItem from './track_index_item';
const TrackDetail = ({ tracks }) => (
<ul>
{ tracks.map(track => <TrackIndexItem key={track.id} track={track} />) }
</ul>
);
export default TrackDetail;
// my selector that I'm using to select all tracks from a playlist:
export const selectTracksFromPlaylist = (state, playlist) => (
playlist.track_ids.length > 0 ? playlist.track_ids.map(id => state.entities.tracks[id]) : []
);
// the component that's rendering my TrackDetail:
import React from 'react';
import { Route } from 'react-router-dom';
import TrackDetail from '../track/track_detail_container';
class PlaylistDetail extends React.Component {
componentDidMount() {
this.props.requestSinglePlaylist(this.props.match.params.playlistId);
}
render() {
const { playlist } = this.props;
if (!playlist) return null;
return (
<div className="playlist-detail-container">
<div className="playlist-detail-header">
<p>Playlist</p>
<h1>{ playlist.title }</h1>
<p>
Created by <span>{playlist.user}</span> • {playlist.track_ids.length} songs
</p>
</div>
<div className="playlist-detail-tracks">
<TrackDetail playlistId={ playlist.id } />
</div>
</div>
);
}
}
export default PlaylistDetail;
This happens every time I try accessing the page normally (without physically doing a refresh; if I refresh the screen after this error pops up, it works):
Uncaught TypeError: Cannot read property 'id' of undefined
Related
I read relay official docs and followed example.
Now, I slightly change it for practice. I replaced load query with useQueryLoader
but result is, Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
I think it is related to loadRepoQuery() part, but without it, TypeError: null is not an object (evaluating 'preloadedQuery.fetchKey') occurs.
I think it's due to my misunderstanding. Please help me.
// App.js
import './App.css';
import graphql from 'babel-plugin-relay/macro';
import {
usePreloadedQuery,
useQueryLoader,
} from 'react-relay/hooks';
// Define a query
const RepositoryNameQuery = graphql`
query AppRepositoryNameQuery {
repository(owner: "facebook", name: "react") {
name
}
}
`;
function RepoRenderer() {
const [repoQueryRef, loadRepoQuery] = useQueryLoader(RepositoryNameQuery);
loadRepoQuery();
return (
<Repo preloadedQuery={repoQueryRef}/>
)
}
function Repo(props) {
const data = usePreloadedQuery(RepositoryNameQuery, props.preloadedQuery);
return (
<div className="App">
<header className="App-header">
<p>{data.repository.name}</p>
</header>
</div>
);
}
export default function App() {
return (
<RepoRenderer/>
);
}
//
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import { RelayEnvironmentProvider } from 'react-relay';
import relayEnvironment from './relayEnvironment';
import App from './App';
import './index.css';
ReactDOM.render(
<React.StrictMode>
<RelayEnvironmentProvider environment={relayEnvironment}>
<Suspense fallback={'Loading...'}>
<App/>
</Suspense>
</RelayEnvironmentProvider>
</React.StrictMode>,
document.getElementById('root')
);
I kind of solve this problem. Here's code snippet for newbie like me.
See this too.
// index.js
ReactDOM.render(
<RelayEnvironmentProvider environment={relayEnvironment}>
<Suspense fallback={'Loading...'}>
<App />
</Suspense>
</RelayEnvironmentProvider>,
document.getElementById('root')
);
//app.js
import React ,{ useCallback } from 'react';
import './App.css';
import graphql from 'babel-plugin-relay/macro';
import {
usePreloadedQuery,
useQueryLoader,
} from 'react-relay/hooks';
// Define a query
const RepositoryNameQuery = graphql`
query AppRepositoryNameQuery {
repository(owner: "yujong-lee", name: "taggy") {
name
}
}
`;
function Repo({queryRef, refetch}) {
const data = usePreloadedQuery(RepositoryNameQuery, queryRef);
return (
<div className="App">
<header className="App-header">
<p>{data.repository.name}</p>
</header>
</div>
);
}
function App() {
const [repoQueryRef, loadRepoQuery] = useQueryLoader(RepositoryNameQuery);
const refetch = useCallback(() => {
loadRepoQuery();
}, [loadRepoQuery])
if(repoQueryRef !== null) {
return <Repo queryRef={repoQueryRef} refetch={refetch}/>
}
return <button type='button' onClick={() => refetch()}>Fetch</button>
}
export default App;
I have a NextJS prototype live at https://www.schandillia.com/blog. The data displayed is being pulled off a Strapi installation at https://dev.schandillia.com/graphql. I also have the entire codebase up on Github at https://github.com/amitschandillia/proost/web (the frontend).
I'm using an Apollo client to interface with the graphql source. And also a service worker set up to enable PWA.
Everything's working fine except I'm unable to cache the query results at the browser. The service worker is able to cache everything else but the results of Apollo queries. Is there any way this could be enabled? The objective is:
To be able to use some kind of prefetching of query results at the server.
To be able to have the results cached at the browser via service worker.
The three files relevant to this issues are as follows:
Apollo Setup
// web/apollo/index.js
import { HttpLink } from 'apollo-link-http';
import { withData } from 'next-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
// Set up cache.
const cache = new InMemoryCache();
// Configure Apollo.
const config = {
link: new HttpLink({
uri: 'https://dev.schandillia.com/graphql', // Server URL (must be absolute)
}),
cache,
};
export default withData(config);
Query Component
// web/pages/PostsList.jsx
import ReactMarkdown from 'react-markdown';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
import { Fragment } from 'react';
import Typography from '#material-ui/core/Typography';
import CircularProgress from '#material-ui/core/CircularProgress';
const renderers = {
paragraph: props => <Typography variant="body1" gutterBottom {...props} />
};
const PostsList = ({ data: { error, posts } }) => {
let res = '';
if (error) res = (
<Typography variant="subtitle2" gutterBottom>
Error retrieving posts!
</Typography>
);
if (posts && posts.length) {
if (posts.length !== 0) {
// Payload returned
res = (
<Fragment>
{posts.map(post => (
<div>
<Typography variant="display1" gutterBottom>{post.title}</Typography>
<Typography variant="subtitle1" gutterBottom>{post.secondaryTitle}</Typography>
<Typography variant="subtitle2" gutterBottom>Post #{post._id}</Typography>
<ReactMarkdown source={post.body} renderers={renderers} />
</div>
))}
</Fragment>
);
} else {
res = (
// No payload returned
<Typography variant="subtitle2" gutterBottom>
No posts Found
</Typography>
);
}
} else {
res = (
// Retrieving payload
<CircularProgress />
);
}
return res;
};
const query = gql`
{
posts {
_id
title
secondaryTitle
body
}
}
`;
// The 'graphql' wrapper executes a GraphQL query and makes the results
// available on the 'data' prop of the wrapped component (PostsList)
export default graphql(query, {
props: ({ data }) => ({
data,
}),
})(PostsList);
Blog Page
// web/pages/blog.jsx
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import { withStyles } from '#material-ui/core/styles';
import Head from 'next/head';
import Link from 'next/link';
import withRoot from '../lib/withRoot';
import PostsList from '../components/PostsList';
const styles = theme => ({
root: {
textAlign: 'center',
paddingTop: theme.spacing.unit * 20,
},
paragraph: {
fontFamily: 'Raleway',
},
});
class Blog extends PureComponent {
constructor(props) {
super(props);
}
componentDidMount() {
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceWorker.js'); }
}
render() {
const { classes } = this.props;
const title = 'Blog | Project Proost';
const description = 'This is the blog page';
return (
<Fragment>
<Head>
<title>{ title }</title>
<meta name="description" content={description} key="description" />
</Head>
<div className={classes.root}>
<Typography variant="display1" gutterBottom>
Material-UI
</Typography>
<Typography gutterBottom>
<Link href="/about">
<a>Go to the about page</a>
</Link>
</Typography>
<Typography gutterBottom>
<Link href="/blog">
<a>View posts</a>
</Link>
</Typography>
<Button variant="raised" color="primary">
Super Secret Password
</Button>
<Button variant="raised" color="secondary">
Super Secret Password
</Button>
</div>
<PostsList />
</Fragment>
);
}
}
Blog.propTypes = {
classes: PropTypes.shape({
root: PropTypes.string,
}).isRequired,
};
// Posts.propTypes = {
// classes: PropTypes.object.isRequired,
// };
export default withRoot(withStyles(styles)(Blog));
The service worker in question is as follows (redacted for brevity):
// web/offline/serviceWorker.js
const CACHE_NAME = '1b23369032b1541e45cb8e3d94206923';
const URLS_TO_CACHE = [
'/',
'/about',
'/blog',
'/index',
'apple-touch-icon.png',
'browserconfig.xml',
'favicon-16x16.png',
'favicon-194x194.png',
'favicon-32x32.png',
'favicon.ico',
'manifest.json',
];
// Call install event
self.addEventListener('install', (e) => {
e.waitUntil(
caches
.open(CACHE_NAME)
.then(cache => cache.addAll(URLS_TO_CACHE))
.then(() => self.skipWaiting())
);
});
// Call activate event
self.addEventListener('activate', (e) => {
// remove unwanted caches
e.waitUntil(
caches.keys().then((cacheNames) => {
Promise.all(
cacheNames.map((cache) => {
if (cache !== CACHE_NAME) {
return caches.delete(cache);
}
})
);
})
);
});
// Call fetch event
self.addEventListener('fetch', (e) => {
e.respondWith(
fetch(e.request).catch(() => caches.match(e.request))
);
});
Please advise!
I am trying to get user input from antd input.password field. Is it possible?
I didnt see any information on antd docs. I wonder if its possible
I am expecting a string for user input.password since i'll be saving them into local storage
You can always use onChange method like onChange={e => console.log(e.target.value) }
CodeSandbox: https://codesandbox.io/s/5283xn4vo4
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Input, Button } from "antd";
class PasswordToLocalStorage extends React.Component {
state = {
password: undefined
};
render = () => {
return (
<React.Fragment>
<Input.Password
onChange={e => this.setState({ password: e.target.value })}
placeholder="Enter Password"
/>
<Button
onClick={() => {
if (this.state.password) {
localStorage.setItem("password", this.state.password);
alert("saved to local storage: " + localStorage.password);
} else {
alert("There is no password to save");
}
}}
>
Save to localStorage
</Button>
</React.Fragment>
);
};
}
ReactDOM.render(
<PasswordToLocalStorage />,
document.getElementById("container")
);
Here is my createFeatureSelector ts file :
import { State } from "./../../state/app.state";
import { EventState, ModelEvent } from "./../models/model.event";
import { createFeatureSelector, createSelector, ActionReducerMap } from "#ngrx/store";
export interface NewState extends State {
events:EventState
}
const getCalendarFeatureState = createFeatureSelector<EventState>("reducer");
export const getEvents = createSelector(getCalendarFeatureState, state => state.events );
my events shell component ( where i call the getEvents ):
import { Component, OnInit, ChangeDetectionStrategy } from '#angular/core';
import { Store, select } from '#ngrx/store';
import { Observable } from 'rxjs';
import * as fromRoot from "./../../state";
import { CalendarActions, Load } from "./../../state/calendar.actions";
import { ModelEvent, EventState } from "./../../models/model.event";
#Component({
selector: 'calendar-shell',
templateUrl: './calendar-shell.component.html',
changeDetection:ChangeDetectionStrategy.OnPush
})
export class CalendarShellComponent implements OnInit {
events$:Observable<any>
constructor(private store:Store<fromRoot.NewState>) { }
ngOnInit():void{
this.store.dispatch(new Load());
this.events$ = this.store.pipe(select(fromRoot.getEvents)); //getting nothing!!
}
}
template :
<div *ngIf="events$ | async"> //shows nothing!!
<ul>
<li *ngFor="let item of events">{{item.title}}</li>
</ul>
</div>
Any help? thanks in advance. ( ready to provide further details if any )
I update the dom element like this:
<div *ngIf="events$ | async"> //shows nothing!!
<ul>
<li *ngFor="let item of events | async">{{item.title}}</li>
</ul>
</div>
works fine.
I am trying to make a self nested component that uses Angular Material mat-menu. I have a flyoutcomponent that is a wrapper for flyout-menu-item component, that will have a button as a matMenuTrigger for the nested component that will appear as many levels as the FeatureInput.FeatureChoices dictates. FeatureInput is an object that has FeatureChoices that may or may not contain other featurechoices etc N levels deep. Below code does not compile but it should demonstrate what I am doing. Basically I have flyout menu component as a input to a form and I am trying to load a stored answer on a form rather than select new, which I can do easily using the nested component. The desired behavior is that if the user clicks top matMenuTrigger button to open the top menu that it would expand all child menus to the menu item that matches with the FeatureInput.FeatureValue and sets the menu item _highlighted to true. I am using the menuOpen input parameter and ngChanges successfully to find the match(with I a setTimeout which cannot be right). Basically when I console.log this.trigger it is undefined. Ideally in the ngOnChange to the openMenu I would go through all menus and call openMenu on all the triggers but I cannot get access to the matMenuTrigger with ViewChild as the docs say. I get undefined. *-( All help welcome please and thanks.
Here is flyout template component.
<div>
<buttonmat-button [matMenuTriggerFor]="menu.childMenu"
(onMenuOpen)="onMenuOpen()"
(onMenuClose)="onMenuClose()">
<span [innerHTML]="featureInput.Text"></span>
</button>
<app-flyout-menu-item #menu
[featureChoicesObject]="featureInput.FeatureChoices"></app-flyout-menu-item>
</div>
And here is its .ts
import { Component, OnInit, Input, ViewChild } from '#angular/core';
import { MatMenuTrigger } from '#angular/material';
#Component({
selector: 'app-flyout',
templateUrl: './flyout.component.html',
styleUrls: ['./flyout.component.scss']
})
export class FlyoutComponent implements OnInit {
#Input() featureInput: FeatureInput
constructor() { }
ngOnInit() {
}
onMenuOpen() {
this.menuOpen = true;
}
onMenuClose() {
this.menuOpen = false;
}
}
And here is flyout-menu-item template
<mat-menu #childMenu="matMenu" [overlapTrigger]="false">
<span *ngFor="let featureChoice of featureChoices">
<span>
<button mat-menu-item [matMenuTriggerFor]="menu.childMenu">
<span [innerHTML]="featureChoice.Text"></span>
</button>
<app-flyout-menu-item #menu
[menuOpen]="menuOpen"
[featureInput]="featureInput"
[featureChoicesObject]="featureChoice.FeatureChoices"
(onOptionSelected)="someService.SomeMethod($event)"></app-flyout-menu-item>
</span>
<span *ngIf="!featureChoice.FeatureChoices">
<button mat-menu-item (click)="selectOption(featureChoice.ID)" [innerHTML]="featureChoice.Text" value="{{featureChoice.ID}}"></button>
</span>
</span>
</mat-menu>
And here is its .ts
import { Component, OnInit, Input, Output, ViewChild, EventEmitter, OnChanges, SimpleChanges } from '#angular/core';
import { MatMenuTrigger } from '#angular/material';
import { FeatureChoice } from 'app/model/feature-choice';
import { FeatureInput } from 'app/model/feature-input';
#Component({
selector: 'app-flyout-menu-item',
templateUrl: './flyout-menu-item.component.html',
styleUrls: ['./flyout-menu-item.component.scss']
})
export class FlyoutMenuItemComponent implements OnInit{
#ViewChild('menu') public menu;
#ViewChild('childMenu') public childMenu;
#ViewChild(MatMenuTrigger) public trigger: MatMenuTrigger;
#Input() featureInput: FeatureInput;
#Input() featureChoicesObject: FeatureChoice;
#Output() onOptionSelected: EventEmitter<FeatureInput> = new EventEmitter<FeatureInput>();
constructor(public solutionDataService: SolutionDataService) { }
ngOnInit() {
console.log(this.trigger);
}
ngOnChanges(simpleChanges: SimpleChanges) {
if (simpleChanges.menuOpen && simpleChanges.menuOpen.currentValue) {
setTimeout(() => {
// console.log(this.menu);
const itemsArray = this.childMenu.items.toArray();
for (let x = 0; x < itemsArray.length; x++) {
const menuItem = itemsArray[x];
if (this.featureInput.FeatureValue !== '' && menuItem._elementRef.nativeElement.value === this.featureInput.FeatureValue) {
menuItem._highlighted = true;
}
}
}, 1);
}
}
}
this.menuOpen = true;
Perhaps add menuOpen: boolean = false as an attribute at the top of your FlyoutComponent. I don't know where the value of menuOpen is saved.
the menuOpen property relates to the matMenuTrigger.
here's an example:
<button [ngClass]="{'active-icon': trigger.menuOpen}" type="button" mat-
icon-button #trigger="matMenuTrigger" [matMenuTriggerFor]="help">
<mat-icon></mat-icon>
</button>
<mat-menu #help="matMenu">
<div> textId </div>
</mat-menu>