Categories
React

How to Use URL Parameters and Query Strings With React Router

React is a library for creating front-end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

To build single-page apps, we have to have some way to map URLs to the React component to display.

In this article, we’ll look at how to access URL parameters in React Router routes.


Define Routes That Accept URL Parameters, and Get Them

To define a Route with a path that takes an id as a parameter, we can put a colon before the parameter name.

For example, we can write:

<Switch>  
    <Route path="/:id" children={<User />} />  
</Switch>

Then we can use the useParams Hook to get the URL parameter.

We can write a full example as follows:

In the code above, we have the Switch component in App with the Route inside as a child. The path prop is set to /:id , which means that we can use the useParams Hook later to get the route parameter.

Then in the children prop, we pass in the User component, which we want to display.

Also, we added Link components with the links that we can click to go to the URL with the given URL parameters in the to prop.

Then in the User component, we get the id from the URL parameter with the useParams Hook.

Finally, we display the id in the User route.


Get and Set Query Strings

We can define and use a useQuery Hook in a component to get query parameters. To pass in query parameters, we just add them to the Links to props as usual.

For example, we can write the following:

We first defined the useQuery Hook to get the query parameters of the URL via the URLSearchParams constructor. We get the useLocation()s search property.

Then we create a QueryScreen component to call useQuery() and get the URLSearchParams instance assigned to query. This is where we can use query to get the name query parameter by calling get with 'name' passed in.

Then we pass in the value returned by query.get(“name” ) to the User component as a prop and display it there.


Conclusion

We can use the useParams Hook to get a URL parameter defined in Routes.

We can accept URL parameters in a Route by putting a colon before the parameter name in the path parameter, like path=”/:id”.

To add query strings in a path, we just append it directly to the path.

Then we can convert the query string into a URLSearchParams instance, and we can use the search property of that to get the query string.

We can then use the get method with the key name to get the value.

Categories
Modern JavaScript

Best of Modern JavaScript — Resolve and Reject Promises

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at JavaScript promises.

Other Ways of Creating Promises

Other than using the Promise constructor, we can create promises in other ways.

One way is the Promise.resolve method.

It takes the resolved value of the promise as its argument.

And it returns a promise.

For instance, we can write:

Promise.resolve(1)  
  .then(x => console.log(x));

x is the resolved value from Promise.resolve .

If x is a promise whose construction is the receiver, then the promise is unchanged.

For instance, if we have:

const p = new Promise(() => null);  
console.log(Promise.resolve(p) === p);

then we get true from the console log.

If the argument is a thenable and then the then method in the argument is a function, then the resolved value with Promise.resolve is the argument that we call the then parameter with.

For example, if we have:

const thenableObj = {  
  then(reaction) {  
    reaction('foo');  
  }  
};  
const promise = Promise.resolve(thenableObj);  
console.log(promise instanceof Promise);   
promise.then(x => console.log(x));

We have a thenableObj , which has the then method.

It takes a reaction function and we called it with 'foo' .

Then we pass in an object to the Promise.resolve method, which returns a promise.

If we check if promise is an instance of a Promise , then that returns true .

We can also call then on it with a callback to get the 'foo' value, which is assigned to x .

Promise.reject()

We can call Promise.reject() to return a promise that’s rejected with a value.

For example, we can write:

const error = new Error('error');  
Promise.reject(error)  
  .catch(err => console.log(err === error));

We pass in an error object by passing that into the Promise.reject method.

Then we call catch by passing in a callback into it.

err has the error object that we called Promise.reject with, which should be the same as error .

Chaining Promises

We can chain promises with then as long as we return a promise.

Whatever we return in the then callback will be the resolved value of the promise.

For instance, if we have:

Promise.resolve('bar')  
  .then(function(value1) {  
    return 'foo';  
  })  
  .then(function(value2) {  
    console.log(value2);  
  });

Then value2 would be 'foo' .

This lets us flatten promises chains.

Instead of writing:

Promise.resolve('bar')  
  .then(function(value1) {  
    Promise.resolve('foo')  
      .then(function(value2) {  
        //...  
      });  
  })

We write:

Promise.resolve('bar')  
  .then(function(value1) {  
    return 'foo';  
  })  
  .then(function(value2) {  
    //...  
  });

Catching Errors

We can catch errors with the catch method.

For example, we can write:

Promise.reject(new Error('error'))  
  .catch(function() {  
    return 'error occurred';  
  })  
  .then(function(value) {  
    //...  
  });

We called catch to catch the error from the rejected promise and run our own code.

This lets us set some value for the next promise to use.

Throw an Exception

We can throw an exception in the then callback, then the promise returned will be rejected.

For instance, we can write:

Promise.resolve()  
  .then(function(value) {  
    throw new Error();  
  })  
  .catch(function(reason) {  
    // ...  
  });

We threw an error in the then callback, so the catch callback will run.

Conclusion

We can resolve a promise with a value so the next one can be invoked.

Also, we can reject promises and catch the error with catch .

Categories
Modern JavaScript

Best of Modern JavaScript — Proxy Handlers

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at how to control JavaScript object operations.

Function Proxy Handlers

We can also add handler methods to the handler we pass into the Proxy constructor or Proxy.revocable method.

apply(target, thisArgument, argumentsList) lets us change the behavior of the apply or call method.

It can also change the behavior of direct calls to a function.

target is the target object which we want to control.

thisArgument lets us pass in the value of this in the function.

argumentsList lets us pass in a list of arguments.

Invariants of Handler Methods

Handler methods have various invariants enforced on them.

They include the following.

apply(target, thisArgument, argumentsList) has no invariants enforced on them.

construct(target, argumentsList, newTarget) must return an object, and not null or a primitive value.

defineProperty(target, propKey, propDesc) must not let us add [properties to an object that isn’t extensible.

The target must have a property must have a non-configurable own property with propKey if configurable is false .

If writable and configurable are both false , then we can’t write a new property to the property.

deleteProperty(target, propKey) can’t delete non-configurable own properties of the target .

get(target, propKey, receiver) must return the property’s value if it has an own, non-writable, and non-configurable data property with name propKey .

Otherwise, it must return undefined .

getOwnPropertyDescriptor(target, propKey) must return either an object or undefined .

Non-configurable now properties of the target can’t be reported as non-existent.

If the target is non-extensile, then it must report as existing.

It can’t report a non-configurable property as configurable and can’t report a different value for a non-configurable and non-writable property.

getPrototypeOf(target) must return either an object or null .

If the target isn’t extensible, then the handler must return the prototype of the target object.

has(target, propKey) mustn’t hide a non-configurable own property of target .

If the target is on-extensible than no own property of target may be hidden.

isExtensible(target) must return a boolean.

The coerced boolean value must be the same as target.isExtensible .

ownKeys(target) must return an object which is treated as array-like and converted to an array.

Each element of the result must either be a string or a symbol.

The result must have the keys of all non-configurable non-inherited properties of target .

If target isn’t extensible then the result must only the keys of its own properties.

preventExtensions(target) returns a boolean.

target.isExtensible() must return false after this is called.

set(target, propKey, value, receiver) should set the property of a writable, configurable property of target .

If target already has an own non-configurable property with name propKey , then a TypeError should be thrown since the property can’t be set.

setPrototypeOf(target, proto) ‘s returned result is coerced into a boolean.

If the target isn’t extensible, then its prototype can’t be changed.

In this case, target ‘s prototype must be the same as what it is now, or a TypeError would be thrown.

Conclusion

The proxy handler methods must follow some conditions even though it lets us customize those operations.

This way, the object operations are still predictable.

Categories
Modern JavaScript

Best of Modern JavaScript — Proxy and Object

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at metaprogramming with JavaScript proxies.

Revocable Proxies

We can create revocable proxies with ES6.

To do that, we call the Proxy.revocable method with the target and handler arguments.

For example, we can write:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    return 'foo';
  },

  has(target, propKey) {
    return true;
  }
};
const {
  proxy,
  revoke
} = Proxy.revocable(target, handler);

The Proxy.revocable function takes the target object which we want to change the behavior of.

handler is an object which has various methods to let us control the behavior of target .

It returns an object with the proxy object and the revoke function to revoke the proxy.

We can call revoke to stop using the proxy.

For example, if we have:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    return target[propKey];
  },

has(target, propKey) {
    return true;
  }
};
const {
  proxy,
  revoke
} = Proxy.revocable(target, handler);

proxy.foo = 'bar';
console.log(proxy.foo);

revoke();

console.log(proxy.foo);

Then the 2nd console log will give us an error since the proxy has been revoked with the revoke function.

Therefore, we get ‘Uncaught TypeError: Cannot perform ‘get’ on a proxy that has been revoked’.

Proxies as Prototypes

We can use proxies as a prototype of an object.

For example, we can write:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    return target[propKey];
  },

  has(target, propKey) {
    return true;
  }
};
const proto = new Proxy(target, handler);
const obj = Object.create(proto);

to use the Object.create method to create a proxy object and use that as the prototype.

Forwarding Intercepted Operations

We can forward intercepted operations with proxies.

For example, we can write:

const target = {};
const handler = {
  deleteProperty(target, propKey) {
    return delete target[propKey];
  },
  has(target, propKey) {
    return propKey in target;
  },
}

const proto = new Proxy(target, handler);

to add the forward the delete operation and the in operation.

We just do the same thing that we expect without the proxy within the handler methods.

Wrapping an Object Affects this

Since the handler is an object, it has its own value of this .

Therefore, if we have:

const target = {
  foo() {
    console.log(this === target)
    console.log(this === proxy)
  }
};
const handler = {};
const proxy = new Proxy(target, handler);

proxy.foo()

Then the first console log is false and the 2nd is true since we called foo on proxy .

On the other hand, if we have:

const target = {
  foo() {
    console.log(this === target)
    console.log(this === proxy)
  }
};
const handler = {};
const proxy = new Proxy(target, handler);

target.foo()

Then the first console log is true and the 2nd is false .

Objects that can’t be Wrapped Transparently

If an object has some private data, then a proxy can’t wrap the object transparently.

For example, if we have:

const _name = new WeakMap();
class Animal {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this);
  }
}

Then if we have:

const mary = new Animal('mary');
const proxy = new Proxy(mary, {});
console.log(proxy.name);

We get that the name property in undefined .

This is because the name is stored in the WeakMap instead of as a direct property of this itself.

this is the proxy so it doesn’t have the name property.

Conclusion

Proxies can be revocable, and the value of this are different between the proxy and the original object.

Categories
Modern JavaScript

Best of Modern JavaScript — Proxies and Object Operations

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at metaprogramming with JavaScript proxies.

Wrapping Instances of Built-in Constructors

We can use proxies to wrap instances of built-in constructors.

For instance, we can write:

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

But if we write:

console.log(proxy.getDate());

We get ‘Uncaught TypeError: this is not a Date object.’ because we called getDate on the proxy instead of the original Date instance.

However, proxies can be wrapped transparently with arrays.

For instance, we can write:

const p = new Proxy(new Array(), {});
p.push('foo');
console.log(p.length);

We created a proxy with the Array constructor.

The handler object is an empty object.

We call push with to insert an entry into the array.

It works with the proxy.

And we get the length with as we expected.

For objects that can’t be transparently wrapped with proxies, we can modify the handler so that it does what we expect.

For instance, we can write:

const handler = {
  get(target, propKey, receiver) {
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

const target = new Date();
const proxy = new Proxy(target, handler);

Then we call the target ‘s propKey method.

In the get method, we check if target[propKey] ‘s type is a 'function' .

If it is, then we change the value of this with bind so that the function will have the correct value of this .

Otherwise, we just return the value as-is.

Now we can call the getDate method on the object without issue:

const handler = {
  get(target, propKey, receiver) {
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

const target = new Date();
const proxy = new Proxy(target, handler);

console.log(proxy.getDate());

Use Cases for Proxies

We can use proxies to log the operations of the object.

For example, we can write:

const handler = {
  get(target, propKey, receiver) {
    console.log(target[propKey])
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  toString() {
    return `Person(${this.firstName}, ${this.lastName})`;
  }
}

const target = new Person('jane', 'smith');
const proxy = new Proxy(target, handler);

console.log(proxy.toString());

to trace the object operation.

We get the structure of the toString method.

And then we get the returned result with it.

We can also use proxies to warn about unknown properties.

For instance, we can write:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    if (!(propKey in target)) {
      throw new Error('property does not exist');
    }
    return target[propKey];
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.foo);

We have a handler object that has a get method.

Inside it, we use the in operator to check if there’s a property with the name propKey .

Since there’s no foo property in target , we get ‘Uncaught Error: property does not exist’.

This is a handy feature that doesn’t in JavaScript objects natively.

Conclusion

We can use proxies to log object operations and intercept object operations in various ways.