ReactJS Conditional Header with Redux - javascript

I want to create a conditional component called HeaderControl that can generate a type of Header for the aplication if the user is Logged or not.
This is my Header.jsx :
import React from 'react';
import { connect } from 'react-redux';
import { isAuthenticated } from '../Login/reducers';
import ucaLogo from '../../assets/logo.gif';
import '../../styles/base.scss';
const mapStateToProps = state => ({
isAuthenticated: isAuthenticated(state),
});
function HeaderNoLogin() {
return <div className="header-div col-md-12">
<img className="img-login-header" src={ucaLogo} alt="logo" />
<div className="title-head-div">
<p className="title-head">Not logged</p>
<p className="title-head"> Not logged</p>
</div>
</div>;
}
function HeaderLogged() {
return <div className="header-div col-md-12">
<img className="img-login-header" src={ucaLogo} alt="" />
<div className="title-head-div">
<p className="title-head">Logged</p>
<p className="title-head"> Logged</p>
</div>
</div>;
}
class HeaderControl extends React.Component {
render() {
const isLoggedIn = (props) => {
if (this.props.isAuthenticated) {
return true;
}
return false;
};
let button = null;
if (isLoggedIn) {
button = <HeaderLogged />;
} else {
button = <HeaderNoLogin />;
}
return (
<div>
{button}
</div>
);
}
}
export default connect(mapStateToProps)(HeaderControl);
My entry point (app.jsx) in have a Provider with the store like this:
const history = createHistory();
const store = configureStore(history);
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<MuiThemeProvider>
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute path="/" component={Home} />
</Switch>
</MuiThemeProvider>
</ConnectedRouter>
</Provider>,
document.getElementById('app'),
);
My questions are:
Where and how can I check if the user is authenticate using the redux store?
Where and how should I import the Header ? I think that I should import it in the app.jsx but I do not know where exactly.
Sorry if these are dumbs questions but this is the first time that I am using Reactjs with Redux.
Thanks.

If your a newbie docs has good example in react-router v4 https://reacttraining.com/react-router/web/guides/philosophy.
index.js:
export const store = createStore(
app,
composeWithDevTools(applyMiddleware(thunk))
); //mandatory to use async in redux
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Router>
<App />
</Router>
</ConnectedRouter>
</Provider>,
document.getElementById("root")
);
And in app.js:
const App = () => (
<div>
<HeaderControl /> // header will be always visible
<MuiThemeProvider>
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute path="/" component={Home} />
</Switch>
</MuiThemeProvider>
</div>
);
export default App;
Handle async logic with redux is not intuitive you shouldn't start by that if your new to redux.
reducers.js:
import {LOGGED_IN, LOGGED_OUT} from '../actions/User'
const initialState = {
logged: false
}
// reducer
function User (state = initialState, action) {
switch (action.type) {
case LOGGED_IN:
return Object.assign({}, state, {logged: true}, action.payload)
case LOGGED_OUT:
return initialState
default:
return state
}
}
export default User
You neet to setup a middlewear to handle async request in redux. It is the more simple approach. there is others options like redux saga and redux-observable. In this example i use redux-thunk.
I advise you to check redux docs on middlweare
https://redux.js.org/docs/advanced/Middleware.html
and/or this course on udemy https://www.udemy.com/react-redux-tutorial/
export const checkLogin = payload => {
return dispatch =>
xhr
.post(/login , payload) // payload is json name and password
.then(res => dispatch(LOGGED_IN))) // response is positive
.cath(err => console.log(err))
}
And your checkLogin action should be using in login.js form
login.js
import checkLogin from action.js
class Login extends React.Component {
render() {
return (
<form submit={checkLogin}>
{" "}
// this is pseudo code but i hope it's clear , you take data from your
form and you you call cheklogin.
</form>
);
}
}
So the logic is pretty simple:
In login you call the action checkLogin
A request will start to you server with login+pass
When the request end login reducer is call.
Reducer update state.
Header is refresh.

Related

React state inside React-context is not updating

