I'm new to using Redux, and I'm working on a test project to better understand how to use it in conjunction with React Router. My goal is to have the state updated with user input onClick in the route edi1 and to then use useSelector in route edi2 to display the updated state. Currently, in route edi1, I can see the state updating both through useSelector and the Redux Profiler in the Dev. Tools. However, in route edi2 it renders the initial state from the store but does not respond to any state changes. I have been getting these results by clicking Submit in edi1 to update the state and then in a separate tab viewing route edi2. Even if I refresh the tab with 'edi2' the state remains as the initial state.
I've been looking through both React Router and Redux Toolkit Docs. In the React Router Docs, they recommend a "Deep Redux Integration" if you require for "Synchronize the routing data with, and accessed from, the store." (https://v5.reactrouter.com/web/guides/deep-redux-integration). I'm not sure if that would apply to my case here, but even if it did in the past, the docs are for version 5, and I'm using the updated version of React Router 6.4.1.
I've been stuck on this issue for days, so I appreciate any guidance to help me move forward from here. Please see the CodeSandBox below to review the code. I have also included a screenshot of the Profiler from Dev tools. If further information is needed, I will be happy to provide it. Thank you for your time!
Code:
https://codesandbox.io/s/render-redux-state-react-router-y46wqk?file=/src/App.js
Similar Issue :
Redux Toolkit useSelector not updating state in React component
The solution for their issue was to update React-Redux. I have version 8.0.4 and I think that is the most updated version.
Profiler:
store.js:
import { configureStore } from '#reduxjs/toolkit'
import ediReducer from './ediSlice'
export const store = configureStore({
reducer: {
edi: ediReducer,
},
})
edislice.js:
import {createSlice} from '#reduxjs/toolkit';
const initialState = {
id:0, content: ["Hello World"],
}
export const ediSlice = createSlice({
name: 'edi',
initialState,
reducers: {
addContent: (state, action) => {
state.content.push(action.payload); // add new content to the array
// state.value = action.payload; // this will overwrite the array
state.id += 1 // this will increment the id
console.log(state.id)
},
}
})
export const {addContent} = ediSlice.actions;
export default ediSlice.reducer;
SendEdi.js:
import { Editor } from '#tinymce/tinymce-react';
import {useRef, useState} from "react";
import {useSelector, useDispatch} from "react-redux";
import {addContent} from "../components/ediSlice.js";
export default function SendEdi() {
const ediContent = useSelector((state) => state.edi.content);
const dispatch = useDispatch()
const editorRef = useRef();
const [entryArray, setEntryArray] = useState();
const log = () => {
if (editorRef.current) {
setEntryArray(editorRef.current.getContent());
dispatch(addContent(entryArray));
console.log("Editor Conent:", {ediContent})
}}
let entryEdi = <Editor
onInit={(evt, editor) => editorRef.current = editor}
apiKey='your-api-key'
disabled = {false}
inline = {false}
init={{
selector: "#entryEdi",
height: 500,
menubar: false,
placeholder: "Whats on your mind?",
plugins: [
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
'insertdatetime', 'media', 'table', 'code', 'help', 'wordcount'
],
toolbar: 'undo redo | blocks | ' +
'bold italic forecolor | alignleft aligncenter ' +
'alignright alignjustify | bullist numlist outdent indent | ' +
'removeformat | help',
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
}}
/>
return (
<div>
{ediContent}
{entryEdi}
<button onClick={log}>Submit</button>
</div>
)
}
RecEdi.js:
import {useSelector} from "react-redux";
export default function RecEdi() {
const ediContent = useSelector((state) => state.edi.content);
return (
<div>
{ediContent}
</div>
)
}
App.js:
import {
BrowserRouter as Router,
Routes,
Route
} from "react-router-dom";
import Edi1 from "./pages/edi1";
import Edi2 from "./pages/edi2";
function App() {
return (
<div className="App">
<Router>
<Routes>
<Route path="/Edi1" element={<Edi1 />} />
<Route path="/Edi2" element={<Edi2 />} />
</Routes>
</Router>
</div>
);
}
export default App;
edi1.js:
import SendEdi from "../components/SendEdi.js";
import { content } from "../components/ediSlice.js";
export default function Edi1() {
return (
<div>
<SendEdi />
<h1>edi1</h1>
</div>
);
}
edi2.js:
import RecEdi from "../components/RecEdi";
import { useSelector } from "react-redux";
export default function Edi2() {
// const edicontent = useSelector((state) => state.content.value)
return (
<div>
<RecEdi />
<h1>edi2</h1>
</div>
);
}
Index.js:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { store } from "./components/store.js";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
You do not need any React Router <-> Redux integration at all - you are reading up on the wrong topic here.
You are missing one point though: Redux is an in-memory router within that one tab you have open. So if you close the tab and open it again, it will initialize from the "initial state" again. The same goes for refreshing the page, or navigating in a way that unloads the page and navigating back to the page.
Depending on what you do here, that last part might be interesting: my guess is that while you navigate from page A to page B to see your result, you are doing so in a way that unloads the whole page and reloads it again, resulting in a complete re-initialization of your Redux store.
That would mean that you are navigating wrong (not using the components or methods provided by react-router), and that's what I'd be looking for.
Of course, if everything works and you want state to stay there over a page reload, you can look into something like redux-persist, but for right now you should find that navigation bug first or otherwise non-redux things will break as well.
Related
I am building my first React app and just added redux on it as it started getting too complex to pass states to my different components.
I have been following the official redux documentation as well as online tutorials and although everything seems to work fine, the app crashes whenever I reload the page and I receive the following error message "Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>".
Here is what my index.js looks like:
import React from 'react'
import ReactDOM from 'react-dom'
import './app.css'
import App from './App'
import { store } from './app/store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Here is my store.js:
import { configureStore } from '#reduxjs/toolkit';
import userReducer from "features/user";
import userInterfaceReducer from 'features/userInterface';
export const store = configureStore({
reducer: {
user: userReducer,
userInterface: userInterfaceReducer,
}
})
And my two slice reducers:
import { createSlice } from "#reduxjs/toolkit";
export const userSlice = createSlice({
name: "user",
initialState: { value: {userType: "", fName: "", lName: ""}},
reducers: {
user: (state, action) => {
state.value = action.payload;
}
}
})
export default userSlice.reducer;
and
import { createSlice } from "#reduxjs/toolkit";
export const userInterfaceSlice = createSlice({
name: "open",
initialState: { value: false}, // this state toggles a drawer present on several pages
reducers: {
toggleOpen: (state, action) => {
state.value = action.payload;
},
}
})
export const { toggleOpen } = userInterfaceSlice.actions;
export default userInterfaceSlice.reducer;
I read through dozens of posts, tried several of the solutions given, killed my app and restarted it several times but the problem persists.
The code in itself seems to work fine, I can access and update the states as I want to and they stay consistent through the different pages. But if I refresh any of the pages where I use useSelector, the app crashes. The redux devtools doesn't show any error until I refresh and then says "No store found. Make sure to follow the instructions."
Am I missing something?
Thanks in advance!
EDIT: it took me a while but I got it to work. Hopefully, this might help someone one day, so here is how I solved my issue:
Following tutorials, my redux Provider was wrapped around my App component in my index.js file. I moved it to the actual App component. Problem solved!
wrapping the provider directly in my app at app.js really helped
Heyo this is my first time using react/redux for a project and so I've been migrating my "navigation" to a redux style implementation where I (at the start here) want to change a store state (activePage) on the click of a button, and then my App should render whatever page is active through that activePage state.
I'm stuck (should emphasize that I'm not sure what is overboard/overwriting stuff or missing for this, I've followed a few online tutorials for action/reducer/store type stuff (and I was going to do a dispatch call but it seems like I can call changePage right from the button click instead of dispatching (had problems implementing the dispatch)) and I've been banging my head against the desk as to how action is undefined when import it...perhaps I'm not looking at it correctly...am I missing any data that would help diagnose this error?:
TypeError: Cannot read property 'type' of undefined
allReducers
.../client/src/redux/reducer/index.js:9
6 | activePage: 'HomePage',
7 | }
8 | function allReducers(state = initState, action){
9 | switch(action.type){
10 | case CHANGE_PAGE:
11 | return{
12 | ...state,
loginButton.js
const mapDispatchToProps = (state) => {
return {
changePage : state.activePage
}
};
function LoginButtonThing (){
return(
<div>
<button onClick={() =>(changePage('loginPage'))}>Login Page</button>
</div>
)
}
//export default LoginButtonThing;
export default connect(null,mapDispatchToProps)(LoginButtonThing);
actions.js
import {
CHANGE_PAGE,
} from "../constants/action-types";
export const changePage = (activePage) => ({
type: CHANGE_PAGE,
payload: {
activePage,
},
});
action-types.js
export const CHANGE_PAGE = "CHANGE_PAGE";
reducer/index.js
import {CHANGE_PAGE} from "../constants/action-types";
import {changePage} from "../actions/actions"
const initState = {
activePage: 'HomePage',
}
function allReducers(state = initState, action){
switch(action.type){
case CHANGE_PAGE:
return{
...state,
activePage :action.payload.activePage,
};
}
}
//const store = createStore(rootReducer);
export default allReducers;
App.js
class App extends React.Component {
render() {
//this.props.activePage = 'HomePage';
let renderedPage;
if (this.props.changePage === "LoginPage") renderedPage = <LoginPage/>;
else if (this.props.changePage === "HomePage") renderedPage = <HomePage/>;
else renderedPage = <HomePage/>;
//defaultStatus:activePage = "HomePage";
return (
<div id="App">
{renderedPage}
</div>
);
}
}
index.js
import {createStore, combineReducers} from 'redux';
import allReducers from "./redux/reducer"
//import LoginPage from "./loginPage";
import {Provider} from "react-redux";
const store = createStore(
allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
ReactDOM.render(
<Provider store = {store}>
<React.StrictMode>
<BrowserRouter>
<Route exact path="/" component = {HomePage}>
<Route path= "/loginPage" component ={LoginPage} />
</Route>
</BrowserRouter>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
Missing Default Case
Your reducer needs to return state if an unknown action is dispatched, such as the "init" action which sets your state to the initial value.
function allReducers(state = initState, action) {
switch (action.type) {
case CHANGE_PAGE:
return {
...state,
activePage: action.payload.activePage
};
default:
return state;
}
}
Mixing up State & Dispatch
The connect HOC takes two arguments: mapStateToProps and mapDispatchToProps. I think the names are self-explanatory. So why does your mapDispatchToProps function take state as an argument instead of dispatch?
If using connect, the LoginButtonThing should access a version of changePage which is already bound to dispatch from its props. You can use the action object shorthand like this:
import { connect } from "react-redux";
import { changePage } from "../store/actions";
function LoginButtonThing({ changePage }) {
return (
<div>
<button onClick={() => changePage("loginPage")}>Login Page</button>
</div>
);
}
export default connect(null, { changePage })(LoginButtonThing);
But you should use hooks, like this:
import { useDispatch } from "react-redux";
import { changePage } from "../store/actions";
export default function LoginButtonThing() {
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(changePage("loginPage"))}>Login Page</button>
</div>
);
}
Inconsistent Naming
This doesn't actually navigate to the LoginPage component! That's because LoginButtonThing updated the page to "loginPage" (lowercase), but App is looking for the string "LoginPage" (uppercase).
Don't Do This
There is a lot that's wrong with your code and a lot that's just sort of "dated" and we have better ways now, like hooks and Redux-Toolkit.
I actually think that moving navigation state into Redux like you are trying to do is not a good idea and I would recommend that you stick with react-router. You can read here why you don't need or want to do this.
So react-admin seems to have a feature where if you're idle for a little while and come back it will reload the data, presumably to make sure you're looking at the most up to date version of a record.
This is causing some issues for my editing feature that has some custom components. Is there a way to disable this auto-reload feature?
The auto-refresh is triggered by the loading indicator (the spinner icon that you see in the top right part of the app bar).
You can disable the auto-refresh by replacing the loading indicator by your own.
import * as React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { makeStyles } from '#material-ui/core/styles';
import CircularProgress from '#material-ui/core/CircularProgress';
import { useRefreshWhenVisible, RefreshIconButton } from 'react-admin';
const useStyles = makeStyles(
{
loader: {
margin: 14,
},
loadedIcon: {},
},
{ name: 'RaLoadingIndicator' }
);
const LoadingIndicator = props => {
const { classes: classesOverride, className, ...rest } = props;
useRefreshWhenVisible(); // <= comment this line to disable auto-refresh
const loading = useSelector(state => state.admin.loading > 0);
const classes = useStyles(props);
return loading ? (
<CircularProgress
className={classNames('app-loader', classes.loader, className)}
color="inherit"
size={18}
thickness={5}
{...rest}
/>
) : (
<RefreshIconButton className={classes.loadedIcon} />
);
};
LoadingIndicator.propTypes = {
classes: PropTypes.object,
className: PropTypes.string,
width: PropTypes.string,
};
export default LoadingIndicator;
You'll also need to put this button in a custom AppBar, inject your AppBar in a custom Layout, and use that Layout in your Admin, as explained in the react-admin Theming documentation.
I was able to turn off auto-refresh by dispatching the setAutomaticRefresh action from react-admin:
import { setAutomaticRefresh } from 'react-admin';
import { useDispatch } from 'react-redux';
// inside component
const dispatch = useDispatch();
dispatch(setAutomaticRefresh(false))
This action was added here as part of release 3.8.2.
I'm trying to switch from using redux in my new projects to using hooks. My understanding is that I can use a custom hook such as this one to an load it from various components to access the same state similar to how redux would let me access state.
import { useState } from 'react';
export const useSelectedStore = () => {
const [selectedStore, setSelectedStore] = useState();
return [
selectedStore,
setSelectedStore,
];
};
The issue that I'm having is that I have a page with an item where when the user clicks an item I need to set the selected store and then redirect them to the page. Here is the click action within the component:
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './pages/Home/Home';
import Stores from './pages/Stores/Stores';
import StoreDetails from './pages/StoreDetails/StoreDetails';
function App() {
return (
<Router>
<div>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/stores">
<Stores />
</Route>
<Route path="/store-details">
<StoreDetails />
</Route>
</Route>
</Switch>
</div>
</Router>
);
}
export default App;
import React, { useState } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { useSelectedStore } from '../../hooks/useSelectedStore';
function Stores(props) {
const { history } = props;
const [selectedStore, setSelectedStore] = useSelectedStore();
const goToStoreDetails = (index) => {
// Now add our store data
setSelectedStore(storeRequestDetails.stores[index]);
console.log(selectedStore);
history.push('/store-details');
};
The console log outputs undefined because the store hasn't been set yet so the history push happens before the value truly gets set.
I tried to use useEffect like this in the component to pick up on the change and handle the change before redirect but the state is getting reset on the component for the store-details page.
import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { useSelectedStore } from '../../hooks/useSelectedStore';
function Stores(props) {
const { history } = props;
const [selectedStore, setSelectedStore] = useSelectedStore();
useEffect(() => {
if (selectedStore) {
console.log(selectedStore);
history.push('/store-details');
}
}, [selectedStore]);
const goToStoreDetails = (index) => {
// Now add our store data
setSelectedStore(storeRequestDetails.stores[index]);
};
The console log here will show the actual selected store details but again once the next page loads like so the state is now undefined again.
import React from 'react';
import { useSelectedStore } from '../../hooks/useSelectedStore';
function StoreDetails() {
const [selectedStore, setSelectedStore] = useSelectedStore();
console.log(selectedStore); // Returns undefined
How do I share the state correctly between these two components?
My understanding is that I can use a custom hook such as this one to an load it from various components to access the same state similar to how redux would let me access state.
The custom hook you showed will cause multiple components to each create their own independent local states. These sorts of custom hooks can be useful for code reuse, but it doesn't let components share data.
If you want to share data between components, the general react approach is to move the state up the tree until it's at the common ancestor of every component that needs it. Then the data can be passed down either through props or through context. If you want the data to be globally available, then you'll move it to the top of the component tree and almost certainly make use of context rather than props.
This is what redux does: You set up the store near the top of the tree, and then it uses context to make it available to descendants. You can do similar things yourself, by using a similar pattern. Somewhere near the top of the tree (perhaps in App, but you could also move it elsewhere), create state, and share it via context:
export const SelectedStoreContext = React.createContext();
function App() {
const value = useState();
return (
<SelectedStoreContext.Provider value={value}>
<Router>
<div>
//etc
</div>
</Router>
</StoreContext.Provider>
);
}
And to use it:
function StoreDetails() {
const [selectedStore, setSelectedStore] = useContext(SelectedStoreContext);
If you want to call this useSelectedStore and slightly hide the fact that context is involved, you can create a custom hook like this:
const useSelectedStore = () => useContext(SelectedStoreContext);
// used like
const [selectedStore, setSelectedStore] = useSelectedStore();
Customs hook do not share any data. They are just compositions of the built-in hooks. But you can still share the store state by using react context:
/* StoreContext.js */
const StoreContext = createContext(null);
export const StoreProvider = ({children}) => {
const storeState = useState();
return (
<StoreContext.Provider value={storeState}>
{children}
</StoreContext.Provider>
);
};
export const useStore = () => useContext(StoreContext);
Usage:
/* App.js */
/* wrap your components using the store in the StoreProvider */
import {StoreProvider} from './StoreContext';
const App = () => (
<StoreProvider>
{/* some children */}
</StoreProvider>
);
and
/* StoreDetails.js */
/* use the useStore hook in your components */
import {useStore} from './StoreContext';
function StoreDetails() {
const [selectedStore, setSelectedStore] = useStore();
// ...
}
I have a new React/Redux app that I've recently ported over to the .NET Core 2.0 React/Redux template. I'm using some of the boilerplate but have mostly tried to strip out the things I'm not using. One of things I am trying to use, however, is some of the built in server-side prerendering. I'm running into a problem where the page is rendered correctly, but if I click a navigation button that is supposed to take me to a different component, it instead makes a call to the page controller (in my case the initial HomeController which is barebones), causing a whole new view to be rendered server-side. It still takes me to the right component, but not before doing a full postback. I feel like it's probably something small that I'm missing, but I can't seem to figure it out.
NOTES:
If I use createHashHistory() instead of createBrowserHistory() in my boot-client.js, it'll route to my component correctly. However, I think get this error from React -
attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) eactid="4"><a href="#/" data-reactid="5"
(server) eactid="4"><a href="/" data-reactid="5">
Here are some of the files I have related to all of this:
boot-client.js
import './css/site.css';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { HashRouter as Router, Switch, Link, Route, hashHistory, IndexRoute } from 'react-router-dom';
import { AppContainer } from 'react-hot-loader';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createBrowserHistory } from 'history';
import configureStore from './configureStore';
import { ApplicationState } from './store';
import * as RoutesModule from './routes';
let routes = RoutesModule.routes;
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './reducers';
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const history = createBrowserHistory({ baseName: '/' });
// Get the application-wide store instance, prepopulating with state from the server where available.
//const initialState = (window as any).initialReduxState as ApplicationState;
//const store = configureStore(history, initialState);
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing configuration
// and injects the app into a DOM element.
ReactDOM.render(
<AppContainer>
<Provider store={ store }>
<ConnectedRouter history={ history } children={ routes } />
</Provider>
</AppContainer>,
document.getElementById('app')
);
}
renderApp();
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./routes', () => {
routes = require('./routes').routes;
renderApp();
});
}
boot-server.js
import * as React from 'react';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { replace } from 'react-router-redux';
import { createMemoryHistory } from 'history';
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
import { routes } from './routes';
import configureStore from './configureStore';
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './reducers/index.js';
export default createServerRenderer(params => {
return new Promise((resolve, reject) => {
// Prepare Redux store with in-memory history, and dispatch a navigation event
// corresponding to the incoming URL
const basename = params.baseUrl.substring(0, params.baseUrl.length - 1); // Remove trailing slash
const urlAfterBasename = params.url.substring(basename.length);
//const store = configureStore(createMemoryHistory());
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
// store.dispatch(replace(urlAfterBasename));
// Prepare an instance of the application and perform an inital render that will
// cause any async tasks (e.g., data access) to begin
const routerContext = {};
const app = (
<Provider store={ store }>
<StaticRouter basename={ basename } context={ routerContext } location={ params.location.path } children={ routes } />
</Provider>
);
renderToString(app);
// If there's a redirection, just send this information back to the host application
if (routerContext.url) {
resolve({ redirectUrl: routerContext.url });
return;
}
// Once any async tasks are done, we can perform the final render
// We also send the redux store state, so the client can continue execution where the server left off
params.domainTasks.then(() => {
resolve({
html: renderToString(app),
globals: { initialReduxState: store.getState() }
});
}, reject); // Also propagate any errors back into the host application
});
});
routes.jsx
export const routes = <Layout>
<Route exact path='/' component={ StartPage } />
<Route path='/somecomp1' component={ SomeComponent1 } />
<Route exact path='/somecomp2' component={ SomeComponent2 } />
<Route path='/somecomp3' component={ SomeComponent3 } />
</Layout>;
Layout.jsx
export default class Layout extends React.Component {
render() { return <div style={{ backgroundColor: '#F8F8FF' }}>
<SomeHeaderComponent />
<div id='content'>
{ this.props.children }
</div>
</div>
}
}
It won't fix the real issue, but removing the asp-prerender-module Tag Helper from the index view removed the warning for me.
I'm not sure if you should look in this direction but:
the hash part of the url and a hash change is not send to the server. Thus serverside rendering can't do anything based on the hash part of the url thus the server would not create a HASH url with serverside rendering because the hash would never lead to calling the server again when the url/achor tag is clicked.
I'd advise you to not use hashes and/or dive into the source-code of these pre rendering packages to understand what is going on.
Other wild guess: when you click the href="/" url even in react it will reload the whole page and go to: / thus this might be the reason nothing happens? try to stop default behaviour with: event.preventDefault().
Use the NavLink that comes with react-router-dom in the Visual Studio template instead of the anchor '' tag to move around and navigate the app
import { NavLink } from 'react-router-dom';
... class / constructor code ...
public render() {
return <ul>
<li>
<NavLink to="/Students">Students</NavLink>
</li>
</ul>;
}
Use that for every component so that react handles that navigation not your webserver / controller (which starts the app rerendering)