How to use reach router with React Context API? - reactjs

I use reach router like this below before and it works fine
.....
<Router>
<ComponentA path="/:id">
<ComponentB path="/">
<Router>
....
I decided to refactor my code with context, and the code is refactored to something like this:
<GlobalContextProvider>
<GlobalContext.Consumer>
{( context) =>{
return(
.....
<Router>
<ComponentA path="/:id">
<ComponentB path="/">
<Router>
....
}
After the refactor, the ComponentA is not working properly, as the url param prop id is not passed
In the ComponentA.js , test like this:
componentDidMount() {
const { id } = this.props;
console.log(id); // return undefined
}
Also when I console.log(this.props) , it returns the same result as this.context
Can someone help me understand why this is happening? How to refactor with context properly?
Thanks a lot

I finally figure out this issue:
ComponentA is wrapped with HOC, and by adding {...this.props} to ComposedComponent,
ComponentA can access the url params from this.props
Please refer this issue Passing React context through an HOC to a wrapped component

I'm not sure it was working at first.
To access the param value, you have to do it this way :
const { match } = props;
const { params } = match;
const { id } = params;
You might need to wrap your component into withRouter(...)
import { withRouter } from "react-router-dom";
class MyComp extends PureComponent {...}
export default withRouter(MyComp);

Related

React Cannot destructure property 'params' of 'this.props.match' as it is undefined

I have a route set up to render the details for an individual item but its throwing an error saying 'this.props.match' is undefined. Hence cant deconstruct
Here is my code snippet
class ProfilesSinglePage extends Component {
constructor (props) {
super (props)
this.state={
details:''
};
}
render(){
console.log(this.props);
const {params} = this.props.match;
return (
<React.Fragment>
<h1>Profiles Details for id {params.id}</h1>
</React.Fragment>
);
};
componentDidMount(){
console.log(this.props.match.params.id);
axios.get(`${Profiles}/${this.props.match.params.id}`)
.then((res) => {
this.setState({details:res.data})
});
};
};
Here is how the route looks like
<Routes>
<Route path="/profiles" exact element={<ProfilesAllPage/>}/>
<Route path="/profile-details/:id" exact element={<ProfileSinglePage/>}/>
</Routes>
you are using react v6 version hence to get access to params you need to import useParams from react-router-dom and then create a hook to access the URL parameters.
for a better understanding visit-
https://www.codingdeft.com/posts/react-get-query-and-url-parameters/#reading-url-parameters

How to get access to state with higher order component

I want to create a higher order component that checks if a user has been logged in. If they have, I show the component if not, I want to redirect them to the login page.
can someone explain what I'm doing wrong here?
Here's the HOC:
import React from 'react';
import { connect } from 'react-redux';
const withAuthentication = (Component) => {
class WithAuthentication extends React.Component {
componentDidMount() {
console.log(this.props.sessionId);
}
render() {
return this.props.sessionId ? <Component {...this.props} /> : null;
}
}
const mapStateToProps = (state) => ({
sessionId: state.auth.userInfo.authUserInfo.sessionId
})
return connect(mapStateToProps, null)(WithAuthentication);
}
export default withAuthentication;
then I call it like this:
....
import withAuthentication from './containers/withAuthentication';
const Hello = () => <h1> Hello</h1>;
....
<Route path="/" component={ withAuthentication(Hello) }/>
I stripped more code that I think is unrelated to this...
TIA for you help!
Update: The code that is causing the propblem seems to be this:
const mapStateToProps = (state) => ({
sessionId: state.auth.userInfo.authUserInfo.sessionId
})
The error: TypeError: Cannot read property 'sessionId' of undefined
So basically, what you are doing in nesting one component into another i.e. a Functional component returning another component after some logical level verification.
What you are doing seems good. You should be able to access the props(not State), in a functional component like this
let aComponent(props)=>{
return <p>{props.name}'s logged in status is {props.loggedIn}</p>
}
if you are using the component like this
<aComponent title="Hello" loggedIn={this.props.isLoggedIn}/> //isLoggedIn from redux store
Also, If the Logical/Authentication verification fails in withAuthentication, you should call the router API to navigate to your desired page.
i.e.
you should call this, if you are using react-router
this.props.router.push('/login')

Is there any chance to pass query params as like ?id="idValue" in route?

I'm using react-router v4. We can pass query params in v4 as /user/:id?. But, I need to pass query params in route like /user/?id="id".
Any help or suggestions are appreciable.
You can use the search property on location object which react router passes to all components
E.g. if your route is /user?id=1, then
console.log(this.props.location.search) // ?id=1
You can then use a library like query-string to parse those query params
import queryString from 'query-string'
// inside your component
const params = queryString.parse(this.props.location.search)
params.id === 1
You can use a HOC which will extract and parse search property of location object provided by React Router to underlying component. Like this:
const withQuery = Component => props => {
if (props.location && props.location.search) {
const query = props.location.search.substr(1)
.split('&')
.reduce((carry, pair) => {
const [ key, val ] = pair.split('=');
carry[unescape(key)] = unescape(val);
return carry;
}, {});
return <Component {...props} query={query} />
} else {
return <Component {...props} query={{}} />
}
}
and wrap components rendered by according <Route />:
<Route
path="/query"
component={withQuery(MyComponent)}
/>
Take a look at this sandbox to get the idea in details.
You can do it using context as follows,
import PropTypes from 'prop-types'
class Sample extends React.Component {
static get contextTypes() {
return {
router: PropTypes.object.isRequired,
};
}
.....
handleSubmit(data) {
let query = {}
query['id'] = data.id
this.context.router.push({
pathname:'/path',
query: query
});
}
}
}

React context update does not cause component rerender

I'm creating a code splitting solution for react app using react router, webpack and dynamic imports. The idea is to map all the routes and corresponding components to different app contexts and split code according to app contexts. When user visits some route the whole code chunk of related app context is being loaded.
Code examples:
App.tsx:
class App extends React.Component<Props, State> {
render() {
if (this.props.data.loading || this.props.localData.loading) {
return <Loader />
}
return (
<IntlProvider locale={this.props.localData.session.locale} messages={this.state.translations}>
<Router history={history}>
<Route
exact
path={`/screens/:action?`}
render={() => <ComponentLoader contextName={Context.Screens} componentName={'ScreenList'} />}
/>
</Router>
</IntlProvider>
)
}
}
export default withData(App)
ComponentLoader.tsx:
export enum Context {
Screens = 'Screens',
Channels = 'Channels'
}
const CONTEXT_LOADERS: { [name: string]: ComponentChunkLoader } = {
[Context.Screens]: () => import('../../routerContexts/screens'),
[Context.Channels]: () => import('../../routerContexts/channels'),
}
const loadedContexts: ContextsCollection = {}
class ComponentLoader extends React.PureComponent<Props, State> {
state: State = {
Component: null
}
async componentDidMount() {
this._updateComponent(this.props)
}
async componentDidUpdate(prevProps: Props, prevState: State) {
if (this.props.componentName !== prevProps.componentName || this.props.contextName !== prevProps.contextName) {
this._updateComponent(this.props)
}
}
_updateComponent = async (props: Props) => {
let module = loadedContexts[props.contextName]
? loadedContexts[props.contextName]
: await CONTEXT_LOADERS[props.contextName]()
if (!loadedContexts[props.contextName]) loadedContexts[props.contextName] = module
let ComponentClass = module[props.componentName]
this.setState({
Component: ComponentClass
})
}
render() {
if (this.state.Component !== null) {
return <this.state.Component />
}
return <Loader />
}
}
export default ComponentLoader
So when I switch to different routes, I see all the components correctly, and code splitting works correctly.
The problem is: when the data in react context updates, ScreenList component in the example doesn't get updated. When I pass ScreenList directly to react router Route everything works well. So the problem is in my ComponentLoader component that I use for code splitting.
Any ideas what can be the reason?
UPDATE:
Alrite, what i figured out now: if I wrap my ComponentLoader in a HOC that injects some data from context (like export default withRouter(ComponentLoader)) , everything works well and components rerender as expected. Why is happening like this?

React router 1.0 pass multiple props to children routes

I'm using react-router 1.0 and react-redux on an app, and I'm wondering what strategy is best to pass props to children on larger apps. Here's a basic situation:
Let's say I have a route /admin/users/edit/:id with the following structure on its components:
Routes:
<Router>
<Route path="admin" component={Admin}>
<Route path="users" component={Users}>
<Route path="edit/:id" component={Edit}/>
</Route>
</Route>
</Router>
Admin:
class Admin extends React.Component {
render() {
return (
{this.props.children}
)
}
}
Users:
class User extends React.Component {
edit = (id, params) => {
const { dispatch } this.props;
dispatch(edit(id, params));
}
other functions (add, remove) ...
render() {
return (
{this.props.children}
)
}
}
function mapStateToProps(state) {
const { users } = state;
return { users };
}
export default connect(mapStateToProps)(User);
Edit:
class Edit extends React.Component {
submit () => {
const { id } = this.props.params;
const { firstName } = this.refs;
this.props.edit(id, {firstName: firstName.value});
}
render() {
const { id } = this.props.params;
const user = this.props.users[id];
return (
<form>
<input ref='firstName' defaultValue={user.firstName}/>
<button onClick={this.submit}>Submit</button>
</form>
)
}
}
How would I pass the users & edit function props down to the children?
I know about React.cloneElement() (as in https://github.com/rackt/react-router/tree/master/examples/passing-props-to-children), but if I have multiple routes like /users/add, /users/remove/:id, etc, I would be passing and exposing all the functions (edit, add, remove...) to all children. That solution doesn't seem to work very well when you have more than one children.
I would like to keep my children as dumb as possible, and use this same structure across the application (/images/add, /images/remove/:id, etc).
Any suggestions?
You have a few options:
First level children:
Use React.cloneElement(), that's something you are already aware of. I wouldn't use it for deeply nested Components though.
To all routes:
Use createElement():
<Router createElement={createElement} />
// default behavior
function createElement(Component, props) {
// make sure you pass all the props in!
return <Component {...props}/>
}
// maybe you're using something like Relay
function createElement(Component, props) {
// make sure you pass all the props in!
return <RelayContainer Component={Component} routerProps={props}/>
}
Check more on that in the React Router docs.
Use context:
Note:
Context is an advanced and experimental feature. The API is likely to change in future releases.
See how to use it in the React docs.
There is also the whole thread about it in the react-router#1531.

Resources