Serverless-stack-com: Comments: Call the List API

Created on 10 Apr 2017  路  46Comments  路  Source: AnomalyInnovations/serverless-stack-com

All 46 comments

Isn't it an antipattern to fetch data in componentWillMount? In this issue tracker, at least from my understanding, people agree that it's discouraged: https://github.com/facebook/react/issues/7671

On top of which, if my memory serves me correct in React 16, cwm may be called multiple times.

You are right. I'll go through and update it. Thanks for pointing it out.

@johnkorzhuk updated - b3984640a56c4b01b7f66d9c5505599b5831d671

Odd, I'm getting a 502 when trying to load the notes
Fetch API cannot load https://u1wagfic06.execute-api.us-west-2.amazonaws.com/prod/notes. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. The response had HTTP status code 502. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Do we need to have Chrome's CORS disabled?

@ryanjcruz I don't think so. This is saying that the API is not returning the Access-Control-Allow-Origin header. Check out this thread, it might help you find the issue - https://github.com/AnomalyInnovations/serverless-stack-com/issues/56#issuecomment-297721513

Just want to clarify something in case I'm missing something. Because userToken is passed through props when I do a reload on the homepage the userToken is now null and thus the note list won't render.

I'm guessing I can't just add userToken to state because the Login component state will be different than Home. Is this a good use case for Redux?

Thanks!

@rogueturnip the userToken is indeed passed through the props and it starts off as null. But when the page is reloaded we get the user token from local storage (this is handled by the AWS JS SDK) and update our components with it. You can see this code here - https://github.com/AnomalyInnovations/serverless-stack-demo-client/blob/call-the-list-api/src/App.js#L38. This means that the note list should render on reload.

There is however a case for redux in the app. If for example you navigate to the create note page from the note list and you hit the browser back button; the note list reloads it's data from the API. This is because the component (and it's state) is unmounted when the route changes. If we were storing the note list in redux separately, then we can simply initialize our component with the state from redux.

Let me know if that makes sense.

@jayair Thanks for the link! Turns out the arn property under authorizer in server.yml for list API isn't indented correctly like what was mentioned there. Will deploy this again with the fix.

@jayair Thanks! I must have something in there wrong. If this was using redux, would it be appropriate to store the whole user detail from Cognito this way? I was thinking of trying to set this up and better learn redux. I could hold all the tokens and also fetch details about the user to hold locally.

@rogueturnip Yeah I would do it that way. Everything that we store in the component's state would go into Redux.

I was doing some more work on this and using not just Redux but also localStorage is a good idea for tokens. This way the login state persists through browser restarts, if that's important.

@rogueturnip Currently, the AWS JS SDK does the localStorage bit for you. That is how we make the login state persist in the demo app. But you can decide to do it on your own as well.

