Author avatar

Gaurav Singhal

How to Get a Component from Anywhere in an App Using React

Gaurav Singhal

  • Aug 18, 2020
  • 15 Min read
  • 145 Views
  • Aug 18, 2020
  • 15 Min read
  • 145 Views
Web Development
Front End Web Development
Client-side Framework
React

Introduction

UI components in React can be seen as a bundle of HTML elements. Web developers often wonder whether it's possible to treat an entire component as a DOM element and reference it using id or className. Certainly React isn't built for that, but it does allow you to fetch components using their routes. This guide offers a concrete example that combines the power of React-Router and Context API to call components from anywhere in the app.

Example App Overview and Setup

This example app allows a user to sign up and log in using the browser's localStorage API. You'll learn how components are identified using routes and see the ease offered by the Context API in triggering explicit calls to these components.

Creating an Empty React Project

Create a new react project by running:

1
npx create-react-app react-router-context
shell

Adding Bootstrap Styles

Use bootstrap by adding the following line inside index.html:

1
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
html

Installing React-Router

Run the following command inside the root directory to install react-router-dom:

1
npm i react-router-dom
shell

Creating Routes

The app contains three routes for three different pages: Login, Signup, and Home. The Home component is what the user sees after successfully logging in. Consider the following code that sets up the routes for the app and renders one route at a time:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
import {Route,BrowserRouter,Switch} from 'react-router-dom';
import Home from '../components/Home';
import Signup from '../components/Signup';
import Login from '../components/Login';

const Routes=()=>{
    return(
        <>
            <BrowserRouter>
                <Switch>
                    <Route exact path="/" component={Home}/> 
                    <Route exact path="/signup" component={Signup}/> 
                    <Route exact path="/login" component={Login}/> 
                </Switch>
            </BrowserRouter>
        </>
    );
}

export default Routes;
jsx

At any point, you can call a component from anywhere in the app by referencing its route.

Creating Components

The Login component contains a bootstrap form with two fields for the user's email and password and state to store them. It also contains a handleChange() method that sets the state and a handleSubmit() method that logs out this state on the console.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import React,{useState,useEffect} from 'react';
import {Link} from 'react-router-dom';
  
const Login=()=>{
    const [loginDetails,setLoginDetails]=useState({
        email:'',
        password:''
    })

    const handleChange=(e)=>{
        setLoginDetails({...loginDetails,[e.target.id]:e.target.value})
    }

    const handleSubmit=(e)=>{
        e.preventDefault();
        console.log(loginDetails)
    }

    return(
        <>
            <div className="container">
                <div className="row">

                    <div className="col-4 mx-auto mt-5">
                    <div className="row my-3 justify-content-center">
                        <h4>Login</h4>
                    </div>
                    <div className="card">
                    <div className="card-body">
                        <form onChange={handleChange} onSubmit={handleSubmit}>
                            <div className="form-group">
                                <label htmlFor="email">Email address</label>
                                <input type="email" className="form-control" id="email" 								aria-describedby="emailHelp" placeholder="Enter email" />
                            </div>
                            <div className="form-group">
                                <label htmlFor="password">Password</label>
                                <input type="password" className="form-control" 										id="password" placeholder="Password" />
                            </div>
                            <div className="row justify-content-center my-3">
                                <Link to="/signup">Don't have an account? Signup</Link>
                            </div>
                            <div className="row justify-content-center">
                                <button type="submit" className="btn btn-												primary">Submit</button>
                            </div>
                            </form>

                    </div>
                </div>
                    </div>
                </div>

            </div>
        </>
    )
}

export default Login;
jsx

Similarly, create the Signup component as shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import React,{useState,useContext} from 'react';
import {Link} from 'react-router-dom';
  
const Signup=()=>{
    const [SignupDetails,setSignupDetails]=useState({
        email:'',
        password:'',
    })

    const handleChange=(e)=>{
        setSignupDetails({...SignupDetails,[e.target.id]:e.target.value})
    }

    const handleSubmit=(e)=>{
        e.preventDefault();
        console.log(SignupDetails)
    }

    return(
        <>
            <div className="container">
                <div className="row">

                    <div className="col-4 mx-auto mt-5">
                    <div className="row my-3 justify-content-center">
                        <h4>Signup</h4>
                    </div>
                    <div className="card">
                    <div className="card-body">
                        <form onChange={handleChange} onSubmit={handleSubmit}>
    
                            <div className="form-group">
                                <label htmlFor="email">Email address</label>
                                <input type="email" className="form-control" id="email" 								aria-describedby="emailHelp" placeholder="Enter email" />
                            </div>
                            <div className="form-group">
                                <label htmlFor="password">Password</label>
                                <input type="password" className="form-control" 										id="password" placeholder="Password" />
                            </div>
                            <div className="row justify-content-center my-3">
                                <Link to="/login">Already have an account? Login</Link>
                            </div>
                            <div className="row justify-content-center">
                                <button type="submit" className="btn btn-primary">Sign 									up</button>
                            </div>
                            </form>
                        </div>
                    </div>
                  </div>
               </div>
           </div>
        </>
    )
}

export default Signup;
jsx

Inside the Home, the component displays the authenticated user's email with a logout button.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React fom 'react';

const Home=()=>{
    return(
        <>
        <div className="container">
            <div className="row">
                <div className="col-4 mx-auto mt-5">
                <div className="card" style={{width: '18rem'}}>
                    <div className="card-body">
                        <h6 className="card-subtitle mb-2 text-muted">Email</h6>
                        <button className="btn btn-info">Logout</button>
                    </div>
                </div>
                </div>
            </div>
        </div>
        </>
    )
}

