Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
June 9, 2022 02:11 pm GMT

Redux Without React

In this blog i will try to explain redux library without react. Just Redux.

Why? Because if we understand redux then well understand react-redux (redux bindings for react) better.

So Redux is a relatively very small API

  • compose
  • createStore
  • bindActionCreators
  • combineReducers
  • applyMiddleware

Yes, just these five functions or methods. We will understand each one by one.

Lets start with compose.

compose

Its just a utility that come along with redux, which is just a javaScript function that takes functions as arguments and returns a new function that executes them from right to left. Yup thats it.

To understand that better, lets say we have a have a bunch of functions that take a string

const makeUppercase = (string) => string.toUpperCase();const logString = (string) => console.log(string) const boldString = (string) => string.bold();

Wecouldcall them all like this:

logString(makeUppercase(boldString("redux")));

But, lets say if we wanted to pass this function as an argument to another function what would we do?

const boldenTheStringAndUppcaseItThenLogIt = (string) => logString(makeUppercase(boldString(string)));

This could take too long , so we compose . Which helps use make a new function that will execute all the function passed as arguments to compose(args)

const boldenTheStringAndUppcaseItThenLogIt = redux.compose(logString , makeUppercase, boldString)boldenTheStringAndUppcaseItThenLogIt("")

this will execute the functions from right to left(boldString makeUppercase logString) as redux as argument sting, and were done with compose. That is what compose is.

So far we have covered 20% for the redux API

Now lets understand createStore . Create store creates a store. A store is where we keep all our state.

But it doesnt just create a store, however

// reducer is a function that we need to pass to the createStore,// we will cover what a reducer shortly..let store = redux.createStore(reducer) console.log(store)// dispatch:  dispatch() {}// subscribe:  subscribe() {}// getState:  getState() {}// replaceReducer:  replaceReducer() {}

four more functions

  • dispatch
  • subscribe
  • getState
  • replaceReducer

But, what is a reducer after all ?

A reducer is also just a function that takes in two arguments

  • state (state of the application)
  • action (an action is event for example , web socket messages, action functions , etc.)

and it returns the new state.

so basically ,a reducer is a function where the first argument is the current state of application and the second is something that happened. Somewhere inside the function, we figure out what the new state of the world should to be based on whatever happened.

// this is not how you should write a reducer , just for exampleconst reducer = (state, action) => state //(new state)

Lets try to understand this with an example. Lets say we have a counter app, and we want to increment the counter, we will have an increment action.

now, actions are also just functions, and they need to have only have type,

const initialState = { value: 0 };const reducer = (state = initialState, action) => {  if (action.type === "INCREMENT") {    return { value: state.value + 1 };  }  return state;};// which can also be written asconst initialState = { value: 0 };const INCREMENT = "INCREMENT"; // const incrementCounterAction = { type: INCREMENT }; // only requires type ,  // others are optional // payload : {}, meta: {}, error.const reducer = (state = initialState, action) => {  if (action.type === INCREMENT) {    return { value: state.value + 1 };  }  return state;};

Alright, there are a few things that we need to understand here.

  • You'll notice that we're creating a new object rather than mutating the existing one.
  • This is helpful because it allows us to figure out what the new state is depending on the current state
  • We also want to make sure we return the existing state if an action we don't care about comes through the reducer.

You will also notice we made a constant calledINCREMENT. The main reason we do this is because we need to make sure that we dont accidentally mis-spell the action typeeither when we create the action or in the reducer.

Lets say we have a counter which has an increment button and add input , which increment the counter and add to the counter whatever value we put in the input.

const initialState = { value: 0 }; // state of the applicationconst INCREMENT = "INCREMENT"; // constants const ADD = "ADD"; // constants// action creators (fancy name for functions)const increment = () => ({ type: INCREMENT });const add = (number) => ({ type: ADD, payload: number });const reducer = (state = initialState, action) => {  if (action.type === INCREMENT) {    return { value: state.value + 1 }; // new state  }  if (action.type === ADD) {    return { value: state.value + action.payload }; // new state  }   return state; // default state };

So far we have understood compose and what createStore does and the four functions it creates, what a reducer and action is. But we need to still understand the four functions createStore creates.

So lets start with

  • getState (this function returns the current value of the state in the store)
  • dispatch (how do we get actions into that reducer to modify the state? Well, we dispatch them.)
const store = createStore(reducer);console.log(store.getState()); // { value: 0 }store.dispatch(increment());console.log(store.getState()); // { value: 1 }
  • susbscribe - This method takes a function that is invoked whenever the state in the store is updated.
const subscriber = () => console.log('this is a subscriber!' store.getState().value);const unsubscribe = store.subscribe(subscriber);store.dispatch(increment()); // "Subscriber! 1"store.dispatch(add(4)); // "Subscriber! 5"unsubscribe();
  • replaceReducer used for code splitting

bindActionCreators

Earlier in the blog we read about action (which are functions). So guess what bindActionCreators does? ... binds actions functions together.!!

In the example below we have actions and reducers

const initialState = { value: 0 }; // state of the applicationconst INCREMENT = "INCREMENT"; // constants const ADD = "ADD"; // constants// action creators (fancy name for functions)const increment = () => ({ type: INCREMENT });const add = (number) => ({ type: ADD, payload: number });const reducer = (state = initialState, action) => {  if (action.type === INCREMENT) {    return { value: state.value + 1 }; // new state  }  if (action.type === ADD) {    return { value: state.value + action.payload }; // new state  }   return state; // default state };-----------------------------------------------------------------------------const store = createStore(reducer);/// notice we have to do this like this everytime store.dispatch(increment());

Notice we have to dispatch the actions store.dispatch(increment()) every time, this could be tedious in a large application.