I'm facing with a strange behaviour on notesId. Currently i'm retrieving the list of notes properly (can be seen by console.log(notes) but it does not render href and key... as a result I don't have a link to the note (but if I manually write on the browser bar the link notes/xxxxxx will work perfectly):

Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `Home`. See https://fb.me/react-warning-keys for more information.
    in ListGroupItem (at Home.js:45)
    in Home (at AppliedRoute.js:5)

this is my home.js

  renderNotesList(notes) {
    console.log(notes);
    return [{}].concat(notes).map(
      (note, i) =>
        i !== 0
          ? <ListGroupItem
              key={note.noteId}
              href={`/notes/${note.noteId}`}
              onClick={this.handleNoteClick}
              header={note.content.trim().split("\n")[0]}
            >
              {"Created: " + new Date(note.createdAt).toLocaleString()}
            </ListGroupItem>
          : <ListGroupItem
              key="new"
              href="/notes/new"
              onClick={this.handleNoteClick}
            >
              <h4>
                <b>{"\uFF0B"}</b> Create a new note
              </h4>
            </ListGroupItem>
    );
  }

any clue about this matter?

@designpressure It could be the format notes are in. What does console.log(notes) show?

[{...}]
0 :
attachment : null
content : "new note 1"
createdAt : 1505999501488
notesId : "68af9b00-9ece-11e7-gggg-eeeeee"
userId : "eu-central-1:xxxx-yyy-zzz-www-ffff"

@designpressure I'm not sure if it's a typo but the returned data has notesId but in the code we look for noteId.

Oops, I've used throughout the examples notesId instead of noteId... solved, thanks

The only thing confusing for me in this chapter is this line.

return [{}].concat(notes).map(

If I understand correctly you need to return an array of objects so it can show the notes, but why is concat method needed? couldn't we just use map without concat?

@x11joe It's a bit of a trick I guess. We want to display the Create a new note button as the first item on the list. So we always start with an array with one empty item in it. Inside the map we treat the item at index 0 as a special case and render a button.

@jayair what if I needed to lay one route open to not-signed in users, for example render example notes created by the admin with his access permissions? How can I bypass the sign-in functionality and send a get request to fetch data from a DynamoDB table? Do I have to create a new Iam user in order to achieve that?

@akolybelnikov You'd need to create an API without an authorizer - https://github.com/AnomalyInnovations/serverless-stack-demo-api/blob/master/serverless.yml#L69. And in the awsLib.js for the React portion, just skip signing the request and directly make a fetch call - https://github.com/AnomalyInnovations/serverless-stack-demo-client/blob/master/src/libs/awsLib.js#L17.

Hi! I'm working through your tutorial and have hit a snag. I've adapted the tutorial for the project I'm working on so there are a few extra functions and db tables. I don't think that's where the problem stems from after following your debugging instructions but wanted to mention that in case it is the problem. Essentially, I can invoke the api gateway fns from the terminal with 'sls deploy' and get the correct response. However, when I try to do the same from the react app, it comes through cloudwatch but doesn't pass into the app. The result var in componentWIllMount after invokeapig is "{}".

I have quadruple checked that all of my files match what is in the tutorial. Followed the error in cloudwatch and it returns the correct information (garden aka note in the tutorial) with a 200 status. I have stepped through the entire app in debugger and can't find where the response body comes in. I can successfully post a new garden to the db.

The awsLibs invokespig() returns this result:

Response聽{type: "cors", url: "https://API_GATEWAY.execute-api.us-east-1.amazonaws.com/prod/gardens", redirected: false, status: 200, ok: true,聽鈥
The body attr is a ReadableStream.

My Notes/gardens page:

class Gardens extends Component {
  static propTypes = {
    match: PropTypes.shape({
      path: PropTypes.string
    }).isRequired,
    isAuthenticated: PropTypes.bool.isRequired
  };

  state = {
    chamberId: 1,
    chamberData: [],
    growingPlants: [],
    chambers: [],
    isLoading: true
  };

  async componentDidMount() {
    console.log('componentDidMount monitor');

    try {
      const growingResults = await this.growingPlants();
      this.setState({growingPlants: growingResults});
    } catch(e) {
      console.log(e);
    }
    this.setState({ isLoading: false });
  }

growingPlants = () => {
    console.log('get growing plants from db');
    return invokeApig({ path: "/gardens" });  // eslint-disable-line
  }

  renderGardensList(chambers) {
    return [{}].concat(gardens).map(
      (garden, i) =>
        i !== 0
          ? <ListGroupItem
              key={garden.chamberId}
              href={`/gardens/${gardens.chamberId}`}
              onClick={this.handleNoteClick}
              header={gardens.chamberName}
            >
              {"Created: " + new Date(garden.createdAt).toLocaleString()}
            </ListGroupItem>
          : <ListGroupItem
              key="new"
              href="/NewGrow"
              onClick={this.handleChamberClick}
            >
              <h4>
                <b>{"\uFF0B"}</b> Start a new Garden
              </h4>
            </ListGroupItem>
    );
  }

  handleGardenClick = event => {
    event.preventDefault();
    this.props.history.push(event.currentTarget.getAttribute("href"));
  }

  renderGardens() {
   return (
     <div className="gardens">
       <PageHeader>Your Gardens</PageHeader>
       <ListGroup>
         {!this.state.isLoading && this.renderGardensList(this.state.growingPlants)}
       </ListGroup>
     </div>
   );
 }

renderLander() {..}

render() {
  return (
      <div className="monitor container">
        {this.props.isAuthenticated ? this.renderChambers() : this.renderLander()}
      </div>
)}};

My AWS permissions for the auth_role:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::MY_BUCKET/${cognito-identity.amazonaws.com:sub}*",
                "arn:aws:s3:::ANOTHER_BUCKET/${cognito-identity.amazonaws.com:sub}*",
                "arn:aws:s3:::BUCKET_FOR_MY_APP/${cognito-identity.amazonaws.com:sub}*",
                "arn:aws:s3:::DEPLOYMENT_BUCKET/${cognito-identity.amazonaws.com:sub}*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:*:MY_API_GATEWAY_ID/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:*"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:*:table/*"
            ]
        }
    ]
}

Thanks in advance for the help and this tutorial has saved me so many hours of confusion. It's seriously one of the best I've tried 馃

@NYCSwan Hmm so you are able to write to the db but when you read from it you get an empty result with a 200 status?

It might be that the table you are writing to (with the user id) and the one you are fetching from is not the same?

@jayair You got it. I can write to the db but it returns an empty hash when I try to get or list items from a table. I found another bug when I try to get one single item. I get a 403 preflight cors issue or TypeError: body stream already read error. I'm fetching and reading from the same table.

Get Method

  getGarden = () => {
    console.log('get indiv garden');
    // debugger
    return invokeApig({ path: `/gardens/${TIMESTAMP}`});
  };

I'm watching the actual lambdas & API-Gateway-Execution-Logs

logs from /gardens/{id}:

...
message: 'The provided key element does not match the schema',
code: 'ValidationException',
time: 2018-02-09T22:42:51.694Z,
requestId: 'CQ2Q628G3DNR2L6MH8NHFDCV3RVXXXXO5AEMVJF66Q9ASUAAJG',
statusCode: 400,
retryable: false,
retryDelay: 5.994444151965716 }
2018-02-09T22:42:51.703Z    8f1bc406-0dea-11e8-b61e-d167a9b81557
{
    "status": false
}

END RequestId: 8f1bc406-0dea-11e8-b61e-d167a9b81557
REPORT RequestId: 8f1bc406-0dea-11e8-b61e-d167a9b81557  Duration: 155.51 ms Billed Duration: 200 ms Memory Size: 1024 MB    Max Memory Used: 37 MB  
START RequestId: ca9dfd41-0dea-11e8-bb46-df0bb292ce1c Version: $LATEST
2018-02-09T22:44:30.899Z    ca9dfd41-0dea-11e8-bb46-df0bb292ce1c
{
    "gardenId": "049d18e0-0d96-11e8-83ad-a9620d2006bc",
    "chamberId": "Chamber 2",
    "climateId": "2",
    "userId": "us-east-1:1a372091-157f-48e9-bc27-9d664a7f7718",
    "plantName": "Kale",
    "createdAt": 1518179860859,
    "plantRecipeId": "3"
}

END RequestId: ca9dfd41-0dea-11e8-bb46-df0bb292ce1c
REPORT RequestId: ca9dfd41-0dea-11e8-bb46-df0bb292ce1c  Duration: 88.78 ms  Billed Duration: 100 ms Memory Size: 1024 MB    Max Memory Used: 37 MB  

From the api_gateway logs, Is Method: OPTIONS the issue? In the same minute/call, there are two logs to /gardens path, the top is METHOD: GET, the second is OPTIONS. I can't find anything that mentions this.

(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) HTTP Method: OPTIONS, Resource Path: /gardens
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Method request path:
{}
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Method request query string:
{}
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Method request headers: {Accept=*/*, CloudFront-Viewer-Country=US, CloudFront-Forwarded-Proto=https, CloudFront-Is-Tablet-Viewer=false, origin=http://localhost:3000, CloudFront-Is-Mobile-Viewer=true, Referer=http://localhost:3000/controls/ExistingGrow, User-Agent=Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1, X-Forwarded-Proto=https, CloudFront-Is-SmartTV-Viewer=false, Host=az9ohrt4i7.execute-api.us-east-1.amazonaws.com, Accept-Encoding=gzip, deflate, br, dnt=1, access-control-request-method=GET, X-Forwarded-Port=443, X-Amzn-Trace-Id=Root=1-5a7e1adf-35b2160342d43365353cef70, Via=2.0 f564d0c1e4568b2b822f986a309f4114.cloudfront.net (CloudFront), access-control-request-headers=authorization,content-type,x-amz-date,x-amz-security-token, X-Amz-Cf-Id=j8QmzcWlIRC2h5fsRq_L237a65ZYHerqsN-kMqNg2Orxub0Pd3jO-Q==, X-Forwarded-For=68.174.1.162, 204.246.168.11, Accept-Language=en-US,en;q=0.9, CloudFront-Is-Desktop-Viewer=false}
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Method request body before transformations:
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Received response. Integration latency: 0 ms
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Endpoint response body before transformations:
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Endpoint response headers:
{}
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Method response body after transformations:
(2b11aa94-0de5-11e8-9e91-69a8d6edbe9f) Method response headers: {Access-Control-Allow-Origin=*, Access-Control-Allow-Credentials=false, Access-Control-Allow-Methods=OPTIONS,GET,POST, Access-Control-Allow-Headers=Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent, Content-Type=application/json}
(8e8be017-0dea-11e8-8f95-0f0011862800) Method request body before transformations:
(8e8be017-0dea-11e8-8f95-0f0011862800) Received response. Integration latency: 0 ms
(8e8be017-0dea-11e8-8f95-0f0011862800) Endpoint response body before transformations:
(8e8be017-0dea-11e8-8f95-0f0011862800) Endpoint response headers:
{}
(8e8be017-0dea-11e8-8f95-0f0011862800) Method response body after transformations:
(8e8be017-0dea-11e8-8f95-0f0011862800) Method response headers: {Access-Control-Allow-Origin=*, Access-Control-Allow-Credentials=false, Access-Control-Allow-Methods=OPTIONS,GET,POST, Access-Control-Allow-Headers=Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent, Content-Type=application/json}
(8e8be017-0dea-11e8-8f95-0f0011862800) Successfully completed execution
(8e8be017-0dea-11e8-8f95-0f0011862800) Method completed with status: 200

The /gardens log:

2018-02-09T22:42:50.711Z    8e95f28a-0dea-11e8-85af-757fe33d1b3a    [
{
    "gardenId": "049d18e0-0d96-11e8-83ad-a9620d2006ca",
    "chamberId": "Chamber 1",
    "climateId": "2",
    "userId": "us-east-1:1a372091-157f-48e9-bc27-9d664a7f7718",
    "plantName": "Cilantro",
    "createdAt": 1518179860846,
    "plantRecipeId": "2"
}
,
{
    "gardenId": "049d18e0-0d96-11e8-83ad-a9620d2006bc",
    "chamberId": "Chamber 2",
    "climateId": "2",
    "userId": "us-east-1:1a372091-157f-48e9-bc27-9d664a7f7718",
    "plantName": "Kale",
    "createdAt": 1518179860859,
    "plantRecipeId": "3"
}
,
{
    "gardenId": "47aede70-0dbe-11e8-9a81-b7b8232688ef",
    "chamberId": "Chamber 3",
    "climateId": "2",
    "userId": "us-east-1:1a372091-157f-48e9-bc27-9d664a7f7718",
    "plantName": "Cilantro",
    "createdAt": 1518197153239,
    "plantRecipeId": "2"
}
]
END RequestId: 8e95f28a-0dea-11e8-85af-757fe33d1b3a

@NYCSwan This The provided key element does not match the schema is not good. It sounds like the table has not been set up properly. What does the table structure look like right now? And the query you are using to get an object?

@mbahfauz Can you please not post the same issue in multiple threads? Also, for large code samples link to a Gist from elsewhere.

Okay @jayair thanks for your supports.

Help Again please - When I'm trying to list the notes -> getting an error - TypeMismatchError.
thank you :)

@nelsoftcom Can I see a screenshot of the error?

nelsoftcom_error
@jayair Please see the error. Thanks

Hello jay, thanks for your support before, I can render image on list now. But I still got little problem. How can I get image url of every note on home page (List notes). Because I only get name of file on database, But still confuse how to get those image url on list notes.

@nelsoftcom I think we need a bit more than that to debug it. Can you try it on Chrome and look at what shows up in the console?

@mbahfauz If you can render it in the notes, why not return it on the list page and do the same?

I had try to render it with {Storage.get('notes.attachment')} But I always got error, this is not react component. If you try to render array, xxxxx

@mbahfauz Yeah you can't directly use that in your component. It returns a promise and you need to set it after it returns. Notice what we do with it here - https://github.com/AnomalyInnovations/serverless-stack-demo-client/blob/master/src/containers/Notes.js#L31.

any idea why when I click on a note it takes me to the right ID page in the URL and I can see the console.log of the object but it always says page not found like its not loading the notes when I click them but its loading them fine in the list and creating them fine, just not loading the page when I click on a note, any idea what might be doing this? No errors in the console atm.

@btotharye Does it fail to load when you go to the URL directly?

I just redid it and it's fine not sure what the issue was, thanks

Anyone know how to call notes for not authorize user?
I had try with this code:
async componentDidMount() { const coins = await this.coins(); this.setState({ coins }); this.setState({ isLoading: false }); }

But got not creditentials.

Thanks

@mbahfauz Sorry what didn't work for you? Are you trying to call an API that doesn't need authentication?

@jayair Yap that is true bro. I want to call the note api for public who doesn't need authentication.m

@mbahfauz You need to turn off authentication here - https://github.com/AnomalyInnovations/serverless-stack-demo-api/blob/master/serverless.yml#L73.

And you would need to pass in the user id for the notes you are fetching.

Got it,
So the code would be like this?

list: # Defines an HTTP API endpoint that calls the main function in list.js # - path: url path is /notes # - method: GET request handler: list.main events: - http: path: notes method: get cors: true authorizer: turn_off

Or I should use other way?

Thanks

@mbahfauz You can just skip the authorizer option.

is there a way to list all the notes even if it is created by other user?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jayair picture jayair  路  9Comments

jayair picture jayair  路  20Comments

AcidLeroy picture AcidLeroy  路  12Comments

jayair picture jayair  路  11Comments

jayair picture jayair  路  21Comments