Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing the event map in runtime #276

Open
4 of 14 tasks
cwayfinder opened this issue Aug 3, 2018 · 6 comments
Open
4 of 14 tasks

Changing the event map in runtime #276

cwayfinder opened this issue Aug 3, 2018 · 6 comments

Comments

@cwayfinder
Copy link

cwayfinder commented Aug 3, 2018

This is a...

  • 🪲 Bug Report
  • 🚀 Feature Request
  • 📜 Documentation Request

Which version of Redux Beacon are you using?

  • 2.0.3

Which target(s) are you using?

  • Google Analytics
  • Google Analytics (gtag)
  • React Native Google Analytics
  • Google Tag Manager
  • React Native Google Tag Manager
  • Amplitude
  • Segment
  • Other/Third Party: ...(Qubit)

🚀 📜 What's missing from Redux Beacon that you'd like to add?

I wish I had a possibility to change event map (passed to the metareducer of my ngrx store) in runtime. Our team needs to adjust analytics events data without rebuilding the application

Can you help out?

  • 🌟 I am a legend and can get started on a pull request right away given the go-ahead.
  • ⭐ I am a superstar and would like to help out given some guidance.
  • 😞 I won't be able to help out on this one.
@cwayfinder cwayfinder changed the title Changing event map in runtime Changing the event map in runtime Aug 3, 2018
@ttmarek
Copy link
Contributor

ttmarek commented Aug 3, 2018

huh, interesting. Are you doing something like:

if user x, track these analytics, else if user y, track these analytics

@cwayfinder
Copy link
Author

cwayfinder commented Aug 3, 2018

No. We need to modify the EventDefinitionsMap in the application without rebuild. Sometimes we need to change an event definition and we just don't want to wait for a new release of our app to do that.

@cwayfinder
Copy link
Author

The only solution I see now is to fetch the configuration from the server as JSON (instead of using your powerful EventDefinitionsMap) via XHR. And create my own meta reducer to apply static definitions declared in that JSON.

@ttmarek
Copy link
Contributor

ttmarek commented Aug 13, 2018

@cwayfinder

Sorry for the late reply on this. Had a busy week work-wise last week, and am in the middle of a move so my evenings/weekends are a bit more busy than usual.

I'm not sold on adding support for this sort of behaviour to the main redux-beacon package. Mostly because it seems like a fairly unique use-case. Is that fair?

But...we should be able to design something pretty easily based on the existing createMiddleware and createMetaReducer functions.

Maybe something like:

const getEvents = (eventsMap: EventsMap | EventsMapper) =>
    typeof eventsMap === 'function'
      ? action => flatten<EventDefinition>([eventsMap(action)])
      : action => getEventsWithMatchingKey(eventsMap, action.type);

function createMiddlewareDynamic(
  eventsMap: EventsMap | EventsMapper,
  target: Target,
  extensions: Extensions = {}
) {
  // save the initial (default) events definitions map
  let currentEventsMap = eventsMap;

  // a function that we expose to update currentEventsMap
  const updateEventsMap = (newEventsMap: EventsMap | EventsMapper) => {
     currentEventsMap = newEventsMap;
  };

  // the middleware
  const middleware = store => next => action => {
    const prevState = store.getState();
    const result = next(action);
    const nextState = store.getState();

    const events = createEvents(
      getEvents(currentEventsMap)(action),
      prevState,
      action,
      nextState
    );

    registerEvents(events, target, extensions, prevState, action, nextState);

    return result;
  };

  return { middleware, updateEventsMap };
}

And you'd use the function like so:

const { middleware, updateEventsMap } = createMiddlewareDynamic(initialEventsMap, gtmTarget(), { logger })

// use updateEventsMap wherever
updateEventsMap(newEventsMap)

Also, the function above would require access to the following utility functions in the main redux-beacon package:

import createEvents from './create-events';
import getEventsWithMatchingKey from './get-events-with-matching-key';
import registerEvents from './register-events';

I don't think I expose these. So for now, if you want to test it out I would copy-paste the compiled versions of these functions. If you need help with that, let me know.

@mike1808
Copy link

mike1808 commented Apr 15, 2019

I'll like to add one use case for this feature. As Redux based application grows there are several solutions to make it load faster:

  1. have several apps
  2. code split modules/pages

In our company, we're doing 2. so in order to use the redux-beacon we need to have the ability to lazy load the events reducers.

For initial app load we fetch only actions/thunks/components that are required for that page. When a user navigates to another page we lazy-load their required files.

If we are going to use redux-beacon as is, we will introduce a dependency graph to all trackable actions that will cause including all that files with actions to the initial load chunk (we're using Webpack) and make the initial JS file quite big. So, having something similar to redux#replaceReducer will help to solve the issue.

@ygr1k
Copy link

ygr1k commented Aug 25, 2023

Thanks all for the comments on this issue!
I'll share my implementation for code splitting in pure JS based on https://github.com/dbartholomae/redux-dynamic-modules-beacon

class EventsManager {
  #eventsMaps = [];

  getEventsMap = action => {
    const eventsMapByAction = this.#eventsMaps
      .map(eventMapOrMapper =>
        typeof eventMapOrMapper === 'function'
          ? eventMapOrMapper(action)
          : eventMapOrMapper[action.type],
      )
      .flat();

    return eventsMapByAction;
  };

  addEventsMap(eventMapOrMapper) {
    this.#eventsMaps.push(eventMapOrMapper);
  }

  removeEventsMap(eventMapOrMapper) {
    this.#eventsMaps = this.#eventsMaps.filter(map => map !== eventMapOrMapper);
  }
}
import { createMiddleware } from 'redux-beacon';

function createManageableMiddleware(eventsMap, target, extensions) {
  const manager = new EventsManager();
  manager.addEventsMap(eventsMap);

  // Setup the event middleware
  const middleware = createMiddleware(manager.getEventsMap, target, extensions);

  return {
    middleware,
    manager,
  };
}
//enhance store with manageable middleware + attach events manager to the redux store in configureStore func
store.eventsManager = manageableMiddleware.manager;
// hook for attach/detach events
import { useStore } from 'react-redux';
import { useEffect, useRef } from 'react';
import { isNull } from 'lodash';

function useEventsMap(eventsMap) {
  const store = useStore();
  const eventsMapWasAttached = useRef(null);

  useEffect(() => {
    if (isNull(eventsMapWasAttached.current)) {
      store.eventsManager.addEventsMap(eventsMap);
      eventsMapWasAttached.current = true;
      console.log(`useEventsMap, events map attached:`, eventsMap);
    }

    return () => {
      if (eventsMapWasAttached.current) {
        store.eventsManager.removeEventsMap(eventsMap);
        eventsMapWasAttached.current = null;
        console.log(`useEventsMap, events map detached:`, eventsMap);
      }
    };
  }, []);
}
//usage example
import {trackEvent} from "@redux-beacon/google-analytics-gtag";
const eventsMap = {
    [ON_ACTION_TYPE_CONST_SUBMIT_THIS_EVENT]: trackEvent(() => {...}),
};

function SomeComponent(){
    //on did mount life cycle attach events from this map, and detach it on unmount
    useEventsMap(eventsMap);

    return (
        ...
    )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants