New React Context API + LocalStorage + Subscribe (Replacement for Redux)
Asked Answered
C

2

7

I have an app using Redux. Is stores the global state as shown below:

Create Store:

import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';

const configureStore = initialState => {
  return createStore(
    rootReducer,
    initialState,
    applyMiddleware(thunk)
  );
};

export default configureStore;

Handles Local Storage

  const storageName = 'xxxState';
  export const loadState = () => {
  try {
    const serializedState = localStorage.getItem(storageName);
    if (serializedState === null) {
      return undefined;
    }
    return JSON.parse(serializedState);
  } catch(err) {
    return undefined;
  }
};

export const saveState = state => {
  try {
    const serializedState = JSON.stringify(state);
    localStorage.setItem(storageName, serializedState);
  } catch(err) {
    throw err;
  }
};

Finaly starts the app session:

import React from 'react';
import ReactDOM from 'react-dom';
import Start from './start';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import { loadState, saveState } from './store/localStorage';
import throttle from 'lodash/throttle';

const persistedState = loadState();

const store = configureStore(persistedState);
store.subscribe(throttle(() => {
  saveState(store.getState());
}, 1000));

ReactDOM.render(
  <Provider store={store}>
    <Start />
  </Provider>,
  document.getElementById('root'));

It works perfectly. So my question is: Is there any possibility to do the same with the new React Context API?

I am pretty confortable using the new context, I just want to know about store, localstorage, subscribe. So the the state is persisted even when the user leaves the app.

Continuity answered 3/8, 2018 at 12:15 Comment(0)
O
4

store.js

import React, { Component, createContext } from "react";

export const createStore = store => {
  const Context = createContext();
  let self;
  const setState = (action, state, args) => {
    self.setState(state);
  };
  const actions = Object.keys(store.actions).reduce(
    (accumulator, currentValue) => {
      return {
        ...accumulator,
        [currentValue]: function(...args) {
          let result = store.actions[currentValue](
            { state: self.state, actions: self.value.actions },
            ...args
          );
          if (result) {
            result.then
              ? result.then(result => setState(currentValue, result, args))
              : setState(currentValue, result, args);
          }
        }
      };
    },
    {}
  );
  class Provider extends Component {
    constructor() {
      super();
      this.state = store.initialState;
      self = this;
      this.value = {
        actions,
        state: this.state
      };
    }
    render() {
      if (this.state !== this.value.state) {
        this.value = {
          actions,
          state: this.state
        };
      }
      return (
        <Context.Provider value={this.value}>
          {this.props.children}
        </Context.Provider>
      );
    }
  }
  class Consumer extends Component {
    renderProps = ({ actions, state }) => {
      // const { mapStateToProps, mapDispatchToProps } = this.props;
      return this.props.children({
        state: state,
        actions: actions
      });
    };
    render() {
      return <Context.Consumer>{this.renderProps}</Context.Consumer>;
    }
  }
  const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => {
    return (
      <Consumer
        mapStateToProps={mapStateToProps}
        mapDispatchToProps={mapDispatchToProps}
      >
        {injectedProps => (
          <WrappedComponent {...injectedProps} {...this.props} />
        )}
      </Consumer>
    );
  };
  return {
    Provider,
    Consumer,
    connect
  };
};

Then for individual pages you can have actions and initial store value like this

import { createStore } from "@src/store";
import { Actions } from "../actions";
import { storage } from "@helpers/storage";
import constants from "@src/constants";

import { util } from "@src/utils";

function getInitialState() {
  let Id = null;
  if (storage.get(constants.STORAGE_KEY)) {
    let storageObject = storage.get(constants.STORAGE_KEY);
    Id = storageObject["Id"];
    selectedDate = storageObject["selectedDate"];
  }
  return {
    data: null,
    activeTab: "score",
    Id: Id
  };
}
const store = {
  initialState: getInitialState(),
  actions: Actions
};

export const { Provider, Consumer } = createStore(store);

Finally in your pages/HomePage/index.js you can have import { Provider } from './store';

render() {
  return (
    <Provider>
     <Homepage user={user} children={children} />
    </Provider>
  );
}

and in your pages/HomePage/Homepage.js you can have

render() {
      return (
        <Consumer>
          {({ state, actions }) => {
            return this.renderChildrens(state, actions);
          }}
        </Consumer>
      );
    }
  }
Offing answered 7/8, 2018 at 12:48 Comment(0)
O
2

First things first

  • Redux has nothing to do with react in specific, it is a state manager.
    it helps you manage a big JavaScript object that holds the state of your application.

  • react's Context API is not even a new feature in react, it was there the whole time and it just got a new face. It's a way to pass down data to children without prop drilling.

I think you are referring to react-redux.
react-redux is an abstraction, a way to bind your Redux store to react. It's doing all the store subscription for you and help you create a Container and consumers using the connect HOC and the Provider component.
It also helps with passing down the store object (via the context API under the hood).

If you are using Redux already, i don't see any point for not using react-redux.

You can read in more details about the differences In this post (full disclosure, I'm the author).

Orlena answered 7/8, 2018 at 16:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.