I am new to React and Redux and currently I am working on React project and have some questions about Redux integration.
1. Load state asynchronously
I have index.js file which is entry point of my project:
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root')
)
Here I import store.js:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const store = createStore(
rootReducer,
applyMiddleware(thunk)
)
export default store
And App.js:
import React, { Component } from 'react'
import { Route } from 'react-router-dom'
import UserRoute from './components/UserRoute'
import GuestRoute from './components/GuestRoute'
import Menu from './components/Menu'
import LoginPage from './components/pages/LoginPage'
import SignupPage from './components/pages/SignupPage'
import HomePage from './components/pages/HomePage'
import ProfilePage from './components/pages/ProfilePage'
import GroupsPage from './components/pages/GroupsPage'
class App extends Component {
render() {
return (
<div>
<Menu />
<Route exact path="/" component={HomePage} />
<GuestRoute exact path="/login" component={LoginPage} />
<GuestRoute exact path="/signup" component={SignupPage} />
<UserRoute exact path="/profile" component={ProfilePage} />
<UserRoute exact path="/groups" component={GroupsPage} />
</div>
)
}
}
export default App
I cut some code to make it more readable for you. Notice UserRoute and GuestRoute components. UserRoute allows only authenticated users to navigate to the page and GuestRoute does the opposite. I have user token saved in localStorage, then I use it to get user data from server. I want to get response before rendering my App component. If I don't wait response and my location is /profile for example, then UserRoute will redirect me to main page. To avoid redirect I can do this:
getState().then(() => {
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root')
)
})
But is it good way to load state before rendering? Or there are other "better" ways to do this?
2. Importing store in other files
How can I get current state in axios request? Is it OK to export store in store.js and then import it in api.js file:
import axios from 'axios'
import store from './store'
const client = axios.create({
baseURL: 'http://localhost:3001',
headers: { 'Content-type': 'application/json' }
})
// Used to set current JWT token(part of third question)
export function setAuthorizationHeader(token = '') {
if (token) {
client.defaults.headers.common['Authorization'] = `Bearer ${token}`
} else {
delete client.defaults.headers.common['Authorization']
}
}
export default {
user: {
fetch() {
return client.get('/user').then(res => res.data)
},
// other api calls which use current state of store
}
}
3. Not pure action creators
This question is about action creators. I have following action creators:
import api, { setAuthorizationHeader } from '../api'
export function setUser(user) {
return { type: 'SET_USER', user }
}
export function setUserToken(token) {
return { type: 'SET_USER_TOKEN', token }
}
export const login = credentials => async dispatch => {
const { user, token } = await api.user.login(credentials)
// TODO: refactor this
setAuthorizationHeader(token)
dispatch(setUserToken(token))
dispatch(setUser(user))
}
When I get response from server I set Authorization header for axios client. Is it OK that they are not pure functions?
Also you can look at my repository to get more understanding how code works.
Related
I'm pretty confident this is due to the RTL render not being used, but I'm not sure where the render should go given I apparently have to use ReactDOM.react. At least it was needed to resolve one 4-5 errors I got along the way.
App and test code...
// App.jsx
import React from 'react';
import { Provider } from 'react-redux';
import { compose } from 'redux';
import { Switch, Route } from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router';
import Authentication from './Authentication';
import configureStore, { history } from '../services/history';
const App = () => {
const protectedRoute = compose(Timers, RequireAuth);
const store = configureStore();
return (
<div data-testid='App'>
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route exact path='/' component={Authentication} />
</Switch>
</div>
</ConnectedRouter>
</Provider>
</div>
);
};
export default App;
// Authentication.jsx
import React, { Component } from 'react';
export default class Authentication extends Component {
render() {
return (
<div data-testid='Authentication' id='auth'>
...
</div>
);
}
}
Started out with this test:
// Authentication.test.jsx
import React from 'react';
import { render, screen } from '#testing-library/react';
import Authentication from './Authentication';
test('authentication page renders', () => {
render(<Authentication />);
const auth = screen.getByTestId('Authentication');
expect(auth).toBeInTheDocument();
});
Which resulted in:
Error: Uncaught [Error: Could not find "store" in the context of "Connect(Form(Connect(Signin)))". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(Form(Connect(Signin))) in connect options.]
Addressing this issue, and 4-5 other subsequent errors, has resulted in the following:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { render, screen } from '#testing-library/react';
import '#testing-library/jest-dom';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import Authentication from './Authentication';
import { MemoryRouter } from 'react-router-dom';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore({
auth: {
authenticated: true,
},
});
test('authentication page renders', () => {
const div = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<MemoryRouter>
<Authentication />
</MemoryRouter>
</Provider>,
div
);
const authentication = screen.queryByTestId('Authentication');
expect(authentication).toBeInTheDocument();
});
Which results in:
received value must be an HTMLElement or an SVGElement.
Received has value: null
28 | );
29 | const authentication = screen.queryByTestId('Authentication');
> 30 | expect(authentication).toBeInTheDocument();
| ^
31 | });
32 |
Again, pretty sure this is from not using RTLs render. Not sure where it should go and adding it creates the first error I had so I'm going in circles.
Any suggestions?
If it is helpful I could retrace all my previous steps and post the various error messages which led me to this point.
Well, somewhere along the line of addressing the various error messages that came up, one fix was adding ReactDOM.render()... thought it was necessary, turns out it was not.
This is sufficient and works:
import React from 'react';
import { Provider } from 'react-redux';
import { render, screen } from '#testing-library/react';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import { MemoryRouter } from 'react-router-dom';
import '#testing-library/jest-dom';
import Authentication from './Authentication';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore({
auth: {
authenticated: true,
},
});
test('authentication page renders', () => {
render(
<Provider store={store}>
<MemoryRouter>
<Authentication />
</MemoryRouter>
</Provider>
);
const authentication = screen.getByTestId('Authentication');
expect(authentication).toBeInTheDocument();
});
I have a React application that accesses a Flask API. To access some API routes, the user needs to log in. I am using Axios to do the requests. Then, he receives a token which is stored in the local storage. When this token expires and the user makes another request, I want to redirect him to the login page. However, I don't know how I would do it.
I am treating API request errors with Axios response interceptor. It removes the token from the local storage and then should redirect the user to the login page. Since I am using functional components, I could not find an example that fits well (besides downloading another package called history).
I have tried to use the 'useHistory' hook and Redirect from react-router-dom (with a proper BrowserRouter set up), but it doesn't work.
api.js
import axios from "axios"
import { RemoveAuth } from "./Auth"
export const api = axios.create({
baseURL: "http://localhost:5000/api/",
timeout: 15000,
})
// more code
api.interceptors.response.use(null, (error) => {
if(error.response.status === 401){
RemoveAuth();
}
return error;
});
Auth.js
import { useHistory } from "react-router-dom"
export const RemoveAuth = () => {
let history = useHistory()
localStorage.clear();
history.push('/login')
}
routes.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import PrivateRoutes from "./PrivateRoutes";
import Dashboard from "../pages/dashboard";
import Login from "../pages/login";
import Logout from "../pages/logout";
const Routes = () => (
<BrowserRouter>
<Switch>
<PrivateRoutes exact path="/dashboard" component={Dashboard} />
<PrivateRoutes exact path="/logout" component={Logout} />
<Route exact path="/login" component={Login} />
</Switch>
</BrowserRouter>
);
PrivateRoutes.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthLogin } from "../services/Auth";
const PrivateRoutes = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={() => (AuthLogin() ? <Redirect to="/login" /> : <Component />)}
/>
);
export default PrivateRoutes;
Thanks in advance for the help!
The simplest thing to do is to create your own history object. Something like this:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
Then in your provider pass in your custom history object:
import { Router } from 'react-router-dom'
import history from './utils/history'
ReactDOM.render(
<Router history={history}>
<App />
</Router>
document.getElementById('root')
);
This allows you to utilize your history in non-component code. Just import your history object into your Auth.js file and use it:
import { history } from './utils/history'
export const RemoveAuth = () => {
localStorage.clear();
history.push('/login')
}
As an added bonus, now your history lives in a place that is easily mock-able, so creating testing around it is more straightforward. You can find more information about creating your own custom history object in the docs.
This seems like a duplicate of a few others. But no solution has worked for me.
Navigating via links works fine. Refreshing pages or manual navigating only works on desktop (chrome and firefox, not working on Safari).
On desktop safari, and all iOS browsers, it simply shows the entire JSON object in the browser and doesn't seem to be serving the static files.
I’ve tried Router, BrowserRouter and HashRouter. The only one that works is HashRouter. But, I don’t want hashes in the url.
I'm not getting any errors, and I've console logged all over.
When I placed a log in the getProducts action creator and on the server "/products" route, Safari doesn't show the action creator console log in the browser. But, heroku logs show that the path="/products" is being hit, but not the path="/static/css/main.etc.," or path="/static/js/main.etc.," files.
Things I've looked into and/or tried:
React-router urls don't work when refreshing or writting manually
Web page not rendering correctly in iOS browsers or desktop Safari
https://github.com/ReactTraining/react-router/issues/4727
How to remove the hash from the url in react-router
React Routing works in local machine but not Heroku
https://github.com/ReactTraining/react-router/issues/4671
Here's a stripped back sample. Note: I'm using concurrently to proxy my requests.
// client/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './styles/index.css';
import App from './App'
import registerServiceWorker from './registerServiceWorker'
import { Router } from 'react-router-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import reduxThunk from 'redux-thunk'
import history from './history'
import reducers from './reducers'
const store = createStore(
reducers,
applyMiddleware(reduxThunk)
)
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>
, document.getElementById('root'))
registerServiceWorker();
// client/history.js
import createHistory from 'history/createBrowserHistory'
export default createHistory()
// client/App.js
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom'
import Home from './components/home'
import Header from './components/header'
import Products from './components/products'
import './styles/App.css'
class App extends Component {
render() {
return (
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/products" component={Products} />
<Route render={() => <p>Not found</p>} />
</Switch>
</div>
);
}
}
export default App;
// client/components/products.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as actions from '../actions'
// import ‘../polyfill’ // imported polyfil object from core-js when previously using Object.values below… same results either way…
class Products extends Component {
componentWillMount() {
this.props.getProducts()
}
renderProducts() {
/* const { products } = this.props
return Object.values(products).map((product) => {
return (
<li key={product.title}>
{product.title}
</li>
)
});*/
const productsArray = []
const { products } = this.props
for(let key in products) {
productsArray.push(<li key={products[key].title} >{products[key].title}</li>)
}
return productsArray
}
render() {
if(!this.props.products) {
return (
<div></div>
)
}
return (
<div>
<ul className="productListItemUl" >{this.renderProducts()}</ul>
</div>
)
}
}
const mapStateToProps = state => {
return { products: state.products.products }
}
export default connect(mapStateToProps, actions)(Products)
// actions/index.js
import axios from 'axios'
import {
GET_PRODUCTS
} from './types'
export function getProducts() {
return async function(dispatch) {
try {
const products = await axios.get('/products')
dispatch({ type: GET_PRODUCTS, payload: products.data })
} catch (err) {
console.log('redux thunk getProducts() action creator error')
console.log(err)
}
}
}
// server.js
"use strict";
require("babel-core")
const express = require('express');
const path = require('path');
const app = express();
const port = process.env.PORT || 3050;
const mongoUtil = require('./server/mongoUtil')
mongoUtil.connect()
const bodyParser = require('body-parser');
const jsonParser = bodyParser.json();
app.use(jsonParser);
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.resolve(__dirname, 'client/build')));
}
let productsRoute = require('./server/routes/products');
app.use('/products', productsRoute)
app.get('*', function(request, response) {
response.sendFile(path.resolve(__dirname, 'client/build', 'index.html'));
});
app.listen(port, () => console.log(`Listening on port ${port}.`));
I try to write bellow code. but redux-thunk doesn't work.
Do you know how to resolve it?
When I exec this code, it can get this error.
createStore.js:113 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
But I already installed redux-thunk. why does this error happen?
index.js
import { createDevTools } from 'redux-devtools';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { syncHistoryWithStore, routerReducer } from 'react-router-redux';
import thunk from 'redux-thunk';
import * as storage from './persistence/storage';
import randomToken from './utlis/random';
import * as reducers from './reducers';
import {
App,
Home
} from './components';
const reducer = combineReducers({
...reducers,
routing: routerReducer
});
if (!storage.get('token')) {
storage.put('token', randomToken());
}
const initialState = {
application: {
token: storage.get('token')
}
};
const DevTools = createDevTools(
<DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q">
<LogMonitor theme="tomorrow" preserveScrollTop={false} />
</DockMonitor>
);
const store = createStore(
reducer,
initialState,
compose(
applyMiddleware(
thunk
),
DevTools.instrument()
)
);
const history = syncHistoryWithStore(browserHistory, store);
ReactDOM.render(
<Provider store={store}>
<div>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
</Route>
</Router>
</div>
</Provider>,
document.getElementById('app')
);
Map.js
import React from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions/cityForcast';
export class Home extends React.Component {
constructor (props, context) {
super(props, context);
}
componentWillMount () {
this.props.dispatch(actions.search(this.props.token));
}
render() {
return (
<div>
<button onClick={() => actions.search()}>Search</button>
</div>
);
}
}
export default connect(({ application, cityForecast }) => ({ application, cityForecast }))(Home);enter code here
cityForcast.js
export function search(token) {
return dispatch => {
console.log(token);
};
}
I could resolve that for myself. I needed to install redux-thunk as devDependencies as well.
I am trying to get react-router and redux to work together.
When my component mounts, I make an ajax-call and until this one finishes I want to show a loading-message. Once the ajax-call returns successfully, I reroute to the correct page.
However, whenever I change routes programmatically, react-router doesn't update the shown page. Also, I don't want to use the browser's history but an internal one (memory history) and can't find any examples of this working together with redux.
import React, {Component, PropTypes} from 'react';
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, createMemoryHistory, useRouterHistory, routerReducer } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
let appHistory;
export default class Transfer extends React.Component {
componentWillMount(){
myAjaxCall(function(){
// Reroute
appHistory.push('/projectrepresentation'); // changes a route change in the router, but doesn't display /projectrepresentation
})
}
constructor(){
const reducers = combineReducers({
routing: routerReducer
})
store = createStore(reducers);
// Create an enhanced internal (!) history that syncs navigation events with the store
let createAppHistory = useRouterHistory(createMemoryHistory);
appHistory = createMemoryHistory();
history = syncHistoryWithStore(appHistory, store);
}
render () {
return <Provider store={store}>
<Router history={history}>
<Route path="/" component={() => <div>Loading</div>}>
<Route path="projectrepresentation" component={() => <div>Project Representation</div>}/>
<Route path="export" component={() => <div>Export</div>}/>
</Route>
</Router>
</Provider>
}
}