I'm having a issue and im not sure the problem is chrome, react or macOs
When i open devtools my component rerender. I have simple code about redux-persist
Summary: Im trying to open second browser when Page1 renders if user closes Page2, a button inside Page1 will be active to open Page2 on second tab.
When user closes/refreshs Page1 tab Page2 tab will also close and clear the persist:root
Im using:
Chrome:Version 107.0.5304.110
React:18.2.0
MacOS Monterey:12.3.1
code examples:
App.js
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Page1 from "./Page1";
import Page2 from "./Page2";
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Page1 />}></Route>
<Route path="test" element={<Page2 />} />
</Routes>
</BrowserRouter>
);
};
export default App;
Page1.js
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { addItem, clearAll, createList, removeItem } from "./testReducer";
const Page1 = () => {
const [isSecondScreenButtonDisable, setIsSecondScreenButtonDisable] =
useState(false);
const dispatch = useDispatch();
const reducerList = useSelector((state) => state.counter);
const array = [1];
window.addEventListener("beforeunload", () => {
localStorage.setItem("isSecondSreenOpen", "false");
localStorage.removeItem("persist:root");
});
window.addEventListener("storage", () => {
const isOpen = localStorage.getItem("isSecondSreenOpen");
if (isOpen === "false") {
setIsSecondScreenButtonDisable(false);
} else {
setIsSecondScreenButtonDisable(true);
}
});
useEffect(() => {
localStorage.setItem("isSecondSreenOpen", true);
window.dispatchEvent(new Event("storage"));
dispatch(createList(array));
}, []);
useEffect(() => {
if (localStorage.getItem("isSecondSreenOpen") === "true") {
window.open("test", "_blank");
}
}, []);
return (
<div className="App">
<button
disabled={isSecondScreenButtonDisable}
onClick={() => {
window.open("test", "_blank");
}}
>
2. sayfa
</button>
<header className="App-header">
<button onClick={() => dispatch(clearAll())}> Hepsini Sil</button>
<button onClick={() => dispatch(addItem(100))}> Ekle</button>
<button onClick={() => dispatch(removeItem())}> Sil</button>
{reducerList.map((item, index) => {
return (
<div key={item + index}>
{index}-{item}
</div>
);
})}
</header>
</div>
);
};
export default Page1;
Page2
import { useEffect } from "react";
import { useSelector } from "react-redux";
const Page2 = () => {
const reducerList = useSelector((state) => state.counter);
const onCloseEvent = () => {
localStorage.setItem("isSecondSreenOpen", false);
window.close();
};
window.addEventListener("beforeunload", () => {
localStorage.setItem("isSecondSreenOpen", false);
});
useEffect(() => {
localStorage.setItem("isSecondSreenOpen", true);
window.addEventListener("storage", () => {
const isOpen = localStorage.getItem("isSecondSreenOpen");
if (isOpen === "false") {
onCloseEvent();
}
});
}, []);
return (
<div className="App">
<header className="App-header">
{reducerList.map((item, index) => {
return (
<div key={item}>
{index}-{item}
</div>
);
})}
</header>
</div>
);
};
export default Page2;
store.js
import { combineReducers, configureStore } from "#reduxjs/toolkit";
import {
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from "redux-persist";
import persistStore from "redux-persist/es/persistStore";
import storage from "redux-persist/lib/storage";
import { createStateSyncMiddleware } from "redux-state-sync";
// import secondReducer from "./secondReducer";
import testReducer from "./testReducer";
const persistConfig = {
key: "root",
version: 1,
storage,
// blacklist: ['secondReducer']
};
const rootReducer = combineReducers({
counter: testReducer,
// secondReducer: secondReducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat(
createStateSyncMiddleware({
blacklist: [PERSIST, REHYDRATE],
})
),
});
export const persistor = persistStore(store);
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
import { persistor, store } from "./store";
import { PersistGate } from "redux-persist/lib/integration/react";
import { initMessageListener } from "redux-state-sync";
const root = ReactDOM.createRoot(document.getElementById("root"));
initMessageListener(store);
root.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
);
reportWebVitals();
Edit: Not only my component rerender it starts from index.js
I found the answer it is not about react. I'm using extension called "Awesome Color Picker" when i disable this extension the problem solved.
Related
I am following the WhatsApp clone on YouTube, I did exactly what they were doing but I don't know why I'm getting this error. I was
I read a lot of blogs, but I couldn't resolve it.
In app, it gives this error and couldn't dismiss.
./src/App.js Line 10: 'dispatch' is assigned a value but never used no-unused-vars
In login, it gives this error.
./src/Login.js Line 9: Unexpected empty object pattern no-empty-pattern
<!-- begin snippet: js hide: false console: true babel: false -->
import React from "react";
import "./Login.css";
import { Button } from "#mui/material";
import { auth, provider } from "./firebase";
import { useStateValue } from "./StateProvider";
import { actionTypes } from "./reducer";
function Login() {
const [value, dispatch] = useStateValue({});
// const [value, dispatch] = useStateValue({})
// const [{ type, user }, dispatch] = useStateValue();
const signIn = () => {
auth
.signInWithPopup(provider)
.then((result) => {
dispatch({
type: actionTypes.SET_USER,
user: result.user,
});
})
.catch((error) => alert(error.message));
};
return (
<div className="login">
<div className="login__container">
<img
src="https://www.freepnglogos.com/uploads/whatsapp-logo-png-hd-2.png"
alt=""
/>
<div className="login__text">
<h1>Sign in to WhatsApp</h1>
</div>
<Button onClick={signIn}>Sign In with Google</Button>
</div>
</div>
);
}
export default Login;
import React from "react";
import "./App.css";
import Sidebar from "./Sidebar";
import Chat from "./Chat";
import Login from "./Login";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useStateValue } from "./StateProvider";
function App() {
const [{ user }, dispatch] = useStateValue();
return (
<div className="app">
{!user ? (
<Login />
) : (
<div className="app__body">
<Router>
<Sidebar />
<Routes>
<Route path="/rooms/:roomId" element={<Chat />} />
<Route path="/" element={<Chat />} />
</Routes>
</Router>
</div>
)}
</div>
);
}
export default App;
import React, { createContext, useContext, useReducer } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
export const initialState = {
user: null,
};
export const actionTypes = {
SET_USER: "SET_USER",
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case actionTypes.SET_USER:
return {
...state,
user: action.user,
};
default:
return state;
}
};
export default reducer;
I am trying to connect the metamask wallet to my react-app. But in the process I am getting a few errors as mentioned below please help.
getWeb3.js
import Web3 from 'web3';
import {useEffect, useState} from 'react';
// Wallet connect code
const useWeb3 = () => {
const [web3,setweb3] = useState(null);
useEffect(() => {
var instance;
if(window.ethereum) {
try{
instance = new Web3(window.ethereum);
}catch(error){
console.error(error);
};
}else if (window.web3) {
instance = new Web3(window.web3);
}else{
const provider = new Web3.provider.HttpProvider('http://127.0.0.1:8545')
instance = new Web3(provider);
}setweb3(instance);
},[]);
return web3;
};
export {useWeb3};
store.js
import React, {useReducer, useContext, createContext} from 'react';
const StoreContext = createContext();
const initialState = {
message: '',
address: null,
balance: 0
};
const reducer = (state, action) => {
switch(action.type){
case "NEW-ADDRESS":
return {
...state,
address: action.newAddress,
message: action.message
}
default:
throw new Error('Unknown type of action ${action.type');
}
};
export const StoreProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StoreContext.Provider value = {{state, dispatch}}>
{children}
</StoreContext.Provider>
);
};
export const useStore = () => useContext(StoreContext);
storeApi.js
import {useStore} from './store'
const useStoreApi = () => {
const {state, dispatch} = useStore();
return {
address: state.address,
balance: state.balance,
message: state.message,
setBalance: (newBalance) => {
dispatch({
type: "SET-BALANCE",
newBalance
});
},
setAddress: newAddress => {
dispatch({
type: "New-Address",
newAddress,
message: "New account added successfully!"
});
}
};
};
export {useStoreApi};
app.js
import { useStoreApi } from './storeApi';
import { useWeb3 } from './getWeb3';
import logo from './logo.svg';
import './App.css';
import {Button} from '#material-ui/core';
function App() {
const {address,balance,message, setBalance,setAddress} = useStoreApi();
const web3 = useWeb3();
const setUserAccount = async (e) => {
console.log("rhis add");
if(window.ethereum) {
await window.ethereum.enable();
web3.eth.getAccounts().then(accounts => {
setAddress(accounts[0]);
console.log("rhis add");
});
}
};
setUserAccount();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
<code>src/App.js</code>
</p>
{
address ? (
<>
<p> Your add: {address}</p></>
): "s"
}
{/* <Button variant="outlined" color="primary" onClick={() => setUserAccount()}>Conn</Button> */}
{/* <form action="http://localhost:3000/">
<button type="submit" onClick={() => setUserAccount()}>Submit</button>
</form> */}
</header>
</div>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {StoreProvider} from './store';
ReactDOM.render(
<React.StrictMode>
<StoreProvider>
<App />
</StoreProvider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
Errors:
App.js:16 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'eth')
at setUserAccount (App.js:16)
store.js:20 Uncaught Error: Unknown type of action ${action.type
index.js:1 The above error occurred in the component:
at StoreProvider (http://localhost:3000/static/js/main.chunk.js:847:3)
It looks like you're importing useWeb3 wrong:
const web3 = useWeb3();
But you export it like:
export {useWeb3};
So now you would have to call it like: web3.useWeb3()
Is this code you found at a tutorial somewhere? It's hard to follow...
I'm building a practice app that uses Unsplash to render users photos. I'm using React and Redux. With react-router-dom, I'm trying to follow the docs but I find it very confusing to set up. Here's what I have so far. When I click on a result out of a returned list of results from a search, I want it to render a user page profile.
index.js (make sure I have react-router-do set up correctly):
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
// import store from './app/store';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducers from "./app/reducers/rootReducer";
import * as serviceWorker from './serviceWorker';
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, storeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
Top component App
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Images from "./app/components/Images";
import Search from "./app/components/Search";
import UserProfile from "./app/components/UserProfile";
import "./App.css";
function App() {
return (
<>
<Search />
<Images />
<Router>
<Route link="/userProfile">
<UserProfile />
</Route>
</Router>
</>
);
}
export default App;
search (parent component to searchResults where exists):
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { queryAction } from "../actions/queryAction";
import SearchResults from "./SearchResults";
const Search = (props) => {
const [query, setQuery] = useState("");
console.log(props.searches);
const searchPhotos = async (e) => {
e.preventDefault();
console.log("submitting form");
props.queryAction(query);
};
const showUsers = (user, e) => {
e.preventDefault()
console.log(user)
};
return (
<>
<form className="form" onSubmit={searchPhotos}>
<label className="label" htmlFor="query">
{" "}
</label>
<input
type="text"
name="query"
className="input"
placeholder={`Try "dog" or "apple"`}
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button type="submit" className="button">
Search
</button>
</form>
<SearchResults results={props.searches} showUsers={showUsers} />
</>
);
};
const mapStateToProps = (state) => {
return {
searches: state.searches,
};
};
const mapDispatchToProps = (dispatch) => {
return {
queryAction: (entry) => dispatch(queryAction(entry)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
searchResults:
import React from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import { getUserAction } from "../actions/getUserAction";
import { connect } from "react-redux";
const SearchResults = (props) => {
const { results } = props.results.searches;
const handleClick = (result, e) => {
e.preventDefault();
props.getUser(result.username);
};
return (
<>
{results &&
results.map((result, id) => {
return (
<div key={id}>
<Router>
<Link to="/userProfile" onClick={(e) => handleClick(result, e)}>
{result.username}
</Link>
</Router>
</div>
);
})}
</>
);
};
const mapDispatchToProps = (dispatch) => {
return {
getUser: (query) => dispatch(getUserAction(query)),
};
};
export default connect(null, mapDispatchToProps)(SearchResults);
and finally the UserProfile component:
import React from 'react';
import { connect } from 'react-redux';
const UserProfile = props => {
console.log(props)
return (
<div>
</div>
);
}
const mapStateToProps = state => {
return {
user: state.users
}
}
export default connect(mapStateToProps, null)(UserProfile);
app component
import React from "react";
import { Switch, Route } from "react-router-dom";
import Images from "./app/components/Images";
import Search from "./app/components/Search";
import UserProfile from "./app/components/UserProfile";
import "./App.css";
function App() {
return (
<>
<Search />
<Images />
<Switch>
<Route path="/userProfile/:username">
<UserProfile />
</Route>
</Switch>
</>
);
}
export default App;
SearchResults component
import React from "react";
import { Link } from "react-router-dom";
const SearchResults = (props) => {
const { results } = props.results.searches;
const handleClick = (result, e) => {
e.preventDefault();
props.getUser(result.username);
};
return (
<>
{results &&
results.map((result, id) => {
return (
<div key={id}>
<Link to={`/userProfile/${result.username}`}>
{result.username}
</Link>
</div>
);
})}
</>
);
};
export default SearchResults;
UserProfile component
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { getUserAction } from "../actions/getUserAction";
const UserProfile = props => {
useEffect(() => {
props.getUserAction(props.match.params.username)
},[])
console.log(props)
return (
<div>
{props.user
? <div>{user.username}</div>
: <div>Loading...</div>
}
</div>
);
}
const mapStateToProps = state => {
return {
user: state.users
}
}
const mapDispatchToProps = (dispatch) => {
return {
getUser: (query) => dispatch(getUserAction(query)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
Edit: Add a param to your link and remove the onclick. Update the Route to expect a :username param. You can access the param through props in UserProfile component.
Make sure to perform the action or access state when mounting the UserProfile component so you have some data when it renders.
Edit 2: Added UserProfile component to answer. You want to dispatch your action when the component is mounting. Also, set a ternary to show "Loading..." if state.user isn't done being fetched.
I want to display userName and changed the button text to 'logout' from 'login' in Navbar(nav component) when user is logged in. I tired to re-render the Nav Component by pushing the router history location to homepage on user logged in.
this.props.history.push('/')
Then User can successfully log in, user data is saved in localStorage and the router location is changed. data is saved in localStorage, but the component is not re-rendering.
Here is what I have tried.
tried to downgrade 'connected-react-router' to 'v6.0.0' but it threw me some errors. so put it back to the latest version '^6.4.0'.
tried to add Navbar code in App.js which is the parents component.
But the app.js itself is not re-rendering either on user logged in.
app.js
import React from 'react';
import { connect } from 'react-redux';
/* --- Components --- */
import Nav from './src/components/nav';
import Loader from './src/shared/loader';
import './styles/main.scss';
const FlashMessagesContainer = Loader({
loader: () =>
import('./src/shared/flassMessagesContainer' /* webpackChunkName: 'FlashMessagesContainer' */),
});
const App = (props, { isOnModal }) => (
<div id="app">
<Nav />
{!isOnModal && (
<div className="flex justify-center">
<FlashMessagesContainer />
</div>
)}
{props.children}
</div>
);
const mapPropsToState = state => ({
isOnModal: state.modal.show,
});
export default connect(
mapPropsToState,
null,
)(App);
nav.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link, withRouter } from 'react-router-dom';
/* --- Components --- */
import {
isLoggedIn,
getCompanyName,
clearLocalStorage,
} from '../../localStorage';
class Nav extends Component {
handleUserLogout = async ev => {
ev.preventDefault();
await clearLocalStorage();
return this.props.history.push('/login');
};
render() {
return (
<div className="nav">
{isLoggedIn() ? (
<div className="flex">
<p className="mr3 mt2">
안녕하세요. <span className="b">{getCompanyName()}</span>
님,
</p>
<button
type="button"
className="login-btn td-none c-text br f-mini"
onClick={this.handleUserLogout}
>
로그아웃
</button>
</div>
) : (
<Link className="login-btn td-none c-text br f-mini" to="/login">
로그인
</Link>
)}
</div>
);
}
}
export default withRouter(Nav);
localStorage.js
export const getToken = () => localStorage.token;
export const getCompanyName = () => localStorage.companyName;
export const isLoggedIn = () => !!localStorage.token;
export const saveUserNameAndToken = userData => {
localStorage.setItem('token', userData.token);
localStorage.setItem('companyName', userData.companyName);
};
export const clearLocalStorage = () => localStorage.clear();
index.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter as Router } from 'connected-react-router';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import configureStore, { history } from './store';
import Routes from './routes';
const theme = createMuiTheme({
...
});
const store = configureStore();
const root = document.createElement('div');
document.body.appendChild(root);
render(
<Provider store={store}>
<MuiThemeProvider theme={theme}>
<Router history={history}>
<Routes />
</Router>
</MuiThemeProvider>
</Provider>,
root,
);
routes.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { hot, setConfig } from 'react-hot-loader';
import { withRouter } from 'react-router';
/* --- Components --- */
import App from './app';
import Loader from './src/shared/loader';
const Home = Loader({
loader: () =>
import('./src/components/home/homeContainer' /* webpackChunkName: 'Home' */),
});
...
const routes = props => (
<div>
<App history={props.history} />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" render={props => <Login {...props} />} />
<Route path="/users/account" component={Users} />
<Route component={NoMatch} />
</Switch>
</div>
);
const Routes =
!module.hot || process.env.NODE_ENV === 'production'
? routes
: hot(module)(routes);
export default withRouter(Routes);
I am expecting Nav component to be re-rendered when router location changes.
react: ^16.8.6
react-dom: ^16.8.6
react-loadable: ^5.5.0
react-redux: ^6.0.0
react-router-dom: ^4.3.1
redux: ^4.0.0
connected-react-router: ^6.4.0
I fixed it by saving 'isLoggedIn' state in redux store. And to persist the state from redux store on page refresh, I used 'redux-persist'.
Now Nav component is re-rendering on router location change and the userName data is saved even on page refresh.
authReducer.js
const initialState = {
isLoggedIn: false,
userName: '',
};
const auth = (state = initialState, action) => {
switch (action.type) {
case types.USER_LOGIN:
return {
...state,
isLoggedIn: true,
userName: action.payload,
};
case types.USER_LOGOUT:
return {
...state,
isLoggedIn: false,
userName: '',
};
default:
return state;
}
};
nav.js
...
{this.props.isLoggedIn ? (
...
) : (
...
)}
...
const mapStateToProps = state => ({
isLoggedIn: state.auth.isLoggedIn,
userName: state.auth.userName,
});
const mapDispatchToProps = dispatch => ({
userLogout: () => dispatch(userLogout()),
});
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps,
)(Nav),
);
[ redux-persist SetUp ]
store.js
import { applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';
import { routerMiddleware } from 'connected-react-router';
import createBrowserHistory from 'history/createBrowserHistory';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import createRootReducer from './src/reducers';
export const history = createBrowserHistory();
const persistConfig = {
key: 'root',
storage,
};
const persistedReducer = persistReducer(
persistConfig,
createRootReducer(history),
);
const store = createStore(
persistedReducer,
composeWithDevTools(
applyMiddleware(
routerMiddleware(history),
thunkMiddleware,
createLogger({
predicate: () => process.env.NODE_ENV === 'development',
collapsed: true,
}),
),
),
);
export const persistor = persistStore(store);
export default store;
index.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter as Router } from 'connected-react-router';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import { PersistGate } from 'redux-persist/lib/integration/react';
import store, { history, persistor } from './store';
import Routes from './routes';
import Loading from './src/shared/loading';
const theme = createMuiTheme({
palette: {
primary: { main: '#AE9A65' },
secondary: { main: '#ee9105' },
success: { main: '#43A047' },
warning: { main: '#FFA000' },
error: { main: '#ed4337' },
info: { main: '#2196F3' },
},
});
const root = document.createElement('div');
document.body.appendChild(root);
render(
<Provider store={store}>
<PersistGate loading={<Loading />} persistor={persistor}>
<MuiThemeProvider theme={theme}>
<Router history={history}>
<Routes />
</Router>
</MuiThemeProvider>
</PersistGate>
</Provider>,
root,
);
I've got a Standard React-Redux Application with react router v4, webpack 4 and I'm trying to perform lazy loading of components via loadable-components library, so webpack can create chunks and load them on demand. The problems seems it's always rendering the Dashboard (refer code below) Component inside the <Switch>, no matter the route.
I don't understand the cause. I found a similar problem: React-Router v4 rendering wrong component but matching correctly but it follows a different pattern and I can't or don't understand how to apply that solution into my issue.
I'm posting The main AppContainer and the routes file:
AppContainer.js:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import ReduxToastr from 'react-redux-toastr'
import 'react-redux-toastr/src/styles/index.scss'
import { Offline, Online } from 'react-detect-offline'
import Footer from 'components/Footer/'
import { Modal, ModalHeader, ModalBody } from 'reactstrap'
import { Translate } from 'react-redux-i18n'
import { PersistGate } from 'redux-persist/integration/react'
import { Router, Switch, Route } from 'react-router-dom'
import PrivateRoute from 'components/PrivateRoute'
import { Dashboard, PasswordReset, PasswordResetEdit, SignIn } from 'routes'
// Layout
import CoreLayout from 'layouts/CoreLayout'
class AppContainer extends Component {
static propTypes = {
history: PropTypes.object.isRequired,
persistor: PropTypes.object.isRequired, // redux-persist
store : PropTypes.object.isRequired
}
render () {
const { history, store, persistor } = this.props
const opened = true
const toastrDefaultTimeout = 8000
const newToastrAlwaysOnTop = false
return (
<div>
<Online>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<div style={{ height: '100%' }}>
<ReduxToastr
timeOut={toastrDefaultTimeout}
newestOnTop={newToastrAlwaysOnTop}
preventDuplicates
position='top-right'
transitionIn='fadeIn'
transitionOut='fadeOut'
progressBar />
<Router history={history}>
<CoreLayout {...this.props} >
<Switch>
<Route exact path='/sign-in' component={SignIn} />
<Route exact path='/passwords/new' component={PasswordReset} />
<Route exact path='/passwords/edit' component={PasswordResetEdit} />
<PrivateRoute exact path='/' component={Dashboard} persistor={persistor} />
</Switch>
</CoreLayout>
</Router>
</div>
</PersistGate>
</Provider>
</Online>
<Offline>
<div className='app'>
<div className='app-body'>
<main className='main align-items-center align-self-center'>
<div className='container'>
<div className='animated fadeIn'>
<Modal isOpen={opened} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}><Translate value={`views.shared.no-internet.title`} /></ModalHeader>
<ModalBody>
<div className='card-block'>
<div className='row'>
<div className='col-sm-12'>
<Translate value='views.shared.no-internet.description' />
</div>
</div>
</div>
</ModalBody>
</Modal>
</div>
</div>
</main>
</div>
<Footer />
</div>
</Offline>
</div>
)
}
}
export default AppContainer
routes/index.js:
// We only need to import the modules necessary for initial render
import loadable from 'loadable-components'
import { hot } from 'react-hot-loader'
// Component split-code (lazy load)
let Dashboard,
SignIn,
PasswordReset,
PasswordResetEdit
// Needed for HMR
if (__DEV__) {
Dashboard = hot(module)(loadable(() => import('./Dashboard')))
PasswordReset = hot(module)(loadable(() => import('./PasswordReset')))
PasswordResetEdit = hot(module)(loadable(() => import('./PasswordResetEdit')))
SignIn = hot(module)(loadable(() => import('./SignIn')))
} else {
Dashboard = loadable(() => import('./Dashboard'))
SignIn = loadable(() => import('./SignIn'))
PasswordReset = loadable(() => import('./PasswordReset'))
PasswordResetEdit = loadable(() => import('./PasswordResetEdit'))
}
export { Dashboard, PasswordReset, PasswordResetEdit, SignIn }
And for anyone curious, here is the PrivateRoute component:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Route, Redirect } from 'react-router-dom'
import { connect } from 'react-redux'
import { validateSession } from 'modules/sessions/session'
const mapStateToProps = state => ({
session: state.session
})
const mapDispatchToProps = (dispatch) => {
return {
validateSession: () => { dispatch(validateSession()) }
}
}
class PrivateRoute extends Component {
componentDidMount () {
this.props.validateSession(this.props.persistor)
}
render () {
const { component: Component, session, ...rest } = this.props
return (
<Route {...rest} render={(routeProps) =>
this.props.session.userSignedIn ? <Component {...routeProps} />
: <Redirect to={{
pathname: '/sign-in',
state: { from: routeProps.location }
}} />
}
/>)
}
}
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
persistor: PropTypes.object.isRequired,
session: PropTypes.object.isRequired,
validateSession: PropTypes.func.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps)(PrivateRoute)
If I remove the if (_DEV_) block in routes/index.js and always do it like the 'else' block everything works okay, however I lose HMR, meaning I have to refresh the browser to see code changes take effect.
Edit
For anyone wandering where the history prop comes from:
Main app entry point (in webpack.config.js). src/main.js:
import React from 'react'
import ReactDOM from 'react-dom'
import createStore from './store/createStore'
import { hot } from 'react-hot-loader'
import AppContainer from './containers/AppContainer'
import { persistStore } from 'redux-persist'
// ========================================================
// Store Instantiation
// ========================================================
const initialState = window.___INITIAL_STATE__
const { history, store } = createStore(initialState)
// begin periodically persisting the store
let persistor = persistStore(store)
// ========================================================
// Render Setup
// ========================================================
const MOUNT_NODE = document.getElementById('root')
let render = () => {
ReactDOM.render(
<AppContainer store={store} persistor={persistor} history={history} />,
MOUNT_NODE
)
}
// This code is excluded from production bundle
if (__DEV__) {
if (module.hot) {
// Development render functions
const renderApp = render
const renderError = (error) => {
const RedBox = require('redbox-react').default
ReactDOM.render(<RedBox error={error} />, MOUNT_NODE)
}
// Wrap render in try/catch
render = () => {
try {
renderApp()
} catch (error) {
console.error(error)
renderError(error)
}
}
// Setup hot module replacement
module.hot.accept('./containers/AppContainer', () =>
render(require('./containers/AppContainer').default)
)
}
}
// ========================================================
// Go!
// ========================================================
export default hot(module)(render)
render()
createStore.js:
import { applyMiddleware, compose, createStore } from 'redux'
import thunk from 'redux-thunk'
import { apiMiddleware } from 'redux-api-middleware'
import { createLogger } from 'redux-logger'
import makeRootReducer from './reducers'
import { routerMiddleware } from 'react-router-redux'
// I18n
import { syncTranslationWithStore, loadTranslations, setLocale } from 'react-redux-i18n'
import { translationsObject } from 'translations/index'
// Router history
import createHistory from 'history/createBrowserHistory'
// Raven for Sentry
import Raven from 'raven-js'
import createRavenMiddleware from 'raven-for-redux'
export default (initialState = {}) => {
// ======================================================
// Middleware Configuration
// ======================================================
const logger = createLogger()
const history = createHistory()
const historyMiddleware = routerMiddleware(history)
const middleware = [historyMiddleware, apiMiddleware, thunk, logger]
if (__PROD__) {
Raven.config(`${__SENTRY_DSN__}`).install()
middleware.push(createRavenMiddleware(Raven))
}
// ======================================================
// Store Enhancers
// ======================================================
const enhancers = []
let composeEnhancers = compose
const composeWithDevToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
if (typeof composeWithDevToolsExtension === 'function') {
composeEnhancers = composeWithDevToolsExtension
}
// ======================================================
// Store Instantiation and HMR Setup
// ======================================================
const store = createStore(
makeRootReducer(),
initialState,
composeEnhancers(
applyMiddleware(...middleware),
...enhancers
)
)
store.asyncReducers = {}
// DEPRECATED in react-router v4: To unsubscribe, invoke `store.unsubscribeHistory()` anytime
// store.unsubscribeHistory = browserHistory.listen(updateLocation(store))
if (module.hot) {
const reducers = require('./reducers').default
module.hot.accept('./reducers', () => {
store.replaceReducer(reducers(store.asyncReducers))
})
}
syncTranslationWithStore(store)
store.dispatch(loadTranslations(translationsObject))
store.dispatch(setLocale('es_AR'))
return { history, store }
}