(continue)

to test, runserver and npm start, access localhost:30
send something
image.png
meet status 403, and the cause is “authentication credentials were not provided”

to fix this problem,

  • comment a SessionAuthentication line /tweets/views.py
    1. @api_view(['POST']) # http method the client === POST
    2. # @authentication_classes([SessionAuthentication])
    3. @permission_classes([IsAuthenticated])
    4. def tweet_create_view(request, *args, **kwargs):
    5. serializer = TweetCreateSerializer(data=request.POST)
    6. if serializer.is_valid(raise_exception=True): # send back what the error is
    7. serializer.save(user=request.user)
    8. return Response(serializer.data, status=201)
    9. return Response({}, status=400)
    to test again, runserver and npm start, access localhost:30
    send something
    image.png
    seems like new tweets are created, but content is null.

Handling New Tweet:

send content:

to trace the problem that the content is not sent.

  • add console log to

    • /twittme-web/src/lookup/lookup.js ```javascript function lookup(method, endpoint, callback, data) { //…

    xhr.onload = function () { callback(xhr.response, xhr.status); }; xhr.onerror = function (e) { console.log(e); callback({ message: “The request was an error” }, 400); }; console.log(jsonData) //new xhr.send(jsonData); }

    1. - twittme-web/src/tweets/components.js
    2. ```javascript
    3. export function TweetsComponent(props) {
    4. //...
    5. const handleSubmit = (event) => {
    6. event.preventDefault();
    7. const newVal = textAreaRef.current.value;
    8. let tempNewTweets = [...newTweets];
    9. console.log("new value", newVal); //new
    10. createTweet(newVal, (response, status) => {
    11. console.log(response, status); //new
    12. if (status === 201) {
    13. tempNewTweets.unshift(response);
    14. } else {
    15. console.log(response);
    16. alert("An error occured please try again");
    17. }
    18. });
    19. //...
    20. };
    21. //...
    22. }

    after this, runserver and npm start, localhost:30 to send something:
    image.png
    so that content was not coming through.

  • print the received POST request in /tweets/views.py

    1. @api_view(['POST']) # http method the client === POST
    2. # @authentication_classes([SessionAuthentication])
    3. @permission_classes([IsAuthenticated])
    4. def tweet_create_view(request, *args, **kwargs):
    5. print(request.POST) # new
    6. # ...
    7. return Response({}, status=400)

    send something and see the terminal
    image.png
    not getting any request either.

so change POST into data here:

  1. @api_view(['POST']) # http method the client === POST
  2. # @authentication_classes([SessionAuthentication])
  3. @permission_classes([IsAuthenticated])
  4. def tweet_create_view(request, *args, **kwargs):
  5. # print(request.POST)
  6. print(request.data)
  7. # serializer = TweetCreateSerializer(data=request.POST)
  8. serializer = TweetCreateSerializer(data=request.data)
  9. # ...
  10. return Response({}, status=400)

send something again
image.png
successful, even though it’s not a correct update for reactjs

update right after sent:

in the previous step, we need to refresh and make new tweets shown

  • to fix this, change the position of setNewTweets() in /twittme-web/src/tweets/components.js

    1. export function TweetsComponent(props) {
    2. //...
    3. const handleSubmit = (event) => {
    4. //...
    5. createTweet(newVal, (response, status) => {
    6. console.log(response, status);
    7. if (status === 201) {
    8. tempNewTweets.unshift(response);
    9. setNewTweets(tempNewTweets); //new
    10. } else {
    11. console.log(response);
    12. alert("An error occured please try again");
    13. }
    14. });
    15. //setNewTweets(tempNewTweets);
    16. textAreaRef.current = ""; //after the submission, clear the textarea
    17. };
    18. //...
    19. }

    to trigger the update quicker
    and new tweets will be shown immediately after sent.

then create a handleBackendUpdate constant to replace some lines before and increase code reuse.

  1. export function TweetsComponent(props) {
  2. //...
  3. const handleBackendUpdate = (response, status) => {
  4. //backend api response handler
  5. let tempNewTweets = [...newTweets];
  6. if (status === 201) {
  7. tempNewTweets.unshift(response);
  8. setNewTweets(tempNewTweets);
  9. } else {
  10. console.log(response);
  11. alert("An error occured please try again");
  12. }
  13. }
  14. const handleSubmit = (event) => {
  15. event.preventDefault();
  16. const newVal = textAreaRef.current.value;
  17. //let tempNewTweets = [...newTweets];
  18. console.log("new value", newVal);
  19. /*
  20. createTweet(newVal, (response, status) => {
  21. console.log(response, status);
  22. if (status === 201) {
  23. tempNewTweets.unshift(response);
  24. setNewTweets(tempNewTweets);
  25. } else {
  26. console.log(response);
  27. alert("An error occured please try again");
  28. }
  29. });
  30. */
  31. createTweet(newVal, handleBackendUpdate)
  32. };
  33. //...
  34. }

