React redux - dispatch in App.js outside provider? - javascript

Let me explain my problem. I have an app reacting on an RBAC model using Redux. To do this, I need to call my dispatch() in the useEffect when loading my app to check if the user is authenticated or not. If yes, I would need to dispatch its information contained in the jwt token.
So I did something like this (in App.jsx) :
const App = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const dispatch = useDispatch()
const authentication = () =>
isAuthenticated ? (
<Redirect to="/app" />
) : (
<PublicRoutes />
);
useEffect(() => {
setIsAuthenticated(Auth.isAuth())
if(isAuthenticated){
store.dispatch(setConnectedUser({name:"Jude"}))
}
}, [isAuthenticated])
const store = createStore(rootReducer, composeWithDevTools(
applyMiddleware(thunk),
));
return (
<Provider store={store}>
<HashRouter>
<Switch>
<Route path="/app" component={PrivateRoutes} />
<Route path="" render={authentication} />
</Switch>
</HashRouter>
</Provider>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById("root"))
Auth.isAuth() just fetches the token, checks if the token is still valid, and if so it returns true.
But here's the thing, by doing it this way I'm making an error :
Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a
I understand that the mistake comes from the fact that I want to dispatch() outside the provider, but is it possible to do so? or would I do it wrong? This is my first project with Redux, maybe I didn't fully understand how it works?
Knowing that the dispatch() works very well, it was called at login time before, but I have an error on my header that will retrieve the info from the reducer, it tries to display the store information before it is there, that's why I would like to do the control at application loading, and not only at login.

With your code you aren't able to use redux in this component, just in all his children.
You can just set the provider outside, maybe in the index.js (or wherever you do the ReactDOM.render() or any superior call).
Or if you wish, you can create any new element that will be used in App.js, like 'router.js' or similar where you can check your logic and redirect for where you want.

Related

React useEffect useContext - context works with some components, but not in dynamic route

