Create a Solana dApp from scratch

Fetching tweets in the frontend

Episode 9
2 years ago
16 min read

In the previous episode, we’ve worked hard to allow users to connect their wallets and ended up with a program object from Anchor allowing us to interact with our Solana program. Now, it’s time to use that program to remove all the mock data and fetch real tweets from the blockchain.

Some of the work we’ll do in this article will be familiar because we’ve already seen how to fetch tweets from the blockchain when testing our Solana program.

Okay, let’s start the wiring!

Fetching all tweets

We’ll start simple, by fetching all existing tweets and displaying them on the home page.

Open the api/fetch-tweets.js file and paste the following code.

import { useWorkspace } from '@/composables'

export const fetchTweets = async () => {
    const { program } = useWorkspace()
    const tweets = await program.value.account.tweet.all();
    return tweets
}

A few things to notice here:

  • First of all, we’re importing the useWorkspace composable to access the workspace store.
  • Because we only need access to the program object from the workspace, we destructure it from the result of useWorkspace().
  • We access the program using program.value because program is a reactive variable and wrapped in a Ref object.
  • Finally, we access all Tweet accounts using account.tweet.all() just like we did when we tested our program.

Okay, let’s try this in our PageHome.vue component. If you look inside the script part of the component, you'll notice we're already calling the fetchTweets api method and using its result to display tweets.

import { ref } from 'vue'
import { fetchTweets } from '@/api'
import TweetForm from '@/components/TweetForm'
import TweetList from '@/components/TweetList'

const tweets = ref([])
const loading = ref(true)
fetchTweets()
    .then(fetchedTweets => tweets.value = fetchedTweets)
    .finally(() => loading.value = false)

// ...

At this point, everything should be wired properly for our home page so let’s see if everything works.

First, you’ll need to start a new local validator. You may do this by running solana-test-validator in your terminal or, alternatively, by running anchor localnet which will also re-build and re-deploy your program.

For us to see some tweets in our application, we’ll need to have some Tweet accounts inside our local ledger. Fortunately for us, we know that running the tests will create 3 of them so let’s run anchor run test to add them to our local ledger.

Okay, now we have a running local ledger that contains 3 tweets in total. Therefore, we should see these tweets on the home page.

However, if you go to the home page and open the “Network” developer tools in your browser, you should see the following.

Screenshot of the app with the dev tools open showing that we are getting 3 tweets but they are not displayed properly.

As we can see in the network tab, we are indeed getting 3 tweet accounts but they are not displayed properly on the home page.

That’s because our frontend is expecting to receive an object with a certain structure that doesn’t match what we get from the API call.

So instead of changing our entire frontend to accommodate for that structure, let’s create a new Tweet model that works for our frontend and abstracts the data received from the API.

The Tweet model

Inside the src folder of our frontend application, let’s create a new folder called models. Inside that new folder, we’ll add two files:

  • Tweet.js. This will structure our tweet accounts using a Tweet class.
  • index.js. This will register the Tweet model so we can import it like we import composables and API endpoints.

Once that folder and those two files are created, paste the following code inside the index.js file.

export * from './Tweet'

And paste the following inside the Tweet.js file.

export class Tweet
{
    constructor (publicKey, accountData) {
        this.publicKey = publicKey
        this.author = accountData.author
        this.timestamp = accountData.timestamp.toString()
        this.topic = accountData.topic
        this.content = accountData.content
    }
}

As you can see, to create a new Tweet object, we need to provide:

  • A publicKey, which will be an instance of Solana’s PublicKey class.
  • And an accountData object, provided by the API endpoint.

When creating a new Tweet object, we store its public key and all of the properties inside the accountData object individually. That way we can access, say, the topic via tweet.topic. We also parse the timestamp into a string because the API endpoint gives us the timestamp as an array of bytes.

On top of these properties, our frontend relies on Tweet objects to have the following additional properties: key, author_display, created_at and created_ago.

Key

