Tamia  Walter

Tamia Walter

1606203241

Building a NextJS Monorepo

You started creating a frontend application for your company, then another, and another and eventually you end up with a dozen frontend applications with a ton of replicated code. Tooling code, linting, tests, helper libraries and domain specific code.

You probably seen this happen before. Is not effective and is prone to errors. The solution, co-locate all code and dependencies on a single repository, in other words create a monorepo.

I will not try to explain what a monorepo is or why do you need one. As usual on all my post I just to help you build one for your NextJS applications. You probably already decided a monorepo is the solution for you.

What do I need from a monorepo?

There are certain minimal requirements any frontend monorepo should support. The ones I care the most are:

  • Contain Applications and Libraries: Usually inside a monorepo you use the term package to identify specific units of code, it could be an App or a shared module. On our monorepo they will live under /packages/app/* and /packages/shared/* each.
  • Bootstrap a new Application or Shared Module with a CLI command.
  • Run ALL Tests in the monorepo with a single command. Alternatively we would want to run only the tests on an App or Shared Module.
  • Storybook is almost a requirement to build frontend applications (or similar tool). We also need to open a Storybook with ALL the stories on the monorepo. Alternatively, again, we would want to open it with only the stories from a single Application or Shared Module.
  • Shared tooling config. ESLint, Prettier, TypeScript, no need to be repeating ourselves in every package.
  • Each Application should have its own build scripts. Along with scripts to run the Application in isolation and also as composition. Composition means, we can have apps targeting different paths in a single domain and run them all at the same time (Ex: /customer-dashboard/... represents an Customer dashboard app and /back-office/... is the administrative Application internal employees use)

You can have other requirements in addition to this. After this post you can dedicate more time to them.

#monorepo #typescript #react #next #web-development

What is GEEK

Buddha Community

Building a NextJS Monorepo
Joel  Hawkins

Joel Hawkins

1629275400

How to use Monorepo to Build an App with Nx, NextJS and TypeScript

Too many repos to update? Try a monorepo! Learn what a monorepo is, and how to use one to build an app with Nx, Next.js and TypeScript.

In this article, we’ll learn what a monorepo is, and how monorepos help in developing applications faster with a better development experience. We’ll discuss the advantages of using the Nx development tools for managing a monorepo, and learn how to use those tools to build a Next.js application.

The code for this article is available on GitHub. You can find a working demo of the app we’re building here.

#monorepo #nx #nextjs #typescript

The Best Way to Build a Chatbot in 2021

A useful tool several businesses implement for answering questions that potential customers may have is a chatbot. Many programming languages give web designers several ways on how to make a chatbot for their websites. They are capable of answering basic questions for visitors and offer innovation for businesses.

With the help of programming languages, it is possible to create a chatbot from the ground up to satisfy someone’s needs.

Plan Out the Chatbot’s Purpose

Before building a chatbot, it is ideal for web designers to determine how it will function on a website. Several chatbot duties center around fulfilling customers’ needs and questions or compiling and optimizing data via transactions.

Some benefits of implementing chatbots include:

  • Generating leads for marketing products and services
  • Improve work capacity when employees cannot answer questions or during non-business hours
  • Reducing errors while providing accurate information to customers or visitors
  • Meeting customer demands through instant communication
  • Alerting customers about their online transactions

Some programmers may choose to design a chatbox to function through predefined answers based on the questions customers may input or function by adapting and learning via human input.

#chatbots #latest news #the best way to build a chatbot in 2021 #build #build a chatbot #best way to build a chatbot

Riyad Amin

Riyad Amin

1571046022

Build Your Own Cryptocurrency Blockchain in Python

Cryptocurrency is a decentralized digital currency that uses encryption techniques to regulate the generation of currency units and to verify the transfer of funds. Anonymity, decentralization, and security are among its main features. Cryptocurrency is not regulated or tracked by any centralized authority, government, or bank.

Blockchain, a decentralized peer-to-peer (P2P) network, which is comprised of data blocks, is an integral part of cryptocurrency. These blocks chronologically store information about transactions and adhere to a protocol for inter-node communication and validating new blocks. The data recorded in blocks cannot be altered without the alteration of all subsequent blocks.

In this article, we are going to explain how you can create a simple blockchain using the Python programming language.

Here is the basic blueprint of the Python class we’ll use for creating the blockchain:

class Block(object):
    def __init__():
        pass
    #initial structure of the block class 
    def compute_hash():
        pass
    #producing the cryptographic hash of each block 
  class BlockChain(object):
    def __init__(self):
    #building the chain
    def build_genesis(self):
        pass
    #creating the initial block
    def build_block(self, proof_number, previous_hash):
        pass
    #builds new block and adds to the chain
   @staticmethod
    def confirm_validity(block, previous_block):
        pass
    #checks whether the blockchain is valid
    def get_data(self, sender, receiver, amount):
        pass
    # declares data of transactions
    @staticmethod
    def proof_of_work(last_proof):
        pass
    #adds to the security of the blockchain
    @property
    def latest_block(self):
        pass
    #returns the last block in the chain

Now, let’s explain how the blockchain class works.

Initial Structure of the Block Class

Here is the code for our initial block class:

import hashlib
import time
class Block(object):
    def __init__(self, index, proof_number, previous_hash, data, timestamp=None):
        self.index = index
        self.proof_number = proof_number
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = timestamp or time.time()
    @property
    def compute_hash(self):
        string_block = "{}{}{}{}{}".format(self.index, self.proof_number, self.previous_hash, self.data, self.timestamp)
        return hashlib.sha256(string_block.encode()).hexdigest()

As you can see above, the class constructor or initiation method ( init()) above takes the following parameters:

self — just like any other Python class, this parameter is used to refer to the class itself. Any variable associated with the class can be accessed using it.

index — it’s used to track the position of a block within the blockchain.

previous_hash — it used to reference the hash of the previous block within the blockchain.

data—it gives details of the transactions done, for example, the amount bought.

timestamp—it inserts a timestamp for all the transactions performed.

The second method in the class, compute_hash , is used to produce the cryptographic hash of each block based on the above values.

As you can see, we imported the SHA-256 algorithm into the cryptocurrency blockchain project to help in getting the hashes of the blocks.

Once the values have been placed inside the hashing module, the algorithm will return a 256-bit string denoting the contents of the block.

So, this is what gives the blockchain immutability. Since each block will be represented by a hash, which will be computed from the hash of the previous block, corrupting any block in the chain will make the other blocks have invalid hashes, resulting in breakage of the whole blockchain network.

Building the Chain

The whole concept of a blockchain is based on the fact that the blocks are “chained” to each other. Now, we’ll create a blockchain class that will play the critical role of managing the entire chain.

It will keep the transactions data and include other helper methods for completing various roles, such as adding new blocks.

Let’s talk about the helper methods.

Adding the Constructor Method

Here is the code:

class BlockChain(object):
    def __init__(self):
        self.chain = []
        self.current_data = []
        self.nodes = set()
        self.build_genesis()

The init() constructor method is what instantiates the blockchain.

Here are the roles of its attributes:

self.chain — this variable stores all the blocks.

self.current_data — this variable stores information about the transactions in the block.

self.build_genesis() — this method is used to create the initial block in the chain.

Building the Genesis Block

The build_genesis() method is used for creating the initial block in the chain, that is, a block without any predecessors. The genesis block is what represents the beginning of the blockchain.

To create it, we’ll call the build_block() method and give it some default values. The parameters proof_number and previous_hash are both given a value of zero, though you can give them any value you desire.

Here is the code:

def build_genesis(self):
        self.build_block(proof_number=0, previous_hash=0)
 def build_block(self, proof_number, previous_hash):
        block = Block(
            index=len(self.chain),
            proof_number=proof_number,
            previous_hash=previous_hash,
            data=self.current_data
        )
        self.current_data = []  
        self.chain.append(block)
        return block

Confirming Validity of the Blockchain

The confirm_validity method is critical in examining the integrity of the blockchain and making sure inconsistencies are lacking.

As explained earlier, hashes are pivotal for realizing the security of the cryptocurrency blockchain, because any slight alteration in an object will result in the creation of an entirely different hash.

Thus, the confirm_validity method utilizes a series of if statements to assess whether the hash of each block has been compromised.

Furthermore, it also compares the hash values of every two successive blocks to identify any anomalies. If the chain is working properly, it returns true; otherwise, it returns false.

Here is the code:

def confirm_validity(block, previous_block):
        if previous_block.index + 1 != block.index:
            return False
        elif previous_block.compute_hash != block.previous_hash:
            return False
        elif block.timestamp <= previous_block.timestamp:
            return False
        return True

Declaring Data of Transactions

The get_data method is important in declaring the data of transactions on a block. This method takes three parameters (sender’s information, receiver’s information, and amount) and adds the transaction data to the self.current_data list.

Here is the code:

def get_data(self, sender, receiver, amount):
        self.current_data.append({
            'sender': sender,
            'receiver': receiver,
            'amount': amount
        })
        return True

Effecting the Proof of Work

In blockchain technology, Proof of Work (PoW) refers to the complexity involved in mining or generating new blocks on the blockchain.

For example, the PoW can be implemented by identifying a number that solves a problem whenever a user completes some computing work. Anyone on the blockchain network should find the number complex to identify but easy to verify — this is the main concept of PoW.

This way, it discourages spamming and compromising the integrity of the network.

In this article, we’ll illustrate how to include a Proof of Work algorithm in a blockchain cryptocurrency project.

Finalizing With the Last Block

Finally, the latest_block() helper method is used for retrieving the last block on the network, which is actually the current block.

Here is the code:

def latest_block(self):
        return self.chain[-1]

Implementing Blockchain Mining

Now, this is the most exciting section!

Initially, the transactions are kept in a list of unverified transactions. Mining refers to the process of placing the unverified transactions in a block and solving the PoW problem. It can be referred to as the computing work involved in verifying the transactions.

If everything has been figured out correctly, a block is created or mined and joined together with the others in the blockchain. If users have successfully mined a block, they are often rewarded for using their computing resources to solve the PoW problem.

Here is the mining method in this simple cryptocurrency blockchain project:

def block_mining(self, details_miner):
            self.get_data(
            sender="0", #it implies that this node has created a new block
            receiver=details_miner,
            quantity=1, #creating a new block (or identifying the proof number) is awarded with 1
        )
        last_block = self.latest_block
        last_proof_number = last_block.proof_number
        proof_number = self.proof_of_work(last_proof_number)
        last_hash = last_block.compute_hash
        block = self.build_block(proof_number, last_hash)
        return vars(block)

Summary

Here is the whole code for our crypto blockchain class in Python:

import hashlib
import time
class Block(object):
    def __init__(self, index, proof_number, previous_hash, data, timestamp=None):
        self.index = index
        self.proof_number = proof_number
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = timestamp or time.time()
    @property
    def compute_hash(self):
        string_block = "{}{}{}{}{}".format(self.index, self.proof_number, self.previous_hash, self.data, self.timestamp)
        return hashlib.sha256(string_block.encode()).hexdigest()
    def __repr__(self):
        return "{} - {} - {} - {} - {}".format(self.index, self.proof_number, self.previous_hash, self.data, self.timestamp)
class BlockChain(object):
    def __init__(self):
        self.chain = []
        self.current_data = []
        self.nodes = set()
        self.build_genesis()
    def build_genesis(self):
        self.build_block(proof_number=0, previous_hash=0)
    def build_block(self, proof_number, previous_hash):
        block = Block(
            index=len(self.chain),
            proof_number=proof_number,
            previous_hash=previous_hash,
            data=self.current_data
        )
        self.current_data = []  
        self.chain.append(block)
        return block
    @staticmethod
    def confirm_validity(block, previous_block):
        if previous_block.index + 1 != block.index:
            return False
        elif previous_block.compute_hash != block.previous_hash:
            return False
        elif block.timestamp <= previous_block.timestamp:
            return False
        return True
    def get_data(self, sender, receiver, amount):
        self.current_data.append({
            'sender': sender,
            'receiver': receiver,
            'amount': amount
        })
        return True        
    @staticmethod
    def proof_of_work(last_proof):
        pass
    @property
    def latest_block(self):
        return self.chain[-1]
    def chain_validity(self):
        pass        
    def block_mining(self, details_miner):       
        self.get_data(
            sender="0", #it implies that this node has created a new block
            receiver=details_miner,
            quantity=1, #creating a new block (or identifying the proof number) is awared with 1
        )
        last_block = self.latest_block
        last_proof_number = last_block.proof_number
        proof_number = self.proof_of_work(last_proof_number)
        last_hash = last_block.compute_hash
        block = self.build_block(proof_number, last_hash)
        return vars(block)  
    def create_node(self, address):
        self.nodes.add(address)
        return True
    @staticmethod
    def get_block_object(block_data):        
        return Block(
            block_data['index'],
            block_data['proof_number'],
            block_data['previous_hash'],
            block_data['data'],
            timestamp=block_data['timestamp']
        )
blockchain = BlockChain()
print("GET READY MINING ABOUT TO START")
print(blockchain.chain)
last_block = blockchain.latest_block
last_proof_number = last_block.proof_number
proof_number = blockchain.proof_of_work(last_proof_number)
blockchain.get_data(
    sender="0", #this means that this node has constructed another block
    receiver="LiveEdu.tv", 
    amount=1, #building a new block (or figuring out the proof number) is awarded with 1
)
last_hash = last_block.compute_hash
block = blockchain.build_block(proof_number, last_hash)
print("WOW, MINING HAS BEEN SUCCESSFUL!")
print(blockchain.chain)

Now, let’s try to run our code to see if we can generate some digital coins…

Wow, it worked!

Conclusion

That is it!

We hope that this article has assisted you to understand the underlying technology that powers cryptocurrencies such as Bitcoin and Ethereum.

We just illustrated the basic ideas for making your feet wet in the innovative blockchain technology. The project above can still be enhanced by incorporating other features to make it more useful and robust.

Learn More

Thanks for reading !

Do you have any comments or questions? Please share them below.

#python #cryptocurrency

Implement a Dynamic Breadcrumb in React/NextJS

 Breadcrumbs are a website navigation tool that allows a user to see their current page's "stack" of how it is nested under any parent pages. Users can then jump back to a parent page by clicking the associated breadcrumb link. These "Crumbs" increase the User Experience of the application, making it easier for the users to navigate nested pages efficiently and effectively.

 

Example Breadcrumbs

Breadcrumbs are popular enough that if you are building a web dashboard or application, you may have considered adding them. Generating these breadcrumb links efficiently and with the appropriate context is key to an improved user experience.

Let's build a smart NextBreadcrumbs React component that will parse the current route and build a dynamic breadcrumbs display that can handle both static & dynamic routes efficiently.

My projects usually revolve around Nextjs and MUI (formerly Material-UI) so that is the angle that I am going to approach this problem from, although the solution should work for any Nextjs-related application.

#Static Route Breadcrumbs

To start, our NextBreadcrumbs component will only handle static routes, meaning that our project has only static pages defined in the pages directory.

The following are examples of static routes because they do not contain [s and ]s in the route names, meaning the directory structure lines up 1:1 exactly with the expected URLs that they serve.

  • pages/index.js --> /
  • pages/about.js --> /about
  • pages/my/super/nested/route.js --> /my/super/nested/route

The solution will be extended to handle dynamic routes later.

#Defining the Basic Component

We can start with the basic component that uses the MUI Breadcrumbs component as a baseline.

import Breadcrumbs from '@mui/material/Breadcrumbs';
import * as React from 'react';

export default function NextBreadcrumbs() {
  return (
    <Breadcrumbs aria-label="breadcrumb" />
  );
}

The above creates the basic structure of the NextBreadcrumbs React component, imports the correct dependencies, and renders an empty Breadcrumbs MUI component.

We can then add in the next/router hooks, which will allow us to build the breadcrumbs from the current route.

We also create a Crumb component that will be used to render each link. This is a pretty dumb component for now, except that it will render normal text instead of a link for the last breadcrumb.

In a situation like /settings/notifications, it would render as the following:

Home (/ link) > Settings (/settings link) > Notifications (no link)

This is because the user is already on the last breadcrumb's page, so there is no need to link out to the same page. All the other crumbs are rendered as links to be clicked.

import Breadcrumbs from '@mui/material/Breadcrumbs';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
import { useRouter } from 'next/router';
import React from 'react';


export default function NextBreadcrumbs() {
  // Gives us ability to load the current route details
  const router = useRouter();
  
  return (
    <Breadcrumbs aria-label="breadcrumb" />
  );
}


// Each individual "crumb" in the breadcrumbs list
function Crumb({ text, href, last=false }) {
  // The last crumb is rendered as normal text since we are already on the page
  if (last) {
  	return <Typography color="text.primary">{text}</Typography>
  }

  // All other crumbs will be rendered as links that can be visited 
  return (
    <Link underline="hover" color="inherit" href={href}>
      {text}
    </Link>
  );
}

With this layout, we can then dive back into the NextBreadcrumbs component to generate the breadcrumbs from the route. Some existing code will start to be omitted to keep the code pieces smaller. The full example is shown below.

We will generate a list of breadcrumb objects that contain the information to be rendered by each Crumb element. Each breadcrumb will be created by parsing the Nextjs router's asPath property, which is a string containing the route as shown in the browser URL bar.

We will strip any query parameters, such as ?query=value, from the URL to make the breadcrumb creation process more straightforward.

export default function NextBreadcrumbs() {
  // Gives us ability to load the current route details
  const router = useRouter();
  
  function generateBreadcrumbs() {
    // Remove any query parameters, as those aren't included in breadcrumbs
    const asPathWithoutQuery = router.asPath.split("?")[0];
	
	// Break down the path between "/"s, removing empty entities
	// Ex:"/my/nested/path" --> ["my", "nested", "path"]
	const asPathNestedRoutes = asPathWithoutQuery.split("/")
												 .filter(v => v.length > 0);
	
	// Iterate over the list of nested route parts and build
	// a "crumb" object for each one.
	const crumblist = asPathParts.map((subpath, idx) => {
	  // We can get the partial nested route for the crumb
	  // by joining together the path parts up to this point.
	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
	  // The title will just be the route string for now
	  const title = subpath;
  	  return { href, text }; 
    })
	
	// Add in a default "Home" crumb for the top-level
	return [{ href: "/", text: "Home" }, ...crumblist];
  }
  
  // Call the function to generate the breadcrumbs list
  const breadcrumbs = generateBreadcrumbs();
  
  return (
    <Breadcrumbs aria-label="breadcrumb" />
  );
}

With this list of breadcrumbs, we can now render them using the Breadcrumbs and Crumb components. As previously mentioned, only the return portion of our component is shown for brevity.

  // ...rest of NextBreadcrumbs component above...
  return (
    {/* The old breadcrumb ending with '/>' was converted into this */}
    <Breadcrumbs aria-label="breadcrumb">
	  {/*
	    Iterate through the crumbs, and render each individually.
		We "mark" the last crumb to not have a link.
      */}
	  {breadcrumbs.map((crumb, idx) => (
	    <Crumb {...crumb} key={idx} last={idx === breadcrumbs.length - 1} />
	  ))}
	</Breadcrumbs>
  );

This should start generating some very basic - but working - breadcrumbs on our site once rendered; /user/settings/notifications would render as

Home > user > settings > notifications

#Memoizing Generated Breadcrumbs

There is a quick improvement that we can make before going further though. Right now the breadcrumb list is recreated every time the component re-renders, so we can memoize the crumb list for a given route to save some performance. To accomplish this, we can wrap our generateBreadcrumbs function call in the useMemo React hook.

  const router = useRouter();
  
  // this is the same "generateBreadcrumbs" function, but placed
  // inside a "useMemo" call that is dependent on "router.asPath"
  const breadcrumbs = React.useMemo(function generateBreadcrumbs() {
    const asPathWithoutQuery = router.asPath.split("?")[0];
	const asPathNestedRoutes = asPathWithoutQuery.split("/")
												 .filter(v => v.length > 0);

	const crumblist = asPathParts.map((subpath, idx) => {
	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
  	  return { href, text: subpath }; 
    })
	
	return [{ href: "/", text: "Home" }, ...crumblist];
  }, [router.asPath]);
  
  return // ...rest below...

#Improving Breadcrumb Text Display

Before we start incorporating dynamic routes, we can clean this current solution up more by including a nice way to change the text shown for each crumb generated.

Right now, if we have a path like /user/settings/notifications, then it will show:

Home > user > settings > notifications

which is not very appealing. We can provide a function to the NextBreadcrumbs component that will try to generate a more user-friendly name for each of these nested route crumbs.


const _defaultGetDefaultTextGenerator= path => path

export default function NextBreadcrumbs({ getDefaultTextGenerator=_defaultGetDefaultTextGenerator }) {
  const router = useRouter();

  // Two things of importance:
  // 1. The addition of getDefaultTextGenerator in the useMemo dependency list
  // 2. getDefaultTextGenerator is now being used for building the text property
  const breadcrumbs = React.useMemo(function generateBreadcrumbs() {
    const asPathWithoutQuery = router.asPath.split("?")[0];
	const asPathNestedRoutes = asPathWithoutQuery.split("/")
												 .filter(v => v.length > 0);

	const crumblist = asPathParts.map((subpath, idx) => {
	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
  	  return { href, text: getDefaultTextGenerator(subpath, href) }; 
    })

	return [{ href: "/", text: "Home" }, ...crumblist];
  }, [router.asPath, getDefaultTextGenerator]);
  
  return ( // ...rest below

and then our parent component can have something like the following, to title-ize the subpaths, or maybe even replace them with a new string.

{/* Assume that `titleize` is written and works appropriately */}
<NextBreadcrumbs getDefaultTextGenerator={path => titleize(path)} />

This implementation would then result in the following breadcrumbs. The full code example at the bottom has more examples of this.

Home > User > Settings > Notifications

#Nextjs Dynamic Routes

Nextjs's router allows for including dynamic routes that uses Pattern Matching to allow for URLs to have slugs, UUIDs, and other dynamic values that will then be passed to your views.

For example, if your Nextjs application has a page component at pages/post/[post_id].js, then the routes /post/1 and /post/abc will match it.

For our breadcrumbs component, we would like to show the name of the associated post instead of just its UUID. This means that the component will need to dynamically look up the post data based on the nested URL route path and regenerate the text of the associated crumb.

Right now, if you visit /post/abc, you would see breadcrumbs that look like

post > abc

but if the post with UUID has a title of My First Post, then we want to change the breadcrumbs to say

post > My First Post

Let's dive into how that can happen using async functions.

#Nextjs Router: asPath vs pathname

The next/router router instance in our code has two useful properties for our NextBreadcrumbs component; asPath and pathname. The router asPath is the URL path as shown directly in the URL bar of the browser. The pathname is a more internal version of the URL that has the dynamic parts of the path replaced with their [parameter] components.

For example, consider the path /post/abc from above.

The asPath would be /post/abc as the URL is shown

The pathname would be /post/[post_id] as our pages directory dictates

We can use these two URL path variants to build a way to dynamically fetch information about the breadcrumb, so we can show more contextually appropriate information to the user.

There is a lot going on below, so please re-read it - and the helpful notes below it - a few times over if needed.


const _defaultGetTextGenerator = (param, query) => null;
const _defaultGetDefaultTextGenerator = path => path;

// Pulled out the path part breakdown because its
// going to be used by both `asPath` and `pathname`
const generatePathParts = pathStr => {
  const pathWithoutQuery = pathStr.split("?")[0];
  return pathWithoutQuery.split("/")
						 .filter(v => v.length > 0);
}

export default function NextBreadcrumbs({
  getTextGenerator=_defaultGetTextGenerator,
  getDefaultTextGenerator=_defaultGetDefaultTextGenerator
}) {
  const router = useRouter();

  const breadcrumbs = React.useMemo(function generateBreadcrumbs() {
    const asPathNestedRoutes = generatePathParts(router.asPath);
	const pathnameNestedRoutes = generatePathParts(router.pathname);

	const crumblist = asPathParts.map((subpath, idx) => {
	  // Pull out and convert "[post_id]" into "post_id"
	  const param = pathnameNestedRoutes[idx].replace("[", "").replace("]", "");

	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
  	  return {
	  	href, textGenerator: getTextGenerator(param, router.query),
		text: getDefaultTextGenerator(subpath, href)
	  }; 
    })

	return [{ href: "/", text: "Home" }, ...crumblist];
  }, [router.asPath, router.pathname, router.query, getTextGenerator, getDefaultTextGenerator]);
  
  return ( // ...rest below

The asPath breakdown was moved to a generatePathParts function since the same logic is used for both router.asPath and router.pathname.

Determine the parameter that lines up with the dynamic route value, so abc would result in post_id.

The nested route parameter and all associated query values (router.query) are passed to a provided getTextGenerator which will return either a null value or a Promise response that should return the dynamic string to use in the associated breadcrumb.

The useMemo dependency array has more dependencies added; router.pathname, router.query, and getTextGenerator.

Finally, we need to update the Crumb component to use this textGenerator value if it is provided for the associated crumb object.

function Crumb({ text: defaultText, textGenerator, href, last=false }) {

  const [text, setText] = React.useState(defaultText);
  
  useEffect(async () => {
    // If `textGenerator` is nonexistent, then don't do anything
    if (!Boolean(textGenerator)) { return; }
	// Run the text generator and set the text again
    const finalText = await textGenerator();
    setText(finalText);
  }, [textGenerator]);

  if (last) {
  	return <Typography color="text.primary">{text}</Typography>
  }

  return (
    <Link underline="hover" color="inherit" href={href}>
      {text}
    </Link>
  );
}

 

The breadcrumbs can now handle both static routes and dynamic routes cleanly with the potential to display user-friendly values. While the above code is the business logic of the component, this can all be used with a parent component that looks like the final example below.

#Full Example

// NextBreadcrumbs.js

const _defaultGetTextGenerator = (param, query) => null;
const _defaultGetDefaultTextGenerator = path => path;

// Pulled out the path part breakdown because its
// going to be used by both `asPath` and `pathname`
const generatePathParts = pathStr => {
  const pathWithoutQuery = pathStr.split("?")[0];
  return pathWithoutQuery.split("/")
						 .filter(v => v.length > 0);
}

export default function NextBreadcrumbs({
  getTextGenerator=_defaultGetTextGenerator,
  getDefaultTextGenerator=_defaultGetDefaultTextGenerator
}) {
  const router = useRouter();

  const breadcrumbs = React.useMemo(function generateBreadcrumbs() {
    const asPathNestedRoutes = generatePathParts(router.asPath);
	const pathnameNestedRoutes = generatePathParts(router.pathname);

	const crumblist = asPathParts.map((subpath, idx) => {
	  // Pull out and convert "[post_id]" into "post_id"
	  const param = pathnameNestedRoutes[idx].replace("[", "").replace("]", "");

	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
  	  return {
	  	href, textGenerator: getTextGenerator(param, router.query),
		text: getDefaultTextGenerator(subpath, href)
	  }; 
    })

	return [{ href: "/", text: "Home" }, ...crumblist];
  }, [router.asPath, router.pathname, router.query, getTextGenerator, getDefaultTextGenerator]);
  
  return (
    <Breadcrumbs aria-label="breadcrumb">
	  {breadcrumbs.map((crumb, idx) => (
	    <Crumb {...crumb} key={idx} last={idx === breadcrumbs.length - 1} />
	  ))}
	</Breadcrumbs>
  );
}


function Crumb({ text: defaultText, textGenerator, href, last=false }) {

  const [text, setText] = React.useState(defaultText);
  
  useEffect(async () => {
    // If `textGenerator` is nonexistent, then don't do anything
    if (!Boolean(textGenerator)) { return; }
	// Run the text generator and set the text again
    const finalText = await textGenerator();
    setText(finalText);
  }, [textGenerator]);

  if (last) {
  	return <Typography color="text.primary">{text}</Typography>
  }

  return (
    <Link underline="hover" color="inherit" href={href}>
      {text}
    </Link>
  );
}

and then an example of this NextBreadcrumbs being used can be seen below. Note that useCallback is used to create only one reference to each helper function which will prevent unnecessary re-renders of the breadcrumbs when/if the page layout component re-rendered. You could also move this out to the top-level scope of the file, but I don't like to pollute the global scope like that.

// MyPage.js (Parent Component)

import React from 'react';
import NextBreadcrumbs from "./NextBreadcrumbs";


function MyPageLayout() {

  // Either lookup a nice label for the subpath, or just titleize it
  const getDefaultTextGenerator = React.useCallback((subpath) => {
    return {
	  "post": "Posts",
	  "settings": "User Settings",
	}[subpath] || titleize(subpath);
  }, [])
  
  // Assuming `fetchAPI` loads data from the API and this will use the
  // parameter name to determine how to resolve the text. In the example,
  // we fetch the post from the API and return it's `title` property
  const getTextGenerator = React.useCallback((param, query) => {
    return {
	  "post_id": () => await fetchAPI(`/posts/${query.post_id}/`).title,
	}[param];
  }, []);

  return () {
    <div>
	  {/* ...Whatever else... */}
	  <NextBreadcrumbs
	    getDefaultTextGenerator={getDefaultTextGenerator}
		getTextGenerator={getTextGenerator}
	  />
	  {/* ...Whatever else... */}
	</div>
  }

}

This is one of my more in-depth and technical posts, so I hope you enjoyed it, and please comment or reach out so that I can ensure consistency and correctness. Hopefully, this post taught you a few strategies or concepts about Nextjs.

Source: https://hackernoon.com/implement-a-dynamic-breadcrumb-in-reactnextjs

#react #nextjs 

Diego  Elizondo

Diego Elizondo

1652517446

Implementar Una Ruta De Navegación Dinámica En React/NextJS

Las migas de pan son una herramienta de navegación del sitio web que permite a un usuario ver la "pila" de su página actual de cómo está anidada debajo de las páginas principales. Luego, los usuarios pueden regresar a una página principal haciendo clic en el enlace de ruta de navegación asociado. Estas "migajas" aumentan la experiencia del usuario de la aplicación, lo que facilita que los usuarios naveguen por páginas anidadas de manera eficiente y efectiva.

 

Ejemplo de migas de pan

Las migas de pan son lo suficientemente populares como para que, si está creando un panel de control web o una aplicación, haya considerado agregarlas. Generar estos enlaces de migas de pan de manera eficiente y con el contexto apropiado es clave para una experiencia de usuario mejorada.

Construyamos un NextBreadcrumbs componente React inteligente que analice la ruta actual y construya una pantalla de migas de pan dinámicas que pueda manejar rutas tanto estáticas como dinámicas de manera eficiente.

Mis proyectos generalmente giran en torno a Nextjs y MUI (anteriormente Material-UI), por lo que ese es el ángulo desde el que abordaré este problema, aunque la solución debería funcionar para cualquier aplicación relacionada con Nextjs.

#Breadcrumbs de ruta estática

Para empezar, nuestro NextBreadcrumbscomponente solo manejará rutas estáticas, lo que significa que nuestro proyecto solo tiene páginas estáticas definidas en el pagesdirectorio.

Los siguientes son ejemplos de rutas estáticas porque no contienen [s y ]s en los nombres de ruta, lo que significa que la estructura del directorio se alinea 1:1 exactamente con las URL esperadas que sirven.

  • pages/index.js-->/
  • pages/about.js-->/about
  • pages/my/super/nested/route.js-->/my/super/nested/route

La solución se ampliará para manejar rutas dinámicas más adelante.

#Definiendo el Componente Básico

Podemos comenzar con el componente básico que usa el componente MUIBreadcrumbs como línea de base.

import Breadcrumbs from '@mui/material/Breadcrumbs';
import * as React from 'react';

export default function NextBreadcrumbs() {
  return (
    <Breadcrumbs aria-label="breadcrumb" />
  );
}

Lo anterior crea la estructura básica del NextBreadcrumbscomponente React, importa las dependencias correctas y genera un Breadcrumbscomponente MUI vacío.

Luego podemos agregar los next/routerganchos, lo que nos permitirá construir las migas de pan a partir de la ruta actual.

También creamos un Crumbcomponente que se usará para representar cada enlace. Este es un componente bastante tonto por ahora, excepto que mostrará texto normal en lugar de un enlace para la última ruta de navegación.

En una situación como /settings/notifications, se representaría de la siguiente manera:

Home (/ link) > Settings (/settings link) > Notifications (no link)

Esto se debe a que el usuario ya está en la última página de migas de pan, por lo que no es necesario vincular a la misma página. Todas las demás migajas se representan como enlaces en los que se puede hacer clic.

import Breadcrumbs from '@mui/material/Breadcrumbs';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
import { useRouter } from 'next/router';
import React from 'react';


export default function NextBreadcrumbs() {
  // Gives us ability to load the current route details
  const router = useRouter();
  
  return (
    <Breadcrumbs aria-label="breadcrumb" />
  );
}


// Each individual "crumb" in the breadcrumbs list
function Crumb({ text, href, last=false }) {
  // The last crumb is rendered as normal text since we are already on the page
  if (last) {
  	return <Typography color="text.primary">{text}</Typography>
  }

  // All other crumbs will be rendered as links that can be visited 
  return (
    <Link underline="hover" color="inherit" href={href}>
      {text}
    </Link>
  );
}

Con este diseño, podemos volver a sumergirnos en el NextBreadcrumbscomponente para generar las migas de pan a partir de la ruta. Parte del código existente comenzará a omitirse para mantener las piezas de código más pequeñas. El ejemplo completo se muestra a continuación.

Generaremos una lista de objetos de migas de pan que contienen la información que cada Crumbelemento debe representar. Cada ruta de navegación se creará analizando la propiedad del enrutador NextjsasPath , que es una cadena que contiene la ruta como se muestra en la barra de URL del navegador.

Quitaremos todos los parámetros de consulta, como ?query=value, de la URL para que el proceso de creación de la ruta de navegación sea más sencillo.

export default function NextBreadcrumbs() {
  // Gives us ability to load the current route details
  const router = useRouter();
  
  function generateBreadcrumbs() {
    // Remove any query parameters, as those aren't included in breadcrumbs
    const asPathWithoutQuery = router.asPath.split("?")[0];
	
	// Break down the path between "/"s, removing empty entities
	// Ex:"/my/nested/path" --> ["my", "nested", "path"]
	const asPathNestedRoutes = asPathWithoutQuery.split("/")
												 .filter(v => v.length > 0);
	
	// Iterate over the list of nested route parts and build
	// a "crumb" object for each one.
	const crumblist = asPathParts.map((subpath, idx) => {
	  // We can get the partial nested route for the crumb
	  // by joining together the path parts up to this point.
	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
	  // The title will just be the route string for now
	  const title = subpath;
  	  return { href, text }; 
    })
	
	// Add in a default "Home" crumb for the top-level
	return [{ href: "/", text: "Home" }, ...crumblist];
  }
  
  // Call the function to generate the breadcrumbs list
  const breadcrumbs = generateBreadcrumbs();
  
  return (
    <Breadcrumbs aria-label="breadcrumb" />
  );
}

Con esta lista de migas de pan, ahora podemos renderizarlas usando los componentes Breadcrumbsy . CrumbComo se mencionó anteriormente, solo returnse muestra la parte de nuestro componente por brevedad.

  // ...rest of NextBreadcrumbs component above...
  return (
    {/* The old breadcrumb ending with '/>' was converted into this */}
    <Breadcrumbs aria-label="breadcrumb">
	  {/*
	    Iterate through the crumbs, and render each individually.
		We "mark" the last crumb to not have a link.
      */}
	  {breadcrumbs.map((crumb, idx) => (
	    <Crumb {...crumb} key={idx} last={idx === breadcrumbs.length - 1} />
	  ))}
	</Breadcrumbs>
  );

Esto debería comenzar a generar algunas migas de pan muy básicas, pero que funcionan, en nuestro sitio una vez renderizado; /user/settings/notificationsrepresentaría como

Home > user > settings > notifications

#Memoizing Breadcrumbs generados

Sin embargo, hay una mejora rápida que podemos hacer antes de seguir adelante. En este momento, la lista de migas de pan se recrea cada vez que el componente se vuelve a renderizar, por lo que podemos memorizar la lista de migas de una ruta determinada para ahorrar algo de rendimiento. Para lograr esto, podemos envolver nuestra generateBreadcrumbsllamada de función en el useMemogancho React.

  const router = useRouter();
  
  // this is the same "generateBreadcrumbs" function, but placed
  // inside a "useMemo" call that is dependent on "router.asPath"
  const breadcrumbs = React.useMemo(function generateBreadcrumbs() {
    const asPathWithoutQuery = router.asPath.split("?")[0];
	const asPathNestedRoutes = asPathWithoutQuery.split("/")
												 .filter(v => v.length > 0);

	const crumblist = asPathParts.map((subpath, idx) => {
	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
  	  return { href, text: subpath }; 
    })
	
	return [{ href: "/", text: "Home" }, ...crumblist];
  }, [router.asPath]);
  
  return // ...rest below...

#Mejora de la visualización del texto de la ruta de navegación

Antes de comenzar a incorporar rutas dinámicas, podemos limpiar más esta solución actual al incluir una forma agradable de cambiar el texto que se muestra para cada migaja generada.

En este momento, si tenemos una ruta como /user/settings/notifications, se mostrará:

Home > user > settings > notifications

que no es muy atractivo. Podemos proporcionar una función al NextBreadcrumbscomponente que intentará generar un nombre más fácil de usar para cada uno de estos fragmentos de ruta anidados.


const _defaultGetDefaultTextGenerator= path => path

export default function NextBreadcrumbs({ getDefaultTextGenerator=_defaultGetDefaultTextGenerator }) {
  const router = useRouter();

  // Two things of importance:
  // 1. The addition of getDefaultTextGenerator in the useMemo dependency list
  // 2. getDefaultTextGenerator is now being used for building the text property
  const breadcrumbs = React.useMemo(function generateBreadcrumbs() {
    const asPathWithoutQuery = router.asPath.split("?")[0];
	const asPathNestedRoutes = asPathWithoutQuery.split("/")
												 .filter(v => v.length > 0);

	const crumblist = asPathParts.map((subpath, idx) => {
	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
  	  return { href, text: getDefaultTextGenerator(subpath, href) }; 
    })

	return [{ href: "/", text: "Home" }, ...crumblist];
  }, [router.asPath, getDefaultTextGenerator]);
  
  return ( // ...rest below

y luego nuestro componente principal puede tener algo como lo siguiente, para titular las rutas secundarias, o tal vez incluso reemplazarlas con una nueva cadena.

{/* Assume that `titleize` is written and works appropriately */}
<NextBreadcrumbs getDefaultTextGenerator={path => titleize(path)} />

Esta implementación daría como resultado las siguientes migas de pan. El ejemplo de código completo en la parte inferior tiene más ejemplos de esto.

Home > User > Settings > Notifications

#Nextjs Rutas dinámicas

El enrutador de Nextjs permite incluir rutas dinámicas que usan Pattern Matching para permitir que las URL tengan slugs, UUID y otros valores dinámicos que luego se pasarán a sus vistas.

Por ejemplo, si su aplicación Nextjs tiene un componente de página en pages/post/[post_id].js, las rutas /post/1y /post/abccoincidirán con él.

Para nuestro componente de migas de pan, nos gustaría mostrar el nombre de la publicación asociada en lugar de solo su UUID. Esto significa que el componente deberá buscar dinámicamente los datos de la publicación en función de la ruta de la ruta URL anidada y regenerar el texto de la migaja asociada.

En este momento, si visitas /post/abc, verás migas de pan que parecen

post > abc

pero si la publicación con UUID tiene un título de My First Post, entonces queremos cambiar las migas de pan para decir

post > My First Post

Profundicemos en cómo puede suceder eso usando asyncfunciones.

Enrutador #Nextjs: asPathvspathname

La next/routerinstancia del enrutador en nuestro código tiene dos propiedades útiles para nuestro NextBreadcrumbscomponente; asPathy pathname. El enrutador asPathes la ruta URL como se muestra directamente en la barra de URL del navegador. Es pathnameuna versión más interna de la URL que tiene las partes dinámicas de la ruta reemplazadas por sus [parameter]componentes.

Por ejemplo, considere el camino /post/abcdesde arriba.

El asPathsería /post/abccomo se muestra la URL

El pathnamesería como dicta /post/[post_id]nuestro directoriopages

Podemos usar estas dos variantes de ruta de URL para crear una forma de obtener información dinámicamente sobre la ruta de navegación, de modo que podamos mostrar información contextualmente más apropiada para el usuario.

Están sucediendo muchas cosas a continuación, así que vuelva a leerlo, y las notas útiles a continuación, unas cuantas veces si es necesario.


const _defaultGetTextGenerator = (param, query) => null;
const _defaultGetDefaultTextGenerator = path => path;

// Pulled out the path part breakdown because its
// going to be used by both `asPath` and `pathname`
const generatePathParts = pathStr => {
  const pathWithoutQuery = pathStr.split("?")[0];
  return pathWithoutQuery.split("/")
						 .filter(v => v.length > 0);
}

export default function NextBreadcrumbs({
  getTextGenerator=_defaultGetTextGenerator,
  getDefaultTextGenerator=_defaultGetDefaultTextGenerator
}) {
  const router = useRouter();

  const breadcrumbs = React.useMemo(function generateBreadcrumbs() {
    const asPathNestedRoutes = generatePathParts(router.asPath);
	const pathnameNestedRoutes = generatePathParts(router.pathname);

	const crumblist = asPathParts.map((subpath, idx) => {
	  // Pull out and convert "[post_id]" into "post_id"
	  const param = pathnameNestedRoutes[idx].replace("[", "").replace("]", "");

	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
  	  return {
	  	href, textGenerator: getTextGenerator(param, router.query),
		text: getDefaultTextGenerator(subpath, href)
	  }; 
    })

	return [{ href: "/", text: "Home" }, ...crumblist];
  }, [router.asPath, router.pathname, router.query, getTextGenerator, getDefaultTextGenerator]);
  
  return ( // ...rest below

El asPathdesglose se movió a una generatePathPartsfunción ya que se usa la misma lógica para ambos router.asPathy router.pathname.

Determine el paraméter que se alinea con el valor de la ruta dinámica, por lo que abcdaría como resultado post_id.

El éter de ruta anidado paramy todos los valores de consulta asociados ( router.query) se pasan a un proveedor getTextGeneratorque devolverá un nullvalor o una Promiserespuesta que debería devolver la cadena dinámica para usar en la ruta de navegación asociada.

La useMemomatriz de dependencia tiene más dependencias agregadas; router.pathname, router.queryy getTextGenerator.

Finalmente, necesitamos actualizar el Crumbcomponente para usar este textGeneratorvalor si se proporciona para el objeto migas asociado.

function Crumb({ text: defaultText, textGenerator, href, last=false }) {

  const [text, setText] = React.useState(defaultText);
  
  useEffect(async () => {
    // If `textGenerator` is nonexistent, then don't do anything
    if (!Boolean(textGenerator)) { return; }
	// Run the text generator and set the text again
    const finalText = await textGenerator();
    setText(finalText);
  }, [textGenerator]);

  if (last) {
  	return <Typography color="text.primary">{text}</Typography>
  }

  return (
    <Link underline="hover" color="inherit" href={href}>
      {text}
    </Link>
  );
}

 

Las migas de pan ahora pueden manejar rutas estáticas y rutas dinámicas de forma limpia con el potencial de mostrar valores fáciles de usar. Si bien el código anterior es la lógica comercial del componente, todo esto se puede usar con un componente principal que se parece al ejemplo final a continuación.

#Ejemplo Completo

// NextBreadcrumbs.js

const _defaultGetTextGenerator = (param, query) => null;
const _defaultGetDefaultTextGenerator = path => path;

// Pulled out the path part breakdown because its
// going to be used by both `asPath` and `pathname`
const generatePathParts = pathStr => {
  const pathWithoutQuery = pathStr.split("?")[0];
  return pathWithoutQuery.split("/")
						 .filter(v => v.length > 0);
}

export default function NextBreadcrumbs({
  getTextGenerator=_defaultGetTextGenerator,
  getDefaultTextGenerator=_defaultGetDefaultTextGenerator
}) {
  const router = useRouter();

  const breadcrumbs = React.useMemo(function generateBreadcrumbs() {
    const asPathNestedRoutes = generatePathParts(router.asPath);
	const pathnameNestedRoutes = generatePathParts(router.pathname);

	const crumblist = asPathParts.map((subpath, idx) => {
	  // Pull out and convert "[post_id]" into "post_id"
	  const param = pathnameNestedRoutes[idx].replace("[", "").replace("]", "");

	  const href = "/" + asPathNestedRoutes.slice(0, idx + 1).join("/");
  	  return {
	  	href, textGenerator: getTextGenerator(param, router.query),
		text: getDefaultTextGenerator(subpath, href)
	  }; 
    })

	return [{ href: "/", text: "Home" }, ...crumblist];
  }, [router.asPath, router.pathname, router.query, getTextGenerator, getDefaultTextGenerator]);
  
  return (
    <Breadcrumbs aria-label="breadcrumb">
	  {breadcrumbs.map((crumb, idx) => (
	    <Crumb {...crumb} key={idx} last={idx === breadcrumbs.length - 1} />
	  ))}
	</Breadcrumbs>
  );
}


function Crumb({ text: defaultText, textGenerator, href, last=false }) {

  const [text, setText] = React.useState(defaultText);
  
  useEffect(async () => {
    // If `textGenerator` is nonexistent, then don't do anything
    if (!Boolean(textGenerator)) { return; }
	// Run the text generator and set the text again
    const finalText = await textGenerator();
    setText(finalText);
  }, [textGenerator]);

  if (last) {
  	return <Typography color="text.primary">{text}</Typography>
  }

  return (
    <Link underline="hover" color="inherit" href={href}>
      {text}
    </Link>
  );
}

y luego se puede ver un ejemplo de este NextBreadcrumbsuso a continuación. Tenga en cuenta que useCallbackse utiliza para crear solo una referencia a cada función auxiliar, lo que evitará que se vuelvan a procesar innecesariamente las migas de pan cuando/si el componente de diseño de página se vuelve a procesar. También puede mover esto al alcance de nivel superior del archivo, pero no me gusta contaminar el alcance global de esa manera.

// MyPage.js (Parent Component)

import React from 'react';
import NextBreadcrumbs from "./NextBreadcrumbs";


function MyPageLayout() {

  // Either lookup a nice label for the subpath, or just titleize it
  const getDefaultTextGenerator = React.useCallback((subpath) => {
    return {
	  "post": "Posts",
	  "settings": "User Settings",
	}[subpath] || titleize(subpath);
  }, [])
  
  // Assuming `fetchAPI` loads data from the API and this will use the
  // parameter name to determine how to resolve the text. In the example,
  // we fetch the post from the API and return it's `title` property
  const getTextGenerator = React.useCallback((param, query) => {
    return {
	  "post_id": () => await fetchAPI(`/posts/${query.post_id}/`).title,
	}[param];
  }, []);

  return () {
    <div>
	  {/* ...Whatever else... */}
	  <NextBreadcrumbs
	    getDefaultTextGenerator={getDefaultTextGenerator}
		getTextGenerator={getTextGenerator}
	  />
	  {/* ...Whatever else... */}
	</div>
  }

}

Esta es una de mis publicaciones más profundas y técnicas, así que espero que la hayas disfrutado, y por favor comenta o comunícate para que pueda garantizar la coherencia y la corrección. Con suerte, esta publicación le enseñó algunas estrategias o conceptos sobre Nextjs.

Fuente: https://hackernoon.com/implement-a-dynamic-breadcrumb-in-reactnextjs

#react #nextjs