Hello guys so I have the following problem:
I have a login form after user successfully provide the right info for signing in, I store the user object and access token in the AuthContext
I protect the home route using the context
Problem => the react state inside the context is not being updated
-[Edit] Solution => I found the solution and it was only changing the following code:
const {setAuth} = useAuth();
to the code:
const {setAuth} = useAuth({});
-[Edit 2] => Because I am a beginner I also discovered that navigation between components with anchor tag () or window location cause the lose of state data so I should use Link from react router dom to avoid re-rendering
AuthProvider.js
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
App.js
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
{/* public routes */}
<Route path="auth" element={<AuthLayout />}>
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
<Route path="unauthorized" element={<Unauthorized />} />
</Route>
{/* we want to protect the following routes */}
{/* RequireAuth having outlet => return child only if context auth has user object */}
<Route element={<RequireAuth />}>
<Route path="home" element={<Home />} />
</Route>
{/* catch all */}
</Route>
</Routes>
);
}
export default App;
RequireAuth.js [ Problem is here, the auth is always empty]
const RequireAuth = () => {
const { auth } = useAuth();
const location = useLocation();
return auth?.userObject ? (
// we used from location and replace so we want to save the previous location of the visited page by user so he is able to go back to it
<Outlet />
) : (
<Navigate to="/auth/login" state={{ from: location }} replace />
);
};
export default RequireAuth;
Login.js [Here I update the state]
const handleFormSubmission = async (data,e) => {
e.preventDefault();
try {
const response = await axios.post(
ApiConstants.LOGIN_ENDPOINT,
JSON.stringify({
Email: email,
Password: password,
}),
{
headers: ApiConstants.CONTENT_TYPE_POST_REQUEST,
}
);
//const roles = ;
const userObject = response?.data;
const accessToken = response?.data?.token;
setAuth({ userObject, password, accessToken });
console.log(userObject);
console.log(accessToken);
console.log(password);
message.success("You are successfully logged in");
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./context/AuthProvider";
import "./styles/ant-design/antd.css";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/*" element={<App />} />
</Routes>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
);
Did you import setAuth correctly?
Did you call setAuth({}) inside handleFormSubmission function?
I have gone through the code and there are few suggestions which will make code work.
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
function updateAuth(authVal) {
console.log("update auth called with value", authVal);
setAuth(authVal);
}
return (
<AuthContext.Provider value={{ auth, updateAuth }}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
Also we need few changes in the consumer code which looks like below
const { updateAuth } = useContext(AuthContext);
const handleFormSubmission = async (e) => {
e.preventDefault();
try {
const response = await axios.get("https://catfact.ninja/breeds?
limit=1");
//const roles = ;
const userObject = response?.data;
console.log("cats api data response is ", userObject);
updateAuth(userObject);
//setAuth({ userObject, password, accessToken });
} catch (err) {
console.error("some error");
}
};
I have made sample application on codesandbox for the same as well. Please find url as https://codesandbox.io/s/funny-scott-n7tmxs?file=/src/App.js:203-689
I hope this help and have a wonderful day :)

Issue with history.push

I'm working on react Disney+ clone etc and I was trying to do something like: if user isnt authorized then show login page but if authorized then show content. I used useHistory for this. And it works for a second, it just starts to download login page (background image is loading, but text of login page is visible) and then it disappears and content page is shown. Url changes for a second too.
App.js
function App() {
return (
<div className="App">
<Router>
<Header />
<Switch>
<Route path="/login">
<Login/>
</Route>
<Route path="/detail/:id">
<Detail/>
</Route>
<Route path="/">
<Home/>
</Route>
</Switch>
</Router>
</div>
);
}
Header.js
import React from 'react'
import {selectUserName, selectUserPhoto, setUserLogin, setUserSignOut} from '../../features/user/userSlice' ;
import { useDispatch, useSelector } from "react-redux" ;
import { auth, provider} from "../../firebase"
import { useHistory} from 'react-router-dom';
const Header = () => {
const dispatch = useDispatch()
const userName = useSelector(selectUserName);
const userPhoto = useSelector(selectUserPhoto);
const history = useHistory();
const signIn = () => {
auth.signInWithPopup(provider)
.then((result) => {
let user = result.user;
dispatch(setUserLogin({
name: user.displayName,
email: user.email,
photo: user.photoURL
}))
history.push('/');
})
}
const signOut = () => {
auth.signOut()
.then(() => {
dispatch(setUserSignOut());
history.push('/login');
})
}
}
Based on your issue you can handle Routes in a different way. So you have routes which can only shown during unauthorised situation and some routes only shown for authorised user. For that you can have following implementation.
First you can create ProtectedRoute function.
import React from "react";
import { Redirect, Route } from "react-router-dom";
function ProtectedRoute({ component: Component, ...restOfProps }) {
const isAuthenticated = localStorage.getItem("isAuthenticated");
console.log("this", isAuthenticated);
return (
<Route
{...restOfProps}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
export default ProtectedRoute;
And then you can use this function in your main App where you will declare your routes with component.
import ProtectedRoute from "./component/ProtectedRoute";
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path="/login" component={Login} />
<ProtectedRoute path="/protected" component={ProtectedComponent} />
</BrowserRouter>
</div>
);
}
export default App;

Why is my component unable to access data from my reducer?

I am writing a React app in which somebody can sign up as a business or user, and a user is able to search for a business by name. I do not understand why I am getting an error when trying to render my search component, saying "TypeError: Cannot read properties of undefined (reading 'toLowerCase')". I do not understand why I am getting this error because I believe I am passing in the appropriate data via my reducers and the Redux store. This is my search component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import BusinessCard from '../Business/BusinessCard'
import { Card } from 'semantic-ui-react';
class Businesses extends Component {
state = {newSearch: ""}
handleInputChange = e => {
this.setState({newSearch: e.target.value})
}
render() {
const { businessesReducer} = this.props;
let businessesMatch = businessesReducer.businesses.filter( (business ) => business.name.toLowerCase().includes(this.state.newSearch.toLowerCase()))
return (
<div>
<input placeholder="Search Events and Services Near You" value={this.state.newSearch} name="businessName" type="text" onChange={this.handleInputChange} />
<Card.Group itemsPerRow={3}>
{ businessesMatch.map((business, id) => <BusinessCard key={id} business={business} />)}
</Card.Group>
</div>
)
}
}
const mapStateToProps = (state) => {
return ({
businessesReducer: state.businessesReducer
})
}
export default connect(mapStateToProps)(Businesses);
My businesses reducer:
const initialState =
{
businesses:[],
isLoading: false
}
export default (state = initialState, action) => {
switch (action.type) {
case 'LOADING':
return {
...state,
isLoading: true
}
case "GET_ALL_BUSINESSES_SUCCESS":
return { ...state,
businesses: action.businesses,
isLoading: false
}
default:
return state
}
}
BusinessCard.js (which I am trying to render per the user's search)
import React, { Component } from 'react';
import { Card } from 'semantic-ui-react';
import { connect } from 'react-redux';
class BusinessCard extends Component {
constructor(props) {
super(props);
}
render(){
const { business, businessesReducer } = this.props;
return(
<Card>
<div key={business.id} >
<Card.Content>
<Card.Header><strong>{business.name}</strong></Card.Header>
</Card.Content>
</div>
</Card>
)
}
}
const mapStateToProps = state => {
return {
businesses: state.businesses,
businessesReducer: state.businessesReducer
}
}
export default connect(mapStateToProps)(BusinessCard);
And App.js
import { getAllBusinesses } from './actions/business/business';
import { BrowserRouter as Router, Route, Switch} from 'react-router-dom';
import history from './history';
class App extends React.Component {
componentDidMount() {
this.props.getAllBusinesses();
}
render() {
return (
<Router history={history}>
<div className="App">
<NavBar />
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About} />
<Route path="/services" component={Services} />
<Route path="/shop" component={Shop}/>
<Route path="/login-signup" component={LoginContainer}/>
<Route path="/signup" component={Signup}/>
<Route path="/business-signup" component={BusinessSignup}/>
<Route path="/professional-signup" component={ProfessionalSignup}/>
<Route path="/search" component={Businesses}/>
</Switch>
</div>
</Router>
)
}
}
const mapStateToProps = (state) => {
return {
businessesReducer: state.businessesReducer
}
}
export default connect(mapStateToProps, {getAllBusinesses})(App);
Does anybody have any idea why my search component cannot access "business" and its properties? Everything looks correct to me.
1: It would be good if you could show getAllBusinesses.
2: Please make sure if data exists in your store, you can use redux-dev-tools for that.
3: The first time that your component renders there is no data in your store and it's just an empty array so please first check if name exists and has value then try to convert it to lower case.
It would be something like this:
let businessesMatch = businessesReducer.businesses.filter(
(business) =>
business.name &&
business.name
.toLowerCase()
.includes(this.state.newSearch.toLowerCase())
);
Or if with optional chaining:
let businessesMatch = businessesReducer.businesses.filter((business) =>
business?.name
.toLowerCase()
.includes(this.state.newSearch.toLowerCase())
);
If none of these help please provide more information like a code sandbox.

React refresh component on login

I have 2 components, NavBar which contains a login modal and the 'body' of page.
I want to detect when a user logs in and re-render the page based on that. How do I update the login prop in the second component when I log in using the modal of the first one?
A simplified version of the code to keep it short:
// NavBar.js
export default class NavBar extends Component {
constructor(props) {
super(props)
this.initialState = {
username: "",
password: "",
loginModal: false
}
this.handleLogin = this.handleLogin.bind(this)
}
handleLogin(e) {
e.preventDefault()
loginAPI.then(result)
}
render() {
return( <nav> nav bar with links and login button </nav>)
}
// Some random page
export default class Checkout extends Component {
constructor(props) {
super(props);
this.state = {
order_type: 'none',
loggedIn: false
}
this.Auth = new AuthService()
}
componentDidMount() {
if (this.Auth.loggedIn()) {
const { username, email } = this.Auth.getProfile()
this.setState({ loggedIn: true, email: email })
}
try {
const { order_type } = this.props.location.state[0]
if (order_type) {
this.setState({ order_type: order_type })
}
} catch (error) {
console.log('No package selected')
}
}
componentDidUpdate(prevProps, prevState) {
console.log("this.props, prevState)
if (this.props.loggedIn !== prevProps.loggedIn) {
console.log('foo bar')
}
}
render() {
return (
<section id='checkout'>
User is {this.state.loggedIn ? 'Looged in' : 'logged out'}
</section>
)
}
}
// App.js
function App() {
return (
<div>
<NavBar />
<Routes /> // This contains routes.js
<Footer />
</div>
);
}
// routes.js
const Routes = () => (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/register" component={Register} />
<Route exact path="/registersuccess" component={RegisterSuccess} />
<Route exact path="/faq" component={FAQ} />
<Route exact path="/checkout" component={Checkout} />
<Route exact path="/contact" component={Contact} />
{/* <PrivateRoute exact path="/dashboard" component={Dashboard} /> */}
<Route path="/(notfound|[\s\S]*)/" component={NotFound} />
</Switch>
)
I would recommend using the react context API to store information about the logged in user.
See: https://reactjs.org/docs/context.html
Example
auth-context.js
import React from 'react'
const AuthContext = React.createContext(null);
export default AuthContext
index.js
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import AuthContext from './auth-context.js'
const AppWrapper = () => {
const [loggedIn, setLoggedIn] = useState(false)
return (
<AuthContext.Provider value={{ loggedIn, setLoggedIn }}>
<App />
</AuthContext.Provider>
)
}
ReactDOM.render(
<AppWrapper/>,
document.querySelector('#app')
)
Then inside any component you can import the AuthContext and use the Consumer component to check if the user is logged in order set the logged in state.
NavBar.js
import React from 'react'
import AuthContext from './auth-context.js'
const NavBar = () => (
<AuthContext.Consumer>
{({ loggedIn, setLoggedIn }) => (
<>
<h1>{loggedIn ? 'Welcome' : 'Log in'}</h1>
{!loggedIn && (
<button onClick={() => setLoggedIn(true)}>Login</button>
)}
</>
)}
</AuthContext.Consumer>
)
export default NavBar
HOC version
with-auth-props.js
import React from 'react'
import AuthContext from './auth-context'
const withAuthProps = (Component) => {
return (props) => (
<AuthContext.Consumer>
{({ loggedIn, setLoggedIn }) => (
<Component
loggedIn={loggedIn}
setLoggedIn={setLoggedIn}
{...props}
/>
)}
</AuthContext.Consumer>
)
}
export default withAuthProps
TestComponent.js
import React from 'react'
import withAuthProps from './with-auth-props'
const TestComponent = ({ loggedIn, setLoggedIn }) => (
<div>
<h1>{loggedIn ? 'Welcome' : 'Log in'}</h1>
{!loggedIn && (
<button onClick={() => setLoggedIn(true)}>Login</button>
)}
</div>
)
export default withAuthProps(TestComponent)
Alternatively if you have redux setup with react-redux then it will use the context API behind the scenes. So you can use the connect HOC to wrap map the logged in state to any component props.