we could do this in a cleaner way like this

const dispatchIncrement = () => store.dispatch(increment());const dispatchAdd = (number) => store.dispatch(add(number));dispatchIncrement();dispatchAdd();

or we could use compose . remember compose for earlier in the blog ?

const dispatchIncrement = compose(store.dispatch, increment);const dispatchAdd = compose(store.dispatch, add);

We could also do this using bindActionCreators , takes two arguments

  • action creators (action functions)
  • dispatch
const actions = bindActionCreators(  {    increment,    add,  },  store.dispatch);actions.increment();

Now we dont have to use bindActionCreators all the time, but its there.

With that we have completed 60% of the redux API.

combineReducers

Guess what this does ? Yes, combines reducers. When we have big applications we have many reducers.

For example when we have a blog application. We would have reducers for storing the user information, reducers for storing the blogs, reducers for storing the comments.

In these big application we split the reducers into different files. combineReducers is used when we have multiple reducers and want to combine them .

Say we have an application with users and tasks. We can assign users and assign tasks

const initState = {  users: [    { id: 1, name: "ved" },    { id: 2, name: "ananya" },  ],  tasks: [    { title: "some task", assignedTo: 1 },    { title: "another task", assignedTo: null },  ],};

reducer file

const ADD_USER = "ADD_USER";const ADD_TASK = "ADD_TASK";const addTask = title => ({ type: ADD_TASK, payload: { title } });const addUser = name => ({ type: ADD_USER, payload: { name } });const reducer = (state = initialState, action) => {  if (action.type === ADD_USER) {    return {      ...state,      users: [...state.users, action.payload],    };  }  if (action.type === ADD_TASK) {    return {      ...state,      tasks: [...state.tasks, action.payload],    };  }};const store = createStore(reducer, initialState);store.dispatch(addTask("Record the song"));store.dispatch(addUser("moksh")); // moksh is my brother's name  console.log(store.getState());

It would be nice if we could have two different reducers for the users and the tasks

const users = (state = initialState.users, action) => {  if (action.type === ADD_USER) {    return [...state, action.payload];  }  return state;};const tasks = (state = initialState.tasks, action) => {  if (action.type === ADD_TASK) {    return [...state, action.payload];  }  return state;};// now we need to combine the reducers and we do that using combineReducersconst reducer = redux.combineReducers({ users, tasks });const store = createStore(reducer, initialState);

Fun Fact!: All actions flow through all of the reducers. So, if you want to update two pieces of state with the same action, you totally can

applyMiddleware

Theres a lot of stuff that Redux can not do by itself, so we can extend what Redux does using middleware and enhancers.

Now what are middleware and enhancers ? Theyre just functions that let us add more functionality to redux.

To be more accurate, enhancers are functions that take a copy of createStore and a copy of the arguments passed tocreateStore beforepassing them tocreateStore. This allows us to make libraries and plugins that will add more capabilities how the store works.

We see enhancers in use when we use the Redux Developer Tools and when we want to dispatch asynchronous actions.

The Actual API forcreateStore()

createStore()takes one, two, or three arguments.

  • reducer (required)
  • initialState(Optional)
  • enhancer(Optional)

Lets try to understand this with an example, below is an enhancer that monitors the time it takes to update a state.

const monitorUpdateTimeReducerEnhancer = (createStore) => (  reducer,  initialState,  enhancer) => {  const monitorUpdateTimeReducer = (state, action) => {    const start = performance.now();    const newState = reducer(state, action);    const end = performance.now();    const diff = round(end - start);    console.log("Reducer process time is:", diff);    return newState;  };  return createStore(monitorUpdateTimeReducer, initialState, enhancer);};const store = createStore(reducer, monitorReducerEnhancer);

If its still not clear , let look at another example that logs the old state and new state using enhancers.

const reducer = (state = { count: 1 }) => state;const logStateReducerEnhancer = (createStore) => (  reducer,  initialState,  enhancer) => {  const logStateReducer = (state, action) => {    console.log("old state", state, action);    const newState = reducer(state, action);    console.log("new state", newState, action);    return newState;  };  return createStore(logStateReducer, initialState, enhancer);};const store = createStore(  reducer,  logStateReducerEnhancer));store.dispatch({ type: "old state" });store.dispatch({ type: "new updated state" });

Ok cool, but what is applyMiddleware used for? its used for creating enhancers out of chain of middleware. What does that mean , maybe be some code would help understand better.

const enhancer = applyMiddleware(  firstMiddleware,  secondMiddleware,  thirdMiddleware);

So Middleware have the following API

const someMiddleware = (store) => (next) => (action) => {  // Do stuff before the action reaches the reducer or the next piece of middleware.  next(action);  // Do stuff after the action has worked through the reducer.};

nextis either the next piece of middleware or it'sstore.dispatch. If you don't callnext, you will swallow the action and it will never hit the reducer.

Here is an example of the logState middleware that we looked at earlier using this

const logStateMiddleware = (store) => (next) => (action) => {  console.log("State Before", store.getState(), { action });  next(action);  console.log("State After", store.getState(), { action });};const store = createStore(reducer, applyMiddleware(logStateMiddleware));

Lets also see how we can do this with the monitorUpdateTime

const monitorUpdateTimeMiddleware = (store) => (next) => (action) => {  const start = performance.now();  next(action);  const end = performance.now();  const diff = Math.round(end - start);  console.log("Reducer process time is:", diff);};const store = createStore(reducer, applyMiddleware(monitorUpdateTimeMiddleware));

With this we have covered all of the Redux API. I hope this blog was helpful and you learnt something new.


Original Link: https://dev.to/vedanthb/redux-without-react-558e

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To