export default Home;
jsx

Creating Global Context Store

The context store communicates with your localStorage and serves as a global state for your app. Components rendered as children using its provider have access to this global state.

To begin, create a context using the createContext() hook and set up an initial state. Then create a provider, a function component that acts as a wrapper for all the components using the global state. Next, extract state and dispatch from the useReducer() hook and pass your reducer as the first argument and your initial state initState as the second argument. Finally, check your localStorage. If it is empty, assign initState to your global state; otherwise, push the existing data to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, {createContext, useEffect, useReducer} from 'react';
import  {AuthReducer} from '../reducers/AuthReducer';

export const UserContext=createContext();

const initState={
    isSignedUp:false,
    isAuthenticated:false,
    currentUser:null
}

 export const UserContextProvider=(props)=>{
  const [state, dispatch] = useReducer(AuthReducer, initState,()=>{
    const data=localStorage.getItem('state');
    return data?JSON.parse(data):initState
  });
  useEffect(()=>{
      localStorage.setItem('state',JSON.stringify(state));
  },[state])
    return(
        <UserContext.Provider value={{state,dispatch}}>
            {props.children}
        </UserContext.Provider>
    )
}
jsx

Wrap your components inside UserContextProvider.

1
2
3
4
5
6
7
...
<UserContextProvider>
    <Route exact path="/" component={Home}/> 
    <Route exact path="/signup" component={Signup}/> 
    <Route exact path="/login" component={Login}/> 
</UserContextProvider>
...
jsx

Creating Reducer

Actions inside the reducer work similar to actions in Redux. You can create cases here to determine the app flow. For instance, on submitting the sign-up form, you want to fire your SIGNUP action to add the newly created user to your database (here, the browser's localStorage). Inside the LOGIN action, write down the logic to authenticate the user. And finally, in the LOGOUT action, expire the user's session. Your reducer updates the global state based on which component you decide to render at what time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
export const AuthReducer=(state,action)=>{
    let allUsers=localStorage.getItem('users') ? 
    JSON.parse(localStorage.getItem('users')) : []
    switch(action.type){
        case "LOGIN": console.log('login')       
            if(allUsers.length==0)  return {...state, msg:'User does not exist'};
            let validLogin=false;
            allUsers.forEach(user=>{
                if(user.email===action.payload.user && 					                                   user.password===action.payload.password){
                validLogin=true;
                return;
                }
            })
            if(validLogin)
            return{
                ...state,
                isSignedUp:true,
                isAuthenticated:true,
                currentUser:{
                    email:action.payload.user,
                    password:action.payload.password
                },
                msg:'Login successful!'
            }
            else
            return{...state}

        case "SIGNUP": console.log('signup')
                                                                                                    allUsers.push({email:action.payload.user,                                                        password:action.payload.password})
           localStorage.setItem('users',JSON.stringify(allUsers))
           return {
               ...state,
            isSignedUp:true,
            isAuthenticated:false,
            currentUser:{
                email:action.payload.user,
                password:action.payload.password
            },
            msg:'Signed Up successfully! Login now.'
            };

        case "LOGOUT": console.log('logout');
            return {...state,
                isSignedUp:false,
                isAuthenticated:false,
                currentUser:null
                };
        default:        return state;
    }
}
jsx

Dispatching Actions

Import UserContext on top of every component and grab state and dispatch using the useContext hook as shown below:

1
2
3
4
5
6
import { UserContext } from '../contexts/UserContext';
  
const Login=()=>{
    const {state,dispatch}=useContext(UserContext)
    ....
}
jsx

Inside your handleSubmit() method, dispatch the needful actions for all your components.

1
2
3
4
5
6
7
8
9
//dispatching action for Login 
dispatch({
    type:"LOGIN",
    payload:{
        user:loginDetails.email,
        password:loginDetails.password
    }
})
     
jsx

Remember to specify the type and payload object so your reducer can access these values and fire the correct action.

1
2
3
4
5
6
7
8
//dispatching action for Signup 
dispatch({
    type:"SIGNUP",
    payload:{
        user:SignupDetails.email,
        password:SignupDetails.password
    }
})
jsx

Finally, inside your Home component, dispatch the LOGOUT action inside the Logout() method.

1
2
3
4
5
6
7
8
//dispatching action for logout
 
const Logout=()=>{
    dispatch({
        type:'LOGOUT'
    })
    history.push('/login')
}
jsx

Also, update your Home component with data from your the context store.

1
2
3
...
  <h6 className="card-subtitle mb-2 text-muted"> {state.currentUser.email}</h6>
...                        
jsx

Calling Components

Based on where the user is in your app flow, using the global state, you can explicitly re-route to a component. To change the current route, import the useHistory() hook from react-router-dom inside all your components.

1
2
3
4
import {Link, useHistory} from 'react-router-dom';
...
    const history=useHistory();
...
jsx

Now any component can be called by passing its route to the history.push() method. You can get your Home component for an authenticated user from anywhere in the app using the following check.

1
if(state.isAuthenticated) history.push('/')
jsx

You can also protect your Home component's route using a condition on your global state.

1
if(!state.isAuthenticated && state.isSignedUp) history.push('/login')
jsx

Conclusion

Context allows you to easily manage your state, which you might need at any point in your app, without having to manually pass it down at each level as props. You can connect your routes to your global state and conditionally call a component from anywhere in the app, as demonstrated in this guide.

1