API Methods in React:

to reduce ambiguity:

  • rename /twittme-web/src/lookup/lookup.js into components.js
  • change import in /twittme-web/src/lookup/index.js ```javascript //import {createTweet, loadTweets} from ‘./lookup’ import {createTweet, loadTweets} from ‘./components’

//…

  1. <a name="XYw3z"></a>
  2. #### export and new lookup:
  3. - rename and export lookup() in /twittme-web/src/lookup/components.js
  4. ```javascript
  5. //function lookup(method, endpoint, callback, data) {
  6. export function backendLookup(method, endpoint, callback, data) {
  7. //...
  8. }
  • create a new file lookup.js in /twittme-web/src/tweets/
  • cut 2 functions in /twittme-web/src/lookup/components.js and paste into /twittme-web/src/tweets/lookup.js ```javascript import { backendLookup } from “../lookup”;

export function createTweet(newTweet, callback) { backendLookup(“POST”, “/tweets/create/“, callback, { content: newTweet }); }

export function loadTweets(callback) { backendLookup(“GET”, “/tweets/“, callback); }

  1. - apply backendLookup in /twittme-web/src/lookup/index.js
  2. ```javascript
  3. //import {createTweet, loadTweets} from './components'
  4. import { backendLookup } from "./components";
  5. export {
  6. /*
  7. createTweet,
  8. loadTweets
  9. */
  10. backendLookup,
  11. };
  • becasue refered lookup was changed, change import in /twittme-web/src/tweets/components.js
    1. //import { createTweet, loadTweets } from "../lookup";
    2. import { createTweet, loadTweets } from "./lookup";

mapping to the right function:

  • rename appropriately in /twittme-web/src/tweets/lookup.js ```javascript import { backendLookup } from “../lookup”;

//export function createTweet(newTweet, callback) { export function apiTweetCreate(newTweet, callback) { backendLookup(“POST”, “/tweets/create/“, callback, { content: newTweet }); }

//export function loadTweets(callback) { export function apiTweetList(callback) { backendLookup(“GET”, “/tweets/“, callback); }

  1. - and
  2. ```javascript
  3. //import { createTweet, loadTweets } from "./lookup";
  4. import { apiTweetCreate, apiTweetList } from "./lookup";
  5. export function TweetsComponent(props) {
  6. //...
  7. const handleSubmit = (event) => {
  8. //...
  9. //createTweet(newVal, handleBackendUpdate);
  10. apiTweetCreate(newVal, handleBackendUpdate);
  11. //...
  12. };
  13. //...
  14. }
  15. export function TweetsList(props) {
  16. //...
  17. useEffect(() => {
  18. if (tweetsDidSet === false) {
  19. //const myCallback = (response, status) => {
  20. const handleTweetListLookup = (response, status) => {
  21. if (status === 200) {
  22. setTweetsInit(response);
  23. setTweetsDidSet(true);
  24. } else {
  25. alert("There was an error");
  26. }
  27. };
  28. //loadTweets(myCallback);
  29. apiTweetList(handleTweetListLookup);
  30. }
  31. }, [tweetsInit, tweetsDidSet, setTweetsDidSet]);
  32. //...
  33. }

