Skip to content

Instantly share code, notes, and snippets.

@AlexandrHoroshih
Last active April 24, 2024 05:13
Show Gist options
  • Save AlexandrHoroshih/231b6c22608e1e1baafa1f8c69a4ab02 to your computer and use it in GitHub Desktop.
Save AlexandrHoroshih/231b6c22608e1e1baafa1f8c69a4ab02 to your computer and use it in GitHub Desktop.
effector + history
import { matchPath, RouteProps } from "react-router";
// historyUpdated event is subscribed to history via history.listen or any other way
export const createPathMatcher = <Match = unknown>(config: {
path: string | string[] | RouteProps;
clock?: Event<any> | Store<any> | Effect<any, any>;
}) => {
return sample({
source: historyUpdated,
clock: config.clock ?? historyUpdated,
fn: (update) => {
const { location } = update;
return location ? matchPath<Match>(location.pathname, config.path) : null;
},
}).filterMap((match) => (match ? match : undefined));
};
// usage
const formMatched = createPathMatcher<{ id: string }>({
path: "/form/edit/:id"
});
// formMatched has { params: { id: string }, // + match meta fields } payload
// formMatched will be called only if matchPath returned match object (it can also return null, if no match)
// usage with route props
const homeMatched = createPathMatcher({
path: {
path: "/home",
exact: true
}
});
@AlexandrHoroshih
Copy link
Author

AlexandrHoroshih commented Oct 10, 2021

An option to connect historyUpdated event to history

const history = createBrowserHistory()
const $history = createStore(history)

$history.watch(appStarted, history => {
  // we also want to react to history's initial state, so we call this event manually too
  historyUpdated({ location: history.location, action: history.action });
  
  history.listen(() => {
    historyUpdated({ location: history.location, action: history.action });
  });
});

If app uses SSR

// if app uses SSR or works in scope, then it must use scopeBind like this
// appStarted event should be called on correct scope
$history.watch(appStarted, history => {
  historyUpdated({ location: history.location, action: history.action });
  
  // scopeBind will bind event to correct scope from internal effector context
  // for more details: https://effector.dev/docs/api/effector/scopeBind
  // not needed for SPA apps
  const listener = scopeBind(historyUpdated)

  history.listen(() => {
    listener({ location: history.location, action: history.action });
  });
});

Later

const App = () => {
 const start = useEvent(appStarted)
 
 React.useLayoutEffect(() => start(), [])
 
 return (
  <Router history={history}>
     ...
  </Router>
 )
}

@AlexandrHoroshih
Copy link
Author

An option to add effector api to history methods

const fxPush = attach({
  source: $history,
  effect: (history, params: To) => {
    history.push(params);
  }
});

@AlexandrHoroshih
Copy link
Author

matchPath as effect

export const createPathMatcher = <Match = unknown>(config: {
  path: string | string[] | RouteProps;
  clock?: Event<any> | Store<any> | Effect<any, any>;
}) => {
  const matchPathFx = createEffect((update) => {
      const { location } = update;

      return location ? matchPath<Match>(location.pathname, config.path) : null;
  })

  sample({
    source: historyUpdated,
    clock: config.clock ?? historyUpdated,
    target: matchPathFx
  })
  
  return guard({
    clock: matchPathFx.doneData,
    filter: Boolean
  })
};

// usage
const formMatched = createPathMatcher<{ id: string }>({
  path: "/form/edit/:id"
});


// formMatched has { params: { id: string }, // + match meta fields } payload
// formMatched will be called only if matchPath returned match object (it can also return null, if no match)

// usage with route props
const homeMatched = createPathMatcher({
  path: {
   path: "/home",
   exact: true
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment