React components not updating from Redux state after Hot Module Reload - javascript

I am using react-hot-loader 3.0.0-beta.6 to hot reload react components. Reloading itself works well - i see the updated component immediately. Unfortunately, after successful reload, dispatching actions inside the application does not trigger rerenders any more and I need to do a full manual refresh to get the application working again. The dispatched actions update the redux store, but the components are not rerendered.
All the components I am using consist of a connected container and a stateless component.
What could be the reason for not rendering the updated state? How could I continue debugging?
MyComponent/container.js:
const mapStateToProps = state => ({
...
});
const mapDispatchToProps = dispatch =>
bindActionCreators({
...
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Component);
MyComponent/component.jsx:
const Component = ({ testProp1, testProp2 }) => (
<div onClick={testProp2}>
{testProp1}
</div>
);
Here is the successful update:
[WDS] App updated. Recompiling...
[WDS] App hot update...
[HMR] Checking for updates on the server...
[HMR] Updated modules:
[HMR] - ./source/modules/DropDown/index.js
...
[HMR] - ./source/modules/App/index.js
Render in the main.jsx:
const render = () => {
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<MuiThemeProvider>
<App />
</MuiThemeProvider>
</Provider>
</AppContainer>,
eyeTalLayer
);
};
render();
if (module.hot) {
module.hot.accept('./modules/App', render);
}

There's actually a couple open issues in the React-Redux repo discussing similar behavior as of React-Redux 5.0. See react-redux#636 and react-redux#670.
I did a bit of research, and it looks like the components higher in the hierarchy are getting recompiled and hot-reloaded, but not the components lower in the hierarchy. Because v5 implemented a top-down subscription system, the lower components aren't aware that their subscription references are now stale. I haven't had time yet to try to figure out a good way to handle that.
Based on what I've seen, I believe that removing the use of React-Hot-Loader will work around the problem. You could just reload the entire component tree using "plain" HMR, and I see you've already got your code set up to do that. You'd lose the potential benefits of RHL trying to maintain component state, but the Redux connections should reset properly.

Related

How can I implement a Telemetry Initializer to overwrite PageView names with the current route?

I'm implementing a ReactJS app which is sending telemetry to App Insights. It's tracking PageViews ok except every PageView has the name "<App Name>" because that's our page title and it does not change between routes (for "reasons"). This makes our App Insights PageView data not as helpful as it could be.
I want to send the current route as the PageView name instead of the page title using a telemetry initializer. Problem is I get an error when I try to use the useLocation hook to get the current route:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
Am using react-dom v17 and react-router-dom v6, so I'm probably "breaking the Rules". I might be using the App Insights SDK incorrectly as well.
Here is my <App /> where I'm currently using App Insights:
import { AppInsightsContext, ReactPlugin, withAITracking } from '#microsoft/applicationinsights-react-js';
import { ApplicationInsights, PageView } from '#microsoft/applicationinsights-web';
//<snip>
const reactPlugin = new ReactPlugin();
const appInsights = new ApplicationInsights({
config: {
connectionString: 'myconnectionstring',
extensions: [reactPlugin],
enableAutoRouteTracking: true,
enableCorsCorrelation: true,
enableRequestHeaderTracking: true,
enableResponseHeaderTracking: true,
},
);
appInsights.addTelemetryInitializer((envelope) => {
const telemetryItem = envelope.data.baseData;
if (envelope.name === PageView.envelopeType) {
const { pathname } = useLocation(); // runtime error
telemetryItem.name = pathname;
}
});
appInsights.loadAppInsights();
const App: React.FC = () => {
return (
<Provider store={store}>
<BrowserRouter>
<AppInsightsContext.Provider value={reactPlugin}>
<AppBootstrap />
<ToastContainer />
</AppInsightsContext.Provider>
</BrowserRouter>
</Provider>
);
};
export default withAITracking(reactPlugin, App);
Any ideas how I might be able to do this? Kinda new to React!

Should I use connect or hooks for react redux and which has better performance?

I am using react-redux for my react project, apparently, there are 2 ways to use the redux state
connect or useSelector,
My redux store has a reducer for each page,
For Home Page > homePageReducer
For Message Page > messagePageReducer
For Authentication > authReducer
For User's blog > blogReducer
For Settings > userSettingsReducer
For User's Profile > userProfileReducer
In my top-level component or the main component, I have used a selector hook to get all the reducer and passed down the reducers as props to the required components
const {home, messages, auth, settings, blogs} = useSelector( (state:RootState) => state)
return(
<main>
<Switch>
<Route exact to={HOME_ROUTE}>
<HomeApp auth={auth} settings={settings} userProfile={userProfile}/>
</Route>
<Route exact to={CHAT_ROUTE}>
<ChatApp auth={auth} messages={messages} userProfile={userProfile}/>
</Route>
<Route exact to={BLOG_ROUTE}>
<BlogApp auth={auth} blogs={blogs} userProfile={userProfile}/>
</Route>
</Switch>
</main>
)
Is it a good architecture for my project and bring no performance issue for my project or should I use connect or useSelector hook inside those components?
What is better?
Redux has a very useful Style Guide explaining all of the current best practices. There are a few recommendations on that list that are applicable to your example.
Use the React-Redux Hooks API
Prefer using the React-Redux hooks API (useSelector and useDispatch) as the default way to interact with a Redux store from your React components.
Connect More Components to Read Data from the Store
Prefer having more UI components subscribed to the Redux store and reading data at a more granular level. This typically leads to better UI performance, as fewer components will need to render when a given piece of state changes.
Call useSelector Multiple Times in Function Components
When retrieving data using the useSelector hook, prefer calling useSelector many times and retrieving smaller amounts of data, instead of having a single larger useSelector call that returns multiple results in an object.
You definitely want to use useSelector. Rather than selecting everything in the parent and passing it down, your Route render components should take no props and get everything that they need from Redux themselves.
const App = {
return(
<Switch>
<Route exact to={HOME_ROUTE}>
<HomeApp />
</Route>
<Route exact to={CHAT_ROUTE}>
<ChatApp />
</Route>
<Route exact to={BLOG_ROUTE}>
<BlogApp />
</Route>
</Switch>
)
}
const HomeApp = () => {
const userProfile = useSelector( (state: RootState) => state.user );
// We probably don't need the whole auth object
const isLoggedIn = useSelector( (state: RootState) => !! state.auth );
// Do you need to return *every* setting? Just select what you need.
const settings = useSelector( (state: RootState) => state.settings );
...
}
You might want to create selector functions especially for commonly-accessed values like userProfile.
An alternative to modifying the current components HomeApp etc. would be to create a HomeScreen component as a wrapper around HomeApp and keep HomeApp as a pure presentational component. HomeScreen would get all of the data from Redux and call HomeApp with the correct props.

React Hot Reload with Redux-Saga

I'm using React Hot Reloader on my project and recently noticed a strange behavior of the redux store in pairs with Hot Reloader.
When I load the page for the first time, my page component calls an action to retrieve a particular data (this is doing in useEffect hook), everything works fine - action triggers once and calls backend properly:
When I make a change in the code and save it, Hot Reloader reloads my component and dispatch action twice (as a result - 2 calls in the network tab):
Same story with further code changes:
I wrap my main app component and redux store with hot reload middleware:
index.js
// Create a reusable render method that we can call more than once
const render = () => {
// Dynamically import our main App component, and render it
const MainApp = require('./MainApp').default;
ReactDOM.render(
<MainApp />,
rootEl,
);
};
if (module.hot) {
module.hot.accept('./MainApp', () => {
const MainApp = require('./MainApp').default;
render(
<MainApp />,
rootEl,
);
});
}
render();
store.js
const store = createStore(reducers(history), initialState,
composeEnhancers(applyMiddleware(...middlewares)));
sagaMiddleware.run(rootSaga);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers/index', () => {
const nextRootReducer = require('../reducers/index');
store.replaceReducer(nextRootReducer);
});
}
Are there any ways to fix that? Why it is happening?

Is it enough to use Provider one time?

In react-native when we use redux module we use createStore from 'redux'. And My question: is it enough to use <Provider/> one time which makes the Redux store available to the rest of our app.
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
const rootElement = document.getElementById('root')
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
Or we must also add <Provider/> somewhere?
Once you create a store with redux with the reducers for example just like below:
const store = createStore(reducers, applyMiddleware(thunk));
Then you need to use <Provider> wrapper only once:
ReactDOM.render(
<Provider store={store}>
<App />
<Provider>, document.getElementById('root')
);
Later if you want to access any of the object from the store you need to use mapStateToProps in your components. If any modification is needed you need to create actions and dispatch them with mapDispatchToProps.
From the the documentation:
The option we recommend is to use a special React Redux component called to magically make the store available to all container components in the application without passing it explicitly. You only need to use it once when you render the root component.
Hope this helps.
Yep, you only need to use Provider once to wrap your whole application.
The question looks interesting. You need to understand how the store is available to every component wrapped inside the <Provider>.
When you wrap any React component inside <Provider store={store}> every component will have access to something called context, through which it is possible to share the data even it's not an immediate child.
Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree. https://reactjs.org/docs/context.html
As it states that context can be used in any child component without explicitly passing a prop.
The <Provider> exactly works the same way. And when you use the redux the <Provider> sets the context. And you need to use the connect() from the react-redux it uses the context set by the <Provider> to pass properties to your component wrapped in connect() HOC.
Hence you need to use the <Provider> only once in your application.

Loading Stores in React-Redux

I am new to React-Redux, I have a app having all stores preloaded on the index.js file which is the entry point that renders the app . I have the following code in the page (a part of it).
if(Auth.isUserAuthenticated()){
store.dispatch(getProductCategories());
store.dispatch(getProducts());
store.dispatch(getAuctions());
store.dispatch(getAppSettings());
store.dispatch(getWalletTransactionHistory());
store.dispatch(getAllUsers());
store.dispatch(getParticipatedAuctions());
store.dispatch(getParticipatedAuctionsList());
store.dispatch(getWalletBalance())
}
store.dispatch(getHomePageAuctions());
store.dispatch(getAllPageAuctions());
store.dispatch(getPriceCards());
render(
<AppContainer>
<App store={store} history={history} />
</AppContainer>,
document.getElementById('app')
);
The action in the above are dispatched on every route change, But i Wanted it to dispatch only once on the page load . I even tried to put it on App component in ComponentDidMount() and also tried in putting it in constructor() of that component but no luck didn't work as expected and also searched google but no luck with exact match or similar to it
I suggest moving your store.dispatch calls inside App.js' componentDidMount. Since App.js will only mount once, they'll only fire once.

Categories

Resources