Tweet Action Btn:

  • create a apiTweetAction() method in /twittme-web/src/tweets/lookup.js ```javascript import { backendLookup } from “../lookup”;

export function apiTweetCreate(newTweet, callback) { backendLookup(“POST”, “/tweets/create/“, callback, { content: newTweet }); }

export function apiTweetList(callback) { backendLookup(“GET”, “/tweets/“, callback); }

export function apiTweetAction(tweetId, action, callback) { //new const data = { id: tweetId, action: action }; backendLookup(“POST”, “/tweets/action/“, callback, data); }

  1. - import and use it in /twittme-web/src/tweets/components.js
  2. ```javascript
  3. //import { apiTweetCreate, apiTweetList } from "./lookup";
  4. import { apiTweetCreate, apiTweetList, apiTweetAction } from "./lookup";
  5. //...
  6. export function ActionBtn(props) {
  7. //...
  8. const handleActionBackendEvent = (response, status) => { //new
  9. console.log(response, status)
  10. if (action.type === "like") {
  11. if (userLike === true) {
  12. setLikes(likes - 1);
  13. setUserLike(false);
  14. } else {
  15. setLikes(likes + 1);
  16. setUserLike(true);
  17. }
  18. }
  19. };
  20. const handleClick = (event) => {
  21. event.preventDefault();
  22. apiTweetAction(tweet.id, action.type, handleActionBackendEvent); //new
  23. /*
  24. if (action.type === "like") {
  25. if (userLike === true) {
  26. setLikes(likes - 1);
  27. setUserLike(false);
  28. } else {
  29. setLikes(likes + 1);
  30. setUserLike(true);
  31. }
  32. }
  33. */
  34. };
  35. //...
  36. }

to test, runserver
image.png
I kept clicking id 100 on “likes” and “unlike” repeatedly, and the right console log has shown the number of “likes” get.

fix 2 “likes”:

Here’s a problem, if I “like” a tweet and then refresh, “like” again:
image.png
It will show 2 “likes”, but a user only could like a tweet once, and we see the count on the log is still 1.
To fix this:

  • disable setUserLike
  • only apply setLikes in /twittme-web/src/tweets/components.js

    • for a user, setLikes make a like either 1 or 0
    • “like” +1, “unlike” -1, and number of likes is 1 or 0 ```javascript export function ActionBtn(props) { const { tweet, action } = props; const [likes, setLikes] = useState(tweet.likes ? tweet.likes : 0);

    / const [userLike, setUserLike] = useState( tweet.userLike === true ? true : false ); /

    //…

    const handleActionBackendEvent = (response, status) => { console.log(response, status);

    if (status === 200) {

    1. //new
    2. setLikes(response.likes);
    3. //setUserLike(true);

    }

    /* if (action.type === “like”) {

    1. if (userLike === true) {
    2. setLikes(likes - 1);
    3. setUserLike(false);
    4. } else {
    5. setLikes(likes + 1);
    6. setUserLike(true);
    7. }

    } */ };

    //… } ``` to test, refresh and send:
    image.png
    count of likes is always either 0 or 1.

Rendering the ReTweet:

include parent:

  • add a new div to display the parent tweet in /twittme-web/src/tweets/components.js
    1. export function Tweet(props) {
    2. const { tweet } = props;
    3. const className = props.className
    4. ? props.className
    5. : "col-10 mx-auto col-md-6";
    6. /*
    7. return (
    8. <div className={className}>
    9. <p>
    10. {tweet.id} - {tweet.content}
    11. </p>
    12. <div className="btn btn-group">
    13. <ActionBtn tweet={tweet} action={{ type: "like", display: "Likes" }} />
    14. <ActionBtn
    15. tweet={tweet}
    16. action={{ type: "unlike", display: "Unlike" }}
    17. />
    18. <ActionBtn
    19. tweet={tweet}
    20. action={{ type: "retweet", display: "Retweet" }}
    21. />
    22. </div>
    23. </div>
    24. );
    25. */
    26. return (
    27. <div className={className}>
    28. <div>
    29. <p>
    30. {tweet.id} - {tweet.content}
    31. </p>
    32. {tweet.parent && (
    33. <div>
    34. <Tweet tweet={tweet.parent} />
    35. </div>
    36. )}
    37. </div>
    38. <div className="btn btn-group">
    39. <ActionBtn tweet={tweet} action={{ type: "like", display: "Likes" }} />
    40. <ActionBtn
    41. tweet={tweet}
    42. action={{ type: "unlike", display: "Unlike" }}
    43. />
    44. <ActionBtn
    45. tweet={tweet}
    46. action={{ type: "retweet", display: "Retweet" }}
    47. />
    48. </div>
    49. </div>
    50. );
    51. }
    to test, refresh and click “retweet” button on something.
    we can see parent tweets like this:
    image.png

However, there are several problems exist, such as cannot retweet with contents.

more organized format:

  • change className
  • add a new

    and

    pair in /twittme-web/src/tweets/components.js ```javascript export function Tweet(props) { const { tweet } = props; const className = props.className ? props.className : “col-10 mx-auto col-md-6”; /* return (
    1. <div>
    2. <p>
    3. {tweet.id} - {tweet.content}
    4. </p>
    5. {tweet.parent && (
    6. <div>
    7. <Tweet tweet={tweet.parent} />
    8. </div>
    9. )}
    10. </div>
    11. <div className="btn btn-group">
    12. <ActionBtn tweet={tweet} action={{ type: "like", display: "Likes" }} />
    13. <ActionBtn
    14. tweet={tweet}
    15. action={{ type: "unlike", display: "Unlike" }}
    16. />
    17. <ActionBtn
    18. tweet={tweet}
    19. action={{ type: "retweet", display: "Retweet" }}
    20. />
    21. </div>
    ); */ return (
    1. <div>
    2. <p>
    3. {tweet.id} - {tweet.content}
    4. </p>
    5. {tweet.parent && (
    6. <div className="row">
    7. <div className="col-11 mx-auto p-3 border rounded">
    8. <p className="mb-0 text-muted small">Retweet</p>
    9. <Tweet className={" "} tweet={tweet.parent} />
    10. </div>
    11. </div>
    12. )}
    13. </div>
    14. <div className="btn btn-group">
    15. <ActionBtn tweet={tweet} action={{ type: "like", display: "Likes" }} />
    16. <ActionBtn
    17. tweet={tweet}
    18. action={{ type: "unlike", display: "Unlike" }}
    19. />
    20. <ActionBtn
    21. tweet={tweet}
    22. action={{ type: "retweet", display: "Retweet" }}
    23. />
    24. </div>
    ); }
  1. and refresh, the page looks like: <br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1594680126096-26571325-d02b-4aeb-829f-b253f58b1639.png#align=left&display=inline&height=419&margin=%5Bobject%20Object%5D&name=image.png&originHeight=837&originWidth=1428&size=34236&status=done&style=none&width=714)<br />It has clear that which one is tweet, which one is retweet now.
  2. <a name="jBbTI"></a>
  3. #### split parent tweet and tweet:
  4. - in /twittme-web/src/tweets/components.js
  5. - pick that double div, one p format into parent tweet into a function
  6. - then apply parentTweet in Tweet
  7. ```javascript
  8. export function ParentTweet(props) {
  9. const { tweet } = props;
  10. return tweet.parent ? (
  11. <div className="row">
  12. <div className="col-11 mx-auto p-3 border rounded">
  13. <p className="mb-0 text-muted small">Retweet</p>
  14. <Tweet className={" "} tweet={tweet.parent} />
  15. </div>
  16. </div>
  17. ) : null;
  18. }
  19. export function Tweet(props) {
  20. const { tweet } = props;
  21. const className = props.className
  22. ? props.className
  23. : "col-10 mx-auto col-md-6";
  24. return (
  25. <div className={className}>
  26. <div>
  27. <p>
  28. {tweet.id} - {tweet.content}
  29. </p>
  30. <ParentTweet tweet={tweet} />
  31. </div>
  32. <div className="btn btn-group">
  33. <ActionBtn tweet={tweet} action={{ type: "like", display: "Likes" }} />
  34. <ActionBtn
  35. tweet={tweet}
  36. action={{ type: "unlike", display: "Unlike" }}
  37. />
  38. <ActionBtn
  39. tweet={tweet}
  40. action={{ type: "retweet", display: "Retweet" }}
  41. />
  42. </div>
  43. </div>
  44. );
  45. /*
  46. return (
  47. <div className={className}>
  48. <div>
  49. <p>
  50. {tweet.id} - {tweet.content}
  51. </p>
  52. {tweet.parent && (
  53. <div className="row">
  54. <div className="col-11 mx-auto p-3 border rounded">
  55. <p className="mb-0 text-muted small">Retweet</p>
  56. <Tweet className={" "} tweet={tweet.parent} />
  57. </div>
  58. </div>
  59. )}
  60. </div>
  61. <div className="btn btn-group">
  62. <ActionBtn tweet={tweet} action={{ type: "like", display: "Likes" }} />
  63. <ActionBtn
  64. tweet={tweet}
  65. action={{ type: "unlike", display: "Unlike" }}
  66. />
  67. <ActionBtn
  68. tweet={tweet}
  69. action={{ type: "retweet", display: "Retweet" }}
  70. />
  71. </div>
  72. </div>
  73. );
  74. */
  75. }