I am working on a React blog app and have posts in markdown files with metadata. The metadata is stored in Firestore. The .md files with the content are stored in Cloud Storage. The App component uses useEffect get the metadata for each post from Firestore and save it (an array of objects) into PostContext. This is the App component:
import React, { useEffect } from 'react'
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
import { firestore } from './firebase'
import { usePostsUpdate } from "./contexts/PostContext"
import PrivateRoute from "./components/auth/PrivateRoute"
...
function App() {
const savePosts = usePostsUpdate()
useEffect(() => {
const postsRef = firestore.collection('metadata')
postsRef.get()
.then((snapshot) => {
const posts = snapshot.docs.map((doc) => ({
...doc.data()
}))
posts.sort((a, b) => b.postId - a.postId)
savePosts(posts)
console.log('App: in useEffect, posts.length', posts.length)
})
}, [])
console.log('APP')
return (
<React.Fragment>
<Router>
<Switch>
<Route exact path="/" component={Home} />
...
<Route path="/all-posts" component={AllPostsPage} />
<Route path='/post/:id' render={props => <Post {...props} />} />
<Route path='/404' component={NotFoundPage} />
</Switch>
</Router>
</React.Fragment>
);
}
export default App;
There are 2 issues. First, regarding context. The 'savePost()' function is saving the array to PostContext, which is being used in the home and 'all-posts' pages successfully. But, when I try a '/post/:id' to access a specific post, the array pulled in from PostContext is empty. Somehow, it seems that the array is being reset to it's default (empty) value, so there are errors when I try to access it (the array holds the urls to access the markdown files in Cloud Storage that are needed to retrieve the content needed on the page). I can't find a reason for this to happen.
Second, React is telling me to include 'savePosts' as a dependency in useEffect. But when I do that, it just loops. The same thing happens if I remove the dependency array (which I don't really want to do). There is no dependency I can add that satisfies React and doesn't loop continuously.
Now it works. I was entering urls manually to test the routing. I went ahead and set up dynamic routing (routing to /post/id by clicking on a post summary on the home page), and now the array in context is populated properly. I don't understand why manually entering 'localhost:3000/post/1' in the browser works differently than routing to the same URL by clicking on a link, but it does.
That didn't solve the issue with the React useEffect warning, but I can live with that.

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 Router V4 protected private route with Redux-persist and React-snapshot

I'm implementing private route like so using React Router Route Component:
function PrivateRoute({component: Component, authed, emailVerified, ...rest}) {
return (
<Route
{...rest}
render={props =>
authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/', state: {from: props.location}}} />}/>
)
}
Expected Behavior:
authed is persisted through page refresh through using redux-persist
So on page refresh or reload, if authed prop is true then router should render <Component /> without ever going to path "/"
Actual Behavior which is the Problem:
With authed === true (persisted)
reloading the page or refreshing it leads to the following actions taking place(checked redux devtools)
the action:
"##router/LOCATION_CHANGE" runs and takes it to the correct secure route but then "##router/LOCATION_CHANGE" runs again and it redirects to "/" for a moment and finally
"##router/LOCATION_CHANGE" runs again and directs route back to the secure path, even though authed === true through all this in the redux devtools
Then: My guess was that this error has something to with my main App Component rendering before redux-persist has time to re-hydrate the Redux store.
So I tried doing the following:
I tried delaying my main App component render until my store is re-hydrated using redux-persist like so:
render() {
const {authed, authedId, location, rehydrationComplete} = this.props
return (
<div>
{ rehydrationComplete
? <MainContainer>
<Switch key={location.key} location={location}>
<Route exact={true} path='/' component={HomeContainer} />
<Route render={() => <h2> Oops. Page not found. </h2>} />
</Switch>
</MainContainer>
: <div>...Loading </div> }
</div>
)
}
This effectively fixes the issue above of the path changing when "##router/LOCATION_CHANGE" action runs(only Changes the path keys), However this leads to another Issue with React-snapshot Now: all the static generated html files from react-snapshot Now contain only ...Loading. I tried to set snapshotDelay of 8200 in the react-snapshot options but that didnt solve the issue.
Then:
I tried the following to delay React-snapshot call so that it renders html after the store has been rehydrated:
import {render as snapshotRender} from 'react-snapshot'
import {ConnectedRouter} from 'react-router-redux'
async function init() {
const store = await configureStore()
snapshotRender(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
registerServiceWorker()
}
init()
But now i get the error: that 'render' from react-snapshot was never called. Did you replace the call to ReactDOM.render()?
I know this is a loaded question, but I want to effectively use these 3 libs(React-Router V4, Redux-persist, React-snapshot) together to serve protected routes without the mentioned errors.
I have something similar to you. Here I use React-Router V4 and a persist-like library.
Your router/routes doesn't need to be aware of the persist. They should rely on your redux's store. The persist should rehydrate your store with all the data.
I didn't see where you are using the PrivateRoute component in your example. Where is it?

location missing in props (ReactJS)

I'm kind of new to reactjs and I'm learning step by step. I'm facing a problem and that is when I try to access the location parameter in the props it returns me undefined. I tried to find the solution but most of the people have written I have to add my component in the router but I'm wrapping it in the router but still, I don't have access to location parameter
export const abcdContainer = withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(abcd));
I'm trying to access the location parameter in the and component but the problem is there is no location parameter in it. Can somebody tell me what is it that is going wrong
Please if anybody know what is wrong please tell me I have spent half of my day and I can't figure it out
CODE AND VERSION ADDED WITH URL
Router version => 2.8.1
URL : http://localhost:3000/somePage?someParam=cm9oYW5qYWxpbHRlYWNoZXJAZ21haWwuY29t
abcdContainer.js
const mapStateToProps = (state, ownProps) => {
// some code over here
}
const mapDispatchToProps = (dispatch, ownProps) => {
// some code over here
};
export const abcdContainer = withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(abcd));
abcd.jsx
class abcd extends Component {
constructor(props, context) {
super(props, context);
this.state = {
// setting state over here
};
}
abcdFunction(){
if (this.props.location.query.someParam !== undefined){ // trying to extract someParam value from location
// some code over here
}
}
render() {
// some code over here
}
}
export default CSSModules(abcd, styles, { allowMultiple: true });
Here is the flow. The router redirect to the container and then the container redirect to the real component
Route.js
export const Routes = ({ store }) => (
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={aContainer}>
<IndexRoute component={IContainer} />
// some routes
<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
// some routes
</Route>
// some routes
</Router>
</Provider>
);
Routes.propTypes = {
store: React.PropTypes.object.isRequired,
};
<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
If you use an inline arrow function to render your component why you don't just pass the props directly to the component? Then you will not need withRouter(). Like this:
<Route path="/openAbcd" component={props => (<abcdContainer caller="aaaaaaa" {...props} />)} />
Also the docs of react-router v2.8.1 says about withRouter():
A HoC (higher-order component) that wraps another component to provide props.router.
It doesn't provide location but router as a prop. I recommend you to update react-router to v4 or at least v3.
EDIT: "But why were the props not being inserted implicitly?":
React knows two types of components. Stateless functional components and class-based components. Functional components are functions that accept a single props object argument with data and return a React element. Take a look at this line of your code again:
<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
You passed an arrow function () => (<abcdContainer caller="aaaaaaa" />) to your <Route> element which is an inline definition of a functional component that takes props as a parameter and returns a rendered React element, in this case this is your <abcdContainer>. But as you can see you omitted the props parameter in your function by defining it with empty parenthesis: () => (<AComponent />). React does not automatically pass props to child components that are rendered inside a functional component. When wrapping your <abcdContainer> into an inline functional component you are responsible for passing props to it yourself.
But if you pass the class/variable of your class-based/functional component to the component prop of your <Route> element like you did it in your other routes then it will render this component an implicitly pass props to it because it isn't wrapped in a functional component:
// this will directly render <aContainer> and pass the props to it
<Route path="/" component={aContainer}>
What you did is creating a "functional unnamed wrapper component" that doesn't take any props and also doesn't pass any props further down.
And note that you should not use withRouter() extensively. This HOC is only there to inject a router into components that do not get rendered by a matching route itself and are e.g. much deeper down your component tree. In your case you do not need it.

Apollo + React Router 4 SSR issue

Not sure if this is an issue with React Router v4, the React Apollo client or my implementation.
But with <ApolloProvider> as the top-level HOC, i.e:
const ComponentsWithData = await getDataFromTree(
<ApolloProvider client={apolloClient}>
<StaticRouter location={ctx.request.url} context={route}>
<App />
</StaticRouter>,
</ApolloProvider>,
);
const html = ReactDOMServer.renderToString(ComponentsWithData);
... I get:
Warning: Failed prop type: Invalid prop children of type array supplied to ApolloProvider, expected a single ReactElement.
in ApolloProvider
Error React.Children.only expected to receive a single React element child.
And flipped around, with React Router's <StaticRouter> as the top, i.e.:
const ComponentsWithData = await getDataFromTree(
<ApolloProvider client={apolloClient}>
<StaticRouter location={ctx.request.url} context={route}>
<App />
</StaticRouter>,
</ApolloProvider>,
);
... I then get:
A <Router> may have only one child element
Rendering works fine in the browser (with React Router's <BrowserRouter>), but fails on the server.
It also works well in React Router v3 due to doing all of the route matching outside of the React hierarchy, and not declaratively inside of it.
This was actually a user error. I expected getDataFromTree() to return a Promise that resolved to the original component chain (with the correctly injected GraphQL data props), but it actually just waits for the data to be ready.
The correct format is:
const Components = (
<StaticRouter location={ctx.request.url} context={route}>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</StaticRouter>
);
await getDataFromTree(Components);
const html = ReactDOMServer.renderToString(Components);

Categories

Resources