Search code examples
javascriptreactjsreact-admin

Updating context value in react-admin causes a router history warning


I'm using context to store a global piece of information which is updated when navigating around the web app from a Link component. For some reason, when I update the context data, I get a "Warning: You cannot change <Router history>" in the console. My data has no history object in it, just a value and a function to update. What could cause this?

Here is my setup:

const AppContext = createContext({
    appData: {},
    setAppData: () => {},
});

const App = () => {

  const [appData, setAppData] = useState({
    foo: null,
  });

  return (
    <AppContext.Provider value={[appData, setAppData]}>
        <Admin title="My Admin App">
          <Resource name="foo" show={ShowFoo} />
        </Admin>
    </AppContext.Provider>
  )
};

My menu has nav links using this component:

export const MyMenuLink = ({ primaryText, to, leftIcon, sidebarIsOpen, onMenuClick, dense, foo }) => {
    
    const clickHandler = (e) => {
        const newAppData = {
            ...appData,
            foo: foo,
        };
        setAppData(newAppData);    
    };

    return (
        <Link to={to} onClick={clickHandler}>                            
            <Typography variant="inherit">{primaryText}</Typography>            
        </Link>
    );
};

So when I click on that link, I get the router history warning, but if I remove the setAppData() call in clickHandler, it disappears.

Thanks


Solution

  • Warning: You cannot change <Router history>

    This is a message coming from React Router. React router supports different history-objects, but usually you would connect to a BrowserHistory and allow the browsers to handle and propagate route changes to the router.
    IE pressing the browsers forward/backward buttons will trigger a re-render, not a total page refresh.

    React-admin

    React-router is also used by React-Admin.
    You can pass along a history into the root <Admin /> component. If you don't set a history, react-admin will create one for you.

    Passing in a history is as simple as:

    import {createBrowserHistory} from "history";
    
    const history = createBrowserHistory();
    
    const App = ()=>{
    return (<Admin
            dashboard={Dashboard}
            authProvider={authProvider}
            dataProvider={dataProvider}
            history={history}
          />)
    }
    
    

    Rerenders

    React-router doesn't want the history reference to change.
    If you attempt to change it, you'll see the warning pop up.
    React will re-render child components when something in the parent changes.
    In your case, the context will re-render their children whenever a new value is received in the provider. (basically when calling setAppData)
    This will trigger a rerender on your <Admin> component.
    Since we didn't pass in a history, a new one is created and passed to the router. probably here
    You will then see the warning from react router.

    Passing in your own router would solve that.

    sandbox

    You can try it in this sandbox. If you comment out the the history prop, you will receive warnings again.
    There's a link on the dashboard to trigger the re-render.