React pass props to children edge case?

My app has the structure below and I would like to pass a prop based in the Header state to the RT component. I can pass it easily with Context but I need to call an API when the prop is a certain value and Context doesn't seem to be designed for this use due to it using the render pattern.
In Header.js I render the children with this.props.children. To pass props I've tried the following patterns but nothing is working. I'm missing a concept here. What is it?
(1) React.Children.map(children, child =>
React.cloneElement(child, { doSomething: this.doSomething }));
(2) {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
(3) <Route
path="/issues"
render={({ staticContext, ...props }) => <RT {...props} />}
/>
Structure:
App.js
<Header>
<Main />
</Header>
Main.js
const Main = () => (
<Grid item xl={10} lg={10}>
<main>
<Switch>
<Route exact path="/" component={RT} />
<Route path="/projects" component={Projects} />
<Route path="/issues" component={RT}/>
<Route path="/notes" component={Notes} />
</Switch>
</main>
</Grid>
);
I would personally recommend using the React Context API to handle the user state rather than manually passing it via props. Here's an example of how I use it:
import React from 'react';
export const UserContext = React.createContext();
export class UserProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
user: false,
onLogin: this.login,
onLogout: this.logout,
};
}
componentWillMount() {
const user = getCurrentUser(); // pseudo code, fetch the current user session
this.setState({user})
}
render() {
return (
<UserContext.Provider value={this.state}>
{this.props.children}
</UserContext.Provider>
)
}
login = () => {
const user = logUserIn(); // pseudo code, log the user in
this.setState({user})
}
logout = () => {
// handle logout
this.setState({user: false});
}
}
Then you can use the User context wherever you need it like this:
<UserContext.Consumer>
{({user}) => (
// do something with the user state
)}
</UserContext.Consumer>

Categories

Resources