The key property should be a unique identifier that represents our tweet. It is used in some VueJS templates when looping through arrays of tweets. Since the public key is unique for each tweet, we’ll use its base-58 format to provide a unique string.

We’ll use a getter function to provide this key property. You can achieve this by adding the following getter at the end of the Tweet class.

export class Tweet
{
    // ...

    get key () {
        return this.publicKey.toBase58()
    }
}

Author display

Whilst we’ve already got access to the author’s public key through the author property, the frontend uses a condensed version of this address on the TweetCard.vue component as not to visually overwhelm the user.

This condensed version is quite simply the first 4 characters and the last 4 characters of the public key with a couple of dots in the middle.

Thus, let’s add another getter function called author_display and use the slice method to condense the author’s public key.

export class Tweet
{
    // ...

    get author_display () {
        const author = this.author.toBase58()
        return author.slice(0,4) + '..' + author.slice(-4)
    }
}

Created at and created ago

The last two properties we need are human-readable versions of the timestamp provided by our program. created_at should be a localised human-readable date including the time whereas created_ago should briefly describe how long ago the tweet was posted.

Fortunately, there are plenty of JavaScript libraries out there for manipulating dates. Moment.js is probably the most popular one but I’d say overkill for our purpose. Instead, I often prefer using Day.js which is super lightweight by default and extendable to fit our needs.

So let’s start by installing Day.js using npm.

npm install dayjs

Next, we need to import it and extend it slightly so it supports localised formats and relative times — used for created_at and created_ago respectively.

In your main.js file, add the following code after the “CSS” section.

// CSS.
import 'solana-wallets-vue/styles.css'
import './main.css'

// Day.js
import dayjs from 'dayjs'
import localizedFormat from 'dayjs/plugin/localizedFormat'
import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.extend(localizedFormat)
dayjs.extend(relativeTime)

// ...

Now, back to our Tweet.js model, we can import Day.js and provide two getter functions for created_at and created_ago. Both of them can use dayjs.unix(this.timestamp) to convert our timestamp property into a Day.js object. Then, we can use the format('lll') and fromNow() methods to get a localised date and a relative time respectively.

We end up with the following Tweet.js model! 🎉

import dayjs from "dayjs"

export class Tweet
{
    constructor (publicKey, accountData) {
        this.publicKey = publicKey
        this.author = accountData.author
        this.timestamp = accountData.timestamp.toString()
        this.topic = accountData.topic
        this.content = accountData.content
    }

    get key () {
        return this.publicKey.toBase58()
    }

    get author_display () {
        const author = this.author.toBase58()
        return author.slice(0,4) + '..' + author.slice(-4)
    }

    get created_at () {
        return dayjs.unix(this.timestamp).format('lll')
    }

    get created_ago () {
        return dayjs.unix(this.timestamp).fromNow()
    }
}

Returning Tweet models

Now that our Tweet model is ready, let’s use it in our fetch-tweets.js API endpoint so that it returns Tweet objects instead of whatever the API returns.

For that, we can use map on the tweets array to transform each item inside it. As we’ve seen in a previous episode, the API returns an object containing a publicKey and an account object which is exactly what we need to create a new Tweet object.

import { useWorkspace } from '@/composables'
import { Tweet } from '@/models'

export const fetchTweets = async () => {
    const { program } = useWorkspace()
    const tweets = await program.value.account.tweet.all();
    return tweets.map(tweet => new Tweet(tweet.publicKey, tweet.account))
}

Right, at this point, we should see all tweets displaying properly on the home page. In the image below, I’ve also logged the return value of the fetchTweets method so we can make sure all our custom getters are working properly.

Screenshot of the home page with our 3 test tweets properly displayed and the console showing an array of Tweet objects with all the relevant data.

All good! let’s move on to the next task.

Add links in the tweet card

When viewing the tweets on the home page, you might have noticed that each tweet contains 3 links:

  • One on the author’s address that should take you to this author’s page.
  • One on the tweet’s time that should take you to a page that only shows that tweet so users can share it.
  • One on the tweet’s topic that should take you to this topic’s page.

