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

Separate history object to its own context #7103

Merged
merged 4 commits into from May 8, 2020

Conversation

illuminist
Copy link
Contributor

Reference #6999

As a suggestion from @MeiKatz . I added a new context for history object only to be used for useHistory hook and prevent the hooks from getting update triggerred by the original context.

The original context still has all the same properties without any change, the history object is also in the original context.

Tested with my project and it reduced rerendering greatly.

@timdorr
Copy link
Member

timdorr commented Jan 20, 2020

Besides useHistory, are there other places we can use the HistoryContext instead of RouterContext?

packages/react-router/modules/hooks.js Outdated Show resolved Hide resolved
packages/react-router/modules/hooks.js Outdated Show resolved Hide resolved
packages/react-router/modules/Router.js Show resolved Hide resolved
Copy link
Contributor Author

@illuminist illuminist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's so embarrassing I missed that.

@illuminist
Copy link
Contributor Author

illuminist commented Jan 21, 2020

Besides useHistory, are there other places we can use the HistoryContext instead of RouterContext?

Other components are still relying on other properties from the main context. I think I can separate other context properties as well, so there will be separate context for each properties: history, location, match, and staticContext. Should I do that now or wait until this branch get merged?

Edit: Forgot to check react-router-dom, found some potential candidates.

@esealcode
Copy link

esealcode commented Feb 1, 2020

Hi,

Sorry for not being much present in the process of my issue the last weeks.

@illuminist about separating each props into their own contexts i'm not sure of the benefit it would give since the original issue was about history prop still getting dispatched along other updated props (while remaining unchanged itself) on a route change. However when a route change occurs it's very likely that location and match props will both be updated in the process meaning separating them into seperate contexts probably won't add any performance improvement.

As for the staticContext i'm not sure why it should be touched at all as it seems to be related to SSR (not using SSR here sorry).

Since the original issue was just about the useHistory performance concern and the pull request actually addresses the issue it should be a go for the merge and moving the separation of the others props to a new issue if needed.

Great work :)

@stale stale bot added the stale label Apr 1, 2020
@timdorr timdorr added the fresh label Apr 2, 2020
@stale stale bot removed the stale label Apr 2, 2020
@remix-run remix-run deleted a comment from stale bot Apr 2, 2020
@djamaile
Copy link

djamaile commented May 7, 2020

When will this be merged?

@jamesthomsondev
Copy link

Also wondering when this will get merged. We're experiencing a lot of component re-renders due to useHistory.

@illuminist illuminist requested a review from timdorr May 8, 2020 07:24
@illuminist
Copy link
Contributor Author

@timdorr There isn't much need to change right now from last time I committed. Do you think is it good to merge?

@AlithAnar
Copy link

+1 for merge

@timdorr
Copy link
Member

timdorr commented May 8, 2020

Yeah, let's do it. I'll see if I can get this out soon.

@timdorr timdorr merged commit fc53733 into remix-run:master May 8, 2020
@Kutalia
Copy link

Kutalia commented May 15, 2020

What if I want to force component rerendering on history changes while maintaining useHistory?
Let's say I have navigation buttons in my router component. Using the history object I can deduce whether I am on the first page (history.index === 0) or on the last page (history.index === history.length - 1) and show or hides buttons accordingly. But I need to rerender the component on each history.push.
I've found a hack for it - useLocation. It acts like a subscriber for history updates. Is there a cleaner way to make it work?

@esealcode
Copy link

esealcode commented May 15, 2020

@Kutalia The history object never changes (it's a mutable object). This is what caused my initial issue which led to this PR because the history object was shipped along some other props (like location) in the same context together causing unnecessary updates from the useHistory when location changed.

Hence if you were relying on useHistory to listen for location updates you were doing it wrong even if it unintendedly worked. You want to use history.listen (see: https://github.com/ReactTraining/history/blob/2e9e5118a33e5b62ac3eb74f2fbc80d7e7a30dde/docs/GettingStarted.md#basic-usage) to listen for every location changes and read history props from there to determine if you need to update your component or not.

@Kutalia
Copy link

Kutalia commented May 16, 2020

I need to track history updates (more specifically, history.index and history.length, I am using a MemoryRouter), and rerender a component. I found a workaround:

const history = useHistory();
const [, forceUpdate ] = useState();

useEffect(() => {
    return history.listen(() => {
        forceUpdate({});
    });
}, [history]);

But I am afraid forcing component rerendering in a such way is not a recommended pattern.

@illuminist
Copy link
Contributor Author

illuminist commented May 17, 2020

@Kutalia Another workaround is just putting useLocation into the same component as it will trigger component update when location change which will also depend on history state.

But the best solution should be creating a custom hook that provide the information about history object.

const useHistoryState = () => {
  const history = useHistory()
  const [index, setIndex] = useState(history.index)
  const [length, setLength] = useState(history.length)
  useEffect(()=> {
    return history.listen(() => {
      setIndex(history.index)
      setLength(history.length)
    })
  }, [history])
  return { index, length }
}

You can add more property from history object as you like.

@Kutalia
Copy link

Kutalia commented May 17, 2020

Thanks for the suggestion. As I actually use history.goBack and history.goForward as well I think I'd better stick with the "forceUpdate" method instead of returning them separately from the custom hook. This way I'll avoid over-abstraction.

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

Successfully merging this pull request may close these issues.

None yet

10 participants