Monday 10 June 2024

Creating a simple Counter React application using Redux

 Background

In this post, we will see how to create a simple Counter React.js application. This post assumes you have an understanding of React, Redux and can use IDE like Visual Studio or Webstorm to create React applications. You should have npm installed as well.

Creating a simple Skeleton React application

Let's start by creating a React App using the following command
  • npx create-react-app my-app


You will see final output like above screenshot when the command is completed.
This will create a default app skeleton for you. If you want to see more details you can visit the official GitHub documentation. Once you have the app you can go into the app directory and execute below command to start the React app on local server.
  • npm start


You should be able to access it via browser using some localhost URL. The URL will get printed in the console when you run the above command. (For me it's http://localhost:3000/)

The default application in the browser will look something like the below:



You can now open the project in any IDE you are comfortable working with. I am using Visual Studio for this demo. Your project structure should look something like below:


Note details about some of the main project files:
  • index.js - This is your file that has your application's root component added to the actual DOM. This is the starting point of your application.
  • App.js - This has your main custom root component defined. This has the code for the UI that you see when your application starts.
  • *.css - These are CSS files that add styling to your application.
  • package.json - package.json and the corresponding lock file is where your project npm dependencies are stored.

Creating a Counter application using Redux

Now we are going to use the above skeleton to create a simple counter application which supports the functionality of incrementing and decrementing the counter and the state is maintained via Redux.

  1. Firstly you need to install the redux package to be able to use it. Inside your project directory you can install the redux package using the following command:
    • npm install @reduxjs/toolkit react-redux
  2. Then you need to define a store that your react application is going to use. Create a file store.js and add the following code to it
      
    import { configureStore } from '@reduxjs/toolkit'
    import counterReducer from './reducer/CounterReducer'
    
    export default configureStore({
        reducer: {
          counter: counterReducer,
        },
      })  
    


    This essentially does
    1. Sets redux store using configureStore imported from @reduxjs/toolkit.
    2. It provides a reducer that will be used to maintain the state (More on this later)
  3. Once you have stored configured the store you need to provide it to the React application. For this go to the starting application file - index.js make the following changes



    1. Put a React Redux <Provider> component around your <App />
    2. Pass the Redux store as <Provider store={store}>
  4. Now you need to define how your state stored in the store actually looks like, what are the actions that will be needed to update that state (the reducer methods). You can do that by creating a new file for reducer and add following code to it.

      
    
    import { createSlice } from '@reduxjs/toolkit'
    
    export const counterSlice = createSlice({
        name: 'counter',
        initialState: {
          value: 0,
        },
        reducers: {
            increment: (state) => {
                state.value += 1
            },
            decrement: (state) => {
                state.value -= 1
            }
            
        }
    })
    
    export const { increment, decrement, incrementByAmount } = counterSlice.actions
    export default counterSlice.reducer
    

    1. Note how we have used createSlice method from @reduxjs/toolkit to achieve this functionality.
    2. This method takes the reducer name, initial state - in our case, we have simply called it value and set it to 0, reducer methods - increment and decrement methods that update the state to increase our counter value by + or -1.




  5. Lastly, you need to create your Counter component to use the the redux store, the state we define and the reducer methods to update the state. See following code for counter component

        
    import {useDispatch, useSelector} from 'react-redux'
    import {increment, decrement} from '../reducer/CounterReducer'
    
    const Counter = (props) => {
    
        const dispatch = useDispatch()
        const count = useSelector((state)=>state.counter.value)
    
        const inc = () => {
            dispatch(increment())
        }
    
        const dec = () => {
            dispatch(decrement())
        }
    
        return (
            <>
            <label>Conter: {count}</label>
            <button onClick={inc}>Increment</button>
            <button onClick={dec}>Decrement</button>
            </>
        )
    
    }
    export default Counter
     
    

    1. Note how we are importing reducer methods increment and decrement and calling them on click of respective buttons.
    2. Also note how we are using two new hooks - useDispatch for dispatching reducer actions and useSelector to select from the state.
    3. Redux state is immutable and the reducer provides a way to mutate the state to create a new immutable state.




  6. You can then plug this Counter component in your App.js by importing it and then opening the URL in the browser to see the result. It should look something like below





You can install redux dev tools in Chrome to inspect what's happening in your redux state, what actions are getting dispatched, etc.







Related links




Wednesday 5 June 2024

Install Node and NPM on Windows using fnm version manager

 Background

In previous posts, we saw how to install node directly using the installer in Windows and on Linux using apt-get command. In this post, we will see how to use a package manager called fnm (Fast Node manager) on Windows.

Install Node and NPM on Windows using fnm package manager

  1. Open your PowerShell window
  2. Run command : winget install Schniz.fnm
    1. This will install the fnm version manager
  3. Before you use fnm you need to do a few terminal settings
    1. Run the command "new-item -type file -path $profile -force" to create your profile file.
    2. Then run the command "notepad $profile" to open your profile file.
    3. Add the below line to the opened Notepad file and save it
      1. fnm env --use-on-cd | Out-String | Invoke-Expression
  4. Once done you can install the node using the following command
    1. fnm use --install-if-missing 20
  5. Once run you can check the installation by running the following commands
    1. node -v
    2. npm -v





Related Links

Saturday 1 June 2024

Understanding descriptors in Python

 Background

As you know, Python does not have a concept of private variables or getters/setters. In one of the previous posts, we saw the use of property to achieve something similar. In this post, we will examine the concept underlying the property functionality, called descriptors. Using the descriptors is the pythonic way to handle attributes of a class. Descriptors are the mechanisms behind properties, methods, static methods, class methods, and super().


Understanding descriptors

Before we see any let's take an example to see why we need descriptors. Consider a simple Employee class below:

class Employee:
    def __init__(self, name):
        self.name = name


emp = Employee("Aniket")
print(emp.name)


For simplicity, it just has one instance variable called "name". In real life you would like to have some check when you set the name of Employee to something - like you might want to ensure it at least has one character, it can have a maximum of 10 chars, etc. We saw how to do this via properties in the last post, in this post we will see how to use descriptors which are the basis of property as well.

class Name:
class Name:
    def __set__(self, instance, value):
        print("Invoking __set__ on Name")
        if not isinstance(instance, Employee):
            raise ValueError("Name descriptor is to be used with Employee instance only")
        if len(value) < 1 or len(value) > 10:
            raise ValueError("Name cannot be less than 1 char or more than 10 char")
        instance._name = value

    def __get__(self, instance, owner):
        print("Invoking __get__ on Name")
        if not isinstance(instance, Employee):
            raise ValueError("Name descriptor is to be used with Employee instance only")
        return instance._name


class Employee:
    name = Name()

    def __init__(self, name):
        self._name = None
        self.name = name


emp = Employee("")
print(emp.name)


emp = Employee("Aniket")
print(emp.name)
emp.name = "Abhijit"
print(emp.name)


The above code defines a descriptor called "Name" and uses it to manage attributes for the Employee class instead. Above prints:

Invoking __set__ on Name
Invoking __get__ on Name
Aniket
Invoking __set__ on Name
Invoking __get__ on Name
Abhijit

You can play around passing names in the constructor as 
  • "Aniket" - Works fine. print Aniket
  • "" - Fails & prints ValueError: Name cannot be less than 1 char or more than 10 char
  • "Aniket Thakur" & prints ValueError: Name cannot be less than 1 char or more than 10 char

See how we now have more granular control over the Name attribute of the Employee class. That's the power of descriptors. 

Notice how emp.name = "Abhijit" works. Normally it would have set name attribute of Employee class to a string "Abhijit" but since in this case it is a descriptor it called __set__ dunder / magic method of the corresponding descriptor class.

Descriptor protocol

A class will be descriptor if it has one of the following methods:
  • __get__(self, obj, type=None) -> value

  • __set__(self, obj, value) -> None

  • __delete__(self, obj) -> None




Define any of these methods and an object is considered a descriptor and can override default behavior upon being looked up as an attribute.

  • If an object defines __set__() or __delete__() , it is considered a data descriptor
  • Descriptors that only define __get__() are called non-data descriptors

NOTEData descriptors always override instance dictionaries.

The example we saw above was of a data descriptor. Consider following example

class Name:

    def __get__(self, instance, owner):
        print("Invoking __get__ on Name")
        if not isinstance(instance, Employee):
            raise ValueError("Name descriptor is to be used with Employee instance only")
        return instance._name


class Employee:
    name = Name()

    def __init__(self, name):
        self._name = name


emp = Employee("Aniket")
print(emp.name)
emp.name = "Abhijit"
print(emp.name)


Here we do not have a __set__ method and consequently, it is not a data descriptor hence we can override it with instance dictionaries. Above prints
Invoking __get__ on Name
Aniket
Abhijit
Notice how it involved __get__ exactly once and when we set it to "Abhijit" it actually replaced the name from a data descriptor instance to a normal string stored in the instance dictionary.


One last thing is that as you see above a class is data descriptor if it has __set__ or __delete__ so if you do not have __set__ but just have delete then it is still a data descriptor and you cannot override in the instance dictionary.

class Name:

    def __get__(self, instance, owner):
        print("Invoking __get__ on Name")
        if not isinstance(instance, Employee):
            raise ValueError("Name descriptor is to be used with Employee instance only")
        return instance._name

    def __delete__(self, instance):
        del instance._name


class Employee:
    name = Name()

    def __init__(self, name):
        self._name = name


emp = Employee("Aniket")
print(emp.name)
emp.name = "Abhijit"
print(emp.name)

The above code will fail by printing
Invoking __get__ on Name
Aniket
Traceback (most recent call last):
  File "/Users/aniketthakur/PycharmProjects/HelloWorld/descriptors.py", line 29, in <module>
    emp.name = "Abhijit"
AttributeError: __set__

and that is because it could not find a __set_ method.

Related Links

t> UA-39527780-1 back to top