However, if you try to click on them, they will always send you to the home page. That’s simply because these links are not yet implemented and that’s what we are going to do now.

If you have a look inside the TweetCard.vue component, you should see a few comments on the template that look like this: <!-- TODO: Link to ... -->. So let’s tackle each of these comments one by one, starting with the author’s link.

The author’s link

This link is slightly more complicated than the others because the route it directs to depends on whether it’s one of our tweets or not. By that I mean, if we click on our own address it should direct us to the profile page, whereas, if we click on the address of someone else, it should take us to the users page with the address already pre-filled.

Therefore, we’re going to create a computed property called authorRoute that will use the connected wallet to figure out which route we should be directed to.

Update the script part of the TweetCard.vue component, with the following lines.

import { toRefs, computed } from 'vue'
import { useWorkspace } from '@/composables'

const props = defineProps({
    tweet: Object,
})

const { tweet } = toRefs(props)
const { wallet } = useWorkspace()
const authorRoute = computed(() => {
    if (wallet.value && wallet.value.publicKey.toBase58() === tweet.value.author.toBase58()) {
        return { name: 'Profile' }
    } else {
        return { name: 'Users', params: { author: tweet.value.author.toBase58() } }
    }
})

Let’s go through that piece of code:

  • We start by importing the computed method from VueJS that we’ll use to create our authorRoute computed property.
  • We also import our workspace via the useWorkspace composable and access the connected wallet from it.
  • Inside the computed method, we first check if the tweet’s author has the same public key as the connected wallet.
  • If that’s the case, we simply redirect to the profile page by returning { name: 'Profile' }. In Vue Router, that's how you can identify a route named “Profile”.
  • Otherwise, we need to redirect the user to the “users page” whilst also pre-filling its address with the tweet’s author. In Vue Router, we can provide route parameters via a params object. Thus, we can access the “Users” page of the tweet’s author by returning: { name: 'Users', params: { author: tweet.value.author.toBase58() } }

Now that our authorRoute computed property is available, we can give it to the relevant <router-link> component and remove the comment above.

- <!-- TODO: Link to author page or the profile page if it's our own tweet. -->
- <router-link :to="{ name: 'Home' }" class="hover:underline">
+ <router-link :to="authorRoute" class="hover:underline">
      {{ tweet.author_display }}
  </router-link>

The tweet’s link

Next, let’s implement the link to the tweet page. For that, we can use the base-58 format of the tweet’s public key as a parameter of the Tweet route. We end up with the following route object.

{ name: 'Tweet', params: { tweet: tweet.publicKey.toBase58() } }

This time, we can use this object directly inside the appropriate <router-link> without the need for a new variable.

- <!-- TODO: Link to the tweet page. -->
- <router-link :to="{ name: 'Home' }" class="hover:underline">
+ <router-link :to="{ name: 'Tweet', params: { tweet: tweet.publicKey.toBase58() } }" class="hover:underline">
      {{ tweet.created_ago }}
  </router-link>

The topic’s link

Finally, we need to implement the link to the topics page. Similarly to the previous links, we can pass the topic as a parameter of the Topics route and end up with the following route object…

{ name: 'Topics', params: { topic: tweet.topic } }

…which we can use directly in the final <router-link> that needs updating.

- <!-- TODO: Link to the topic page. -->
- <router-link v-if="tweet.topic" :to="{ name: 'Home' }" class="inline-block mt-2 text-pink-500 hover:underline">
+ <router-link v-if="tweet.topic" :to="{ name: 'Topics', params: { topic: tweet.topic } }" class="inline-block mt-2 text-pink-500 hover:underline">
      {{ tweet.created_ago }}
  </router-link>

And just link that, our TweetCard.vue component is complete and all of its links are pointing to the right places.

However, if we try to click on these links, they will always show all tweets ever created because that's what our fetchTweets method currently does.

So let’s fix this. We’ll start with the Topics and Users pages. Both of these pages will need access to all tweets from our program that match a certain criterion. However, our fetchTweets API endpoint does not support filters yet. Therefore, we’ve got to sort this out first.

Supporting filters

Since we’ve already seen how to filter accounts in Solana, supporting filters in our API endpoint should be nice and easy.

The first thing we need to do is add a new filters parameter to the fetchTweets method of our fetch-tweets.js file, allowing us to optionally provide filters when fetching tweets.

import { useWorkspace } from '@/composables'
import { Tweet } from '@/models'

export const fetchTweets = async (filters = []) => {
    const { program } = useWorkspace()
    const tweets = await program.value.account.tweet.all(filters);
    return tweets.map(tweet => new Tweet(tweet.publicKey, tweet.account))
}

Now, writing Solana filters can be a little tedious and having that logic scattered everywhere in our components might not be ideal to maintain. It would be nice if we could offer some helper methods that would generate filters so our components can use these methods instead of generating filters themselves. So let’s do that!

We’ll start by exporting an authorFilter function that accepts a public key in base 58 format and returns the appropriate memcmp filter as we’ve seen in episode 5 of this series.

Here’s said function that you can now add at the end of your fetch-tweets.js file.

export const authorFilter = authorBase58PublicKey => ({
    memcmp: {
        offset: 8, // Discriminator.
        bytes: authorBase58PublicKey,
    }
})

Next, we’ll do the same for topics by exporting a topicFilter function that accepts a topic as a string and returns a memcmp filter that encodes the topic properly and provides the right offset for it.

Add the following topicFilter function at the end of your fetch-tweets.js file and don’t forget to import the bs58 library so it can encode the given topic string into a base 58 formatted array of bytes.

import { useWorkspace } from '@/composables'
import { Tweet } from '@/models'
import bs58 from 'bs58'

// ...

export const topicFilter = topic => ({
    memcmp: {
        offset: 8 + // Discriminator.
            32 + // Author public key.
            8 + // Timestamp.
            4, // Topic string prefix.
        bytes: bs58.encode(Buffer.from(topic)),
    }
})

If you’re wondering why we’re using this particular offset, it is for the exact same reasons we described in episode 5 when filtering tweets by topics in our tests.

And that’s it! We now have a fetchTweets endpoint that not only supports filters but also makes it super easy for our components to use them. Your final fetch-tweets.js file should look like this.

import { useWorkspace } from '@/composables'
import { Tweet } from '@/models'
import bs58 from 'bs58'

export const fetchTweets = async (filters = []) => {
    const { program } = useWorkspace()
    const tweets = await program.value.account.tweet.all(filters);
    return tweets.map(tweet => new Tweet(tweet.publicKey, tweet.account))
}

export const authorFilter = authorBase58PublicKey => ({
    memcmp: {
        offset: 8, // Discriminator.
        bytes: authorBase58PublicKey,
    }
})

export const topicFilter = topic => ({
    memcmp: {
        offset: 8 + // Discriminator.
            32 + // Author public key.
            8 + // Timestamp.
            4, // Topic string prefix.
        bytes: bs58.encode(Buffer.from(topic)),
    }
})

Our components can now use this API endpoint to fetch and filter tweets like this.

import { fetchTweets, authorFilter, topicFilter } from '@/api'

// Fetch all tweets.
const allTweets = await fetchTweets()

// Filter tweets by author.
const myTweets = await fetchTweets([
    authorFilter('B1AfN7AgpMyctfFbjmvRAvE1yziZFDb9XCwydBjJwtRN'),
])

// Filter tweets by topic.
const solanaTweets = await fetchTweets([
    topicFilter('solana'),
])

// Filter tweets by author and topic.
const mySolanaTweets = await fetchTweets([
    authorFilter('B1AfN7AgpMyctfFbjmvRAvE1yziZFDb9XCwydBjJwtRN'),
    topicFilter('solana'),
])

Noice! Let’s use that new shiny API endpoint on our Topics and Users pages.

Fetching tweets by topic

Inside our PageTopics.vue component, let’s import the topicFilter helper in addition to the already imported fetchTweets method.

import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { fetchTweets, topicFilter } from '@/api'
import { useSlug, useFromRoute } from '@/composables'
import TweetForm from '@/components/TweetForm'
import TweetList from '@/components/TweetList'
import TweetSearch from '@/components/TweetSearch'

Next, let’s scroll down a bit and provide the appropriate parameter to the fetchTweet method. Here, we’ll use the value of the slugTopic computed property as a topic to use for filtering tweets.

const fetchTopicTweets = async () => {
    if (slugTopic.value === viewedTopic.value) return
    try {
        loading.value = true
        const fetchedTweets = await fetchTweets([topicFilter(slugTopic.value)])
        tweets.value = fetchedTweets
        viewedTopic.value = slugTopic.value
    } finally {
        loading.value = false
    }
}

Topics page… Done! ✅

You should now be able to click on a topic’s link and view all tweets from that topic.

Screenshot of the topics page showing two of our tweets from the “veganism” topic.

Fetching tweets by author

Let’s do the same for our PageUsers.vue component.

Similarly, we import the authorFilter function next to the fetchTweets function.

import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { fetchTweets, authorFilter } from '@/api'
import { useFromRoute } from '@/composables'
import TweetList from '@/components/TweetList'
import TweetSearch from '@/components/TweetSearch'

Next, we provide an authorFilter using the author property to the first parameter of the fetchTweets function.

const fetchAuthorTweets = async () => {
    if (author.value === viewedAuthor.value) return
    try {
        loading.value = true
        const fetchedTweets = await fetchTweets([authorFilter(author.value)])
        tweets.value = fetchedTweets
        viewedAuthor.value = author.value
    } finally {
        loading.value = false
    }
}

Boom, users page… Done! ✅

Screenshot of the users page showing two tweets from a given author.

Before we move on, there’s one more page that needs to use the authorFilter function and that’s the profile page.

So let’s do the same to our PageProfile.vue component. As usual, we import the authorFilter...

import { ref, watchEffect } from 'vue'
import { fetchTweets, authorFilter } from '@/api'
import TweetForm from '@/components/TweetForm'
import TweetList from '@/components/TweetList'
import { useWorkspace } from '@/composables'

... and use it in the first parameter of the fetchTweets function.

watchEffect(() => {
    if (! wallet.value) return
    fetchTweets([authorFilter(wallet.value.publicKey.toBase58())])
        .then(fetchedTweets => tweets.value = fetchedTweets)
        .finally(() => loading.value = false)
})

Notice that, we also added a line that ensures we do have a connected wallet before continuing — i.e. if (! wallet.value) return. Even though the profile page is hidden when no wallet is connected, we still need to add that extra check because, upon refresh, there will be a little delay before the wallet re-connects automatically.

Fetching only one tweet

There’s one last page where users can access tweets and that’s the Tweet page. That page is a little special because instead of displaying multiple tweets, it simply retrieves the content of a Tweet account at a given address.

Therefore, we can’t use the fetchTweets API endpoint here. Instead, there is a getTweet API endpoint located in the get-tweet.js file that we need to update.

Replace everything inside that file with the following code.

import { useWorkspace } from '@/composables'
import { Tweet } from '@/models'

export const getTweet = async (publicKey) => {
    const { program } = useWorkspace()
    const account = await program.value.account.tweet.fetch(publicKey);
    return new Tweet(publicKey, account)
}

The getTweet method accepts a publicKey parameter which should be an instance of Solana’s PublicKey class.

It then uses the fetch method from the account.tweet API provided by Anchor’s program to fetch the content of the account. We can then combine this account data with the provided public key to return a new Tweet object.

Now that our getTweet API endpoint is ready, let’s use it inside our PageTweet.vue component.

If you read the code inside this component, you’ll notice the getTweet method is already imported and used because that’s how we were displaying mock data before.

watchEffect(async () => {
    try {
        loading.value = true
        tweet.value = await getTweet(new PublicKey(tweetAddress.value))
    } catch (e) {
        tweet.value = null
    } finally {
        loading.value = false
    }
})

Notice that, for the public key, we use the tweetAddress reactive property which is dynamically extracted from the current URL. We then wrap its value inside a PublicKey object as this is what our API endpoint expects to receive.

All done! ✅

If you click on the timestamp of a tweet, you now have access to a page that can be used to share it.

Screenshot of the tweet page showing one of the tweets generated by our tests.

Conclusion

Our application really is starting to take shape! At this point, you should be able to look around and read all the tweets present in the blockchain.

The only thing missing before we can share our application to the world is allowing users to send tweets directly via our frontend app which is exactly what we’ll do in the next episode. 🙌

View Episode 9 on GitHub

Compare with Episode 8

Discussions

Author avatar
Andrea
2 years ago

These are a great introduction! Thank you so much!

I think there is a small typo: in the article you reference "HomePage.vue" in app/src/components/ but in your project you call it "PageHome.vue".

💖 1

Discussion

Fetching tweets in the frontend
Author avatar
Andrea
2 years ago

These are a great introduction! Thank you so much!

I think there is a small typo: in the article you reference "HomePage.vue" in app/src/components/ but in your project you call it "PageHome.vue".

💖 1
Author avatar
Loris Leiva
2 years ago

Hi Andrea 👋 Thank you! I have fixed the typo. 😊

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Xena
2 years ago

Curious how Anchor's all() function works (and if you could possibly link the documentation for all() in a comment). What if millions of tweets are sent on this platform? Would the API call not work?

💖 1

Discussion

Fetching tweets in the frontend
Author avatar
Xena
2 years ago

Curious how Anchor's all() function works (and if you could possibly link the documentation for all() in a comment). What if millions of tweets are sent on this platform? Would the API call not work?

💖 1
Author avatar
Loris Leiva
2 years ago

Hi Xena 👋

Under the hood, the all method uses the getProgramAccounts RPC method and adds a memcmp filter to make sure we only get the type of account requested.

I've recently released a new article that might answer some of your questions.

https://lorisleiva.com/paginating-and-ordering-accounts-in-solana

I hope this helps. 🍀

💖 1

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Rixiao Zhang
2 years ago

Many thanks for your efforts

💖 0

Discussion

Fetching tweets in the frontend
Author avatar
Rixiao Zhang
2 years ago

Many thanks for your efforts

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Daniel
2 years ago

Thank you a lot for this amazing content. It is by far the most detailed solana tutorial out there.

I think I found a typo when defining the tweet's link in TweetCard.vue. At least it was throwing errors from my side. Since we need to get the value of the reference to tweet, the object we pass should be

{ name: "Tweet", params: { tweet: tweet.value.publicKey.toBase58() } }

The same for tweet.value.topic

💖 0

Discussion

Fetching tweets in the frontend
Author avatar
Daniel
2 years ago

Thank you a lot for this amazing content. It is by far the most detailed solana tutorial out there.

I think I found a typo when defining the tweet's link in TweetCard.vue. At least it was throwing errors from my side. Since we need to get the value of the reference to tweet, the object we pass should be

{ name: "Tweet", params: { tweet: tweet.value.publicKey.toBase58() } }

The same for tweet.value.topic

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Philip Lee
2 years ago

When trying to fetch tweets to display, Im unable to retrieve actual tweet data. In console, I do see error saying

Uncaught (in promise) TypeError: project_serum_anchor__WEBPACK_IMPORTED_MODULE_3_.Provider is not a constructor

I have test validator running, everything prior to this point has been perfect.

Any clues?

💖 0

Discussion

Fetching tweets in the frontend
Author avatar
Philip Lee
2 years ago

When trying to fetch tweets to display, Im unable to retrieve actual tweet data. In console, I do see error saying

Uncaught (in promise) TypeError: project_serum_anchor__WEBPACK_IMPORTED_MODULE_3_.Provider is not a constructor

I have test validator running, everything prior to this point has been perfect.

Any clues?

💖 0
Author avatar
Philip Lee
2 years ago

another big hint: warning msg when running npm run serve

warning in ./src/composables/useWorkspace.js

export 'Provider' (imported as 'Provider') was not found in '@project-serum/anchor' (possible exports: ACCOUNT_DISCRIMINATOR_SIZE, AccountClient, AnchorError, AnchorProvider, BN, BorshAccountsCoder, BorshCoder, BorshEventCoder, BorshInstructionCoder, BorshStateCoder, EventManager, EventParser, IdlError, LangErrorCode, LangErrorMessage, MethodsBuilderFactory, Program, ProgramError, ProgramErrorStack, Spl, SplTokenCoder, StateClient, eventDiscriminator, getProvider, parseIdlErrors, setProvider, splitArgsAndCtx, stateDiscriminator, toInstruction, translateAddress, translateError, utils, validateAccounts, web3)

💖 0
Author avatar
Medal
2 years ago

changing Provider to AnchorProvider will fix this

💖 1
Author avatar
Philip Lee
2 years ago

Thank you @Medal

💖 0
Author avatar
Nelis
2 years ago

I have the same issue, where did you change the Provider?

💖 0
Author avatar
Nelis
2 years ago

Oh, got it. It worked! Thanks @Medal

💖 0
Author avatar
Rohit Shinde
2 years ago

Updating Provider with AnchorProvider still gives an error saying

    at new st (provider.ts?af05:59:1)
    at ReactiveEffect.eval [as fn] (useWorkspace.js?a7cf:15:1)
    at ReactiveEffect.run (reactivity.esm-bundler.js?89dc:185:1)
    at get value [as value] (reactivity.esm-bundler.js?89dc:1144:1)
    at ReactiveEffect.eval [as fn] (useWorkspace.js?a7cf:16:1)
    at ReactiveEffect.run (reactivity.esm-bundler.js?89dc:185:1)
    at get value [as value] (reactivity.esm-bundler.js?89dc:1144:1)
    at fetchTweets (fetch-tweets.js?0fed:7:1)
    at setup (PageHome.vue?0ec4:9:1)
    at callWithErrorHandling (runtime-core.esm-bundler.js?d2dd:155:1)

any help?

💖 0
Author avatar
Rohit Shinde
2 years ago
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'publicKey')
💖 0
Author avatar
Crypto Dizl
2 years ago

I'm also getting the same error as Rohit above... any tip?

💖 0
Author avatar
Crypto Dizl
2 years ago

OK, fixed it. For the benefit of others, I had to use publicKey instead of value:

const provider = computed(() => new AnchorProvider(connection, wallet.**publicKey**))
💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Manny
2 years ago

I'm stuck after the second step in this episode. When running npm run serve my terminal sends an error message "ERROR in ./src/composables/useWorkspace.js 5:0-58 Module not found: Error: Can't resolve '../../../target/idl/solana_twitter.json' in '/Users/manny/solana-twitter/app/app/src/composables'" I need help

💖 0

Discussion

Fetching tweets in the frontend
Author avatar
Manny
2 years ago

I'm stuck after the second step in this episode. When running npm run serve my terminal sends an error message "ERROR in ./src/composables/useWorkspace.js 5:0-58 Module not found: Error: Can't resolve '../../../target/idl/solana_twitter.json' in '/Users/manny/solana-twitter/app/app/src/composables'" I need help

💖 0
Author avatar
Manny
2 years ago

Has anyone experience the same thing or is it just me?

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Tilak raj
5 months ago

I am getting error while fetching the tweets from the frontend, it says 'cannot read properties of undefined (reading 'kind')'. Also when the printed the Program object, the program.value is "undefined". Please help me out, I am stuck here for the past week. Any help would be much appreciated.

💖 0

Discussion

Fetching tweets in the frontend
Author avatar
Tilak raj
5 months ago

I am getting error while fetching the tweets from the frontend, it says 'cannot read properties of undefined (reading 'kind')'. Also when the printed the Program object, the program.value is "undefined". Please help me out, I am stuck here for the past week. Any help would be much appreciated.

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member

Would you like to chime in?

You must be a member to start a new discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member