I am using React Redux for state management. I have an anonymous user and an admin. I have current user which is empty {} and I am changing it to store the user details when the admin logs in. So, in my application I want to show the admin navbar if there is a logged user and I would like to show the anonymous navbar when there is not. Currently, when the admin logs in, the navbar is changed, but when I click logout, navbar is not changed. How can I solve this issue?
This is my NavBar.js:
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import NavBarAdmin from "./NavBarAdmin";
import NavBarAnonymous from "./NavBarAnonymous";
function NavBar() {
const currentUser = useSelector((state) => state.currentUser);
const [currentView, setCurrentView] = useState('anonymous');
useEffect(() => {
console.log(currentUser);
if(currentUser==null || currentUser.length==0 || currentUser == undefined){
setCurrentView('anonymous');
}else{
setCurrentView('admin');
}
}, [currentUser])
const handleView = () => {
switch(currentView){
case "anonymous": return <>
<NavBarAnonymous/>
</>
case "admin":
return <>
<NavBarAdmin/>
</>
}
}
return(
<>
{handleView()}
</>
)
}
export default NavBar;
My actions.js looks like:
import actionTypes from "./actionTypes";
export function setCurrentUser(value) {
return{
type: actionTypes.SET_CURRENT_USER,
payload: value
}
}
actionTypes.js
const actionTypes = {
SET_CURRENT_USER: 'SET_CURRENT_USER',
}
export default actionTypes;
configureStore.js
import { applyMiddleware, compose, createStore } from "redux";
import thunkMiddleware from "redux-thunk";
import rootReducer from './reducers';
export default function configureStore(preloadedState) {
const middlewares = [thunkMiddleware];
const middlewareEnhancer = applyMiddleware(...middlewares);
const enhancers = [middlewareEnhancer];
const composedEnhancers = compose(...enhancers);
const store = createStore(rootReducer, preloadedState, composedEnhancers);
return store;
}
reducers.js:
import { combineReducers } from "#reduxjs/toolkit";
import actionTypes from "./actionTypes";
export function currentUser(state = {}, action) {
switch (action.type) {
case actionTypes.SET_CURRENT_USER:
return Object.assign({}, state, {
q: action.payload,
})
default:
return state
}
}
export default combineReducers({
currentUser
});
In the logout functionality I am using:
dispatch(setCurrentUser({}));
And when I click logout button, {} is printed in the console, which means that the current user is set to {}, but the NavBar is not re-rendered.
Related
Props.navigation was an empty object no matter what I tried...
so Im trying to render a screen change conditionally in app.js
I've tried about a hundred different things and cant figure it out, its been days. please help!
There is no error it just doesnt navigate away once a user is signed in. in firebase... it shows they are signed in...
I think i need a subscribe function ... like below but I need help making it work as I am jon snow and this somehow needs to mix with useeffect.
function handleStateChange () {
let previousValue = isLoggedIn
isLoggedIn = store.getState().logIn.isSignedIn;
if(previousValue !== isLoggedIn){
console.log(`${previousValue} ${isLoggedIn}`)
}
}
store.subscribe(handleStateChange);
here is the console.log...
undefined false
undefined false
false true
false true
APP.JS....
import { StyleSheet, Text, View,Button } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import WorkoutNavigator from './navigation/WorkoutNavigator';
import Colors from './constants/Colors';
import { createStore , combineReducers, applyMiddleware} from 'redux';
import workOutReducer from './store/Reducers';
import {Provider, useSelector } from 'react-redux';
import ReduxThunk from 'redux-thunk'
import { useEffect, useState } from 'react';
import { initializeApp } from "firebase/app";
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import LoginScreen from './screens/LoginScreen';
import loginReducer from './store/AuthStore/reducer';
const rootReducer = combineReducers({
favorites: workOutReducer,
logIn: loginReducer
});
const store = createStore(rootReducer, applyMiddleware(ReduxThunk));
export default function App(props) {
const [loggedIn, setLoggedIn] = useState(false);
let isLoggedIn = store.getState().logIn.isSignedIn;
useEffect(()=> {
const firebaseConfig = {
},[]);
useEffect(()=>{
setLoggedIn(isLoggedIn)
}, [isLoggedIn])
return (
<Provider store={store}>
{loggedIn ? <WorkoutNavigator /> : <LoginScreen /> }
</Provider>
);
}
...here is my reducer...
import { SIGNIN, SignInACtion } from "./actions"
const initialState = {
isSignedIn: false,
}
const loginReducer = (state = initialState, action) => {
switch(action.type){
case SIGNIN:
return{
isSignedIn: true
}
default: return state;
}
};
export default loginReducer;
here is the action in case you need to see it...
import axios from "axios";
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
export const SIGNUP = 'SIGNUP';
export const SIGNIN = 'SIGNIN';
export const SignUpAction = (phone) => {
return async dispatch => {
try{
await axios.post('https://us-central1-one-time-password-d8d7a.cloudfunctions.net/createUser',{
phone
})
await axios.post('https://us-central1-one-time-password-d8d7a.cloudfunctions.net/requestOneTimePass',{
phone
})
}catch(error){console.log(error)};
dispatch({type: SIGNUP, phone: phone})
};
};
export const SignInACtion = (phone, code) => {
return async dispatch => {
try{
let {data}= await axios.post('https://us-central1-one-time-password-d8d7a.cloudfunctions.net/verifyPassword',{
phone,
code
});
firebase.auth().signInWithCustomToken(data.token)
} catch (error){console.log(error)};
dispatch({type: SIGNIN, payload: {
isSignedin: true,
}})
}
}
Firebase'auth provides a simple way to check whether the user authenticated, let's leverage this feature and refactor code as below:
import { StyleSheet, Text, View, Button } from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import WorkoutNavigator from "./navigation/WorkoutNavigator";
import Colors from "./constants/Colors";
import { createStore, combineReducers, applyMiddleware } from "redux";
import workOutReducer from "./store/Reducers";
import { Provider, useSelector } from "react-redux";
import ReduxThunk from "redux-thunk";
import { useEffect, useState } from "react";
import { initializeApp } from "firebase/app";
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";
import LoginScreen from "./screens/LoginScreen";
import loginReducer from "./store/AuthStore/reducer";
const rootReducer = combineReducers({
favorites: workOutReducer,
logIn: loginReducer,
});
const store = createStore(rootReducer, applyMiddleware(ReduxThunk));
export default function App(props) {
const [loggedIn, setLoggedIn] = useState(null);
const authenticateUser = () => {
// Detected if user is already logged in
firebase.auth().onAuthStateChanged((user) => {
if (user) {
setLoggedIn(true);
} else {
setLoggedIn(false);
}
});
};
let isLoggedIn = store.getState().logIn.isSignedIn;
useEffect(() => {
if (!loggedIn) {
authenticateUser();
}
}, [loggedIn]);
if (loggedIn === null) return null;
return (
<Provider store={store}>
{loggedIn ? <WorkoutNavigator /> : <LoginScreen />}
</Provider>
);
}
I am new to Next.js, So I follow some tutorials for Redux integration in Next.js. All is working fine but whenever I switch between pages, each time API make a call, and Redux lost its stored value.
The basic function is like this. Whenever a user loads a website an API call will fetch category data from the server and save that data in reducer[categoryReducer], then the user can navigate to any page and category data will fetched from the reducer. But in my case, it hits again and again
Full Code:
// Action Call
import * as Constants from '../../constant/constant';
import * as t from '../types';
import axios from 'axios';
export const loadCategoryApi = (type) => dispatch => {
axios.post(Constants.getCategories,type)
.then(function (response) {
console.log(response);
if(response && response.data && response.data.status==="200"){
dispatch({
type: t.LOAD_CATEGORY,
value: type
});
}
else if(response && response.data && response.data.status==="404"){
alert('Someting went wrong');
}
})
}
// Reducer File
import * as t from '../types';
const initialState = {
doc:null
}
const CategoryReducer = (state = initialState, action) =>{
console.log('reducer action', action.type);
switch (action.type){
case t.LOAD_CATEGORY:
console.log('slots actions',action);
return({...state, doc:action.value})
default:
return state;
}
}
export default CategoryReducer;
// Store file
import { createStore, applyMiddleware, compose } from "redux"
import thunk from "redux-thunk"
import { createWrapper } from "next-redux-wrapper"
import rootReducer from "./reducers/rootReducer"
const middleware = [thunk]
const makeStore = () => createStore(rootReducer, compose(applyMiddleware(...middleware)))
export const wrapper = createWrapper(makeStore);
// rootReducer
import { combineReducers } from "redux"
import CategoryReducer from "./CategoryReducer";
const rootReducer = combineReducers({
CategoryReducer: CategoryReducer
})
export default rootReducer;
// _app.js
import React from "react"
import { wrapper } from "../redux/store"
import Layout from '../components/Layout';
import '../styles/globals.css'
const MyApp = ({ Component, pageProps }) =>(
<Layout>
<Component {...pageProps} />
</Layout>
);
export default wrapper.withRedux(MyApp);
// Uses
import React, { useState, useEffect } from 'react';
import {connect} from "react-redux";
import {loadCategoryApi} from "../redux/actions/CategoryAction";
function navbar(props){
const { loadCategory, loadCategoryApi } = props;
useEffect(() => {
if(loadCategory===null){
console.log('navbar loading funciton');
loadCategoryFunation();
}
}, []);
const loadCategoryFunation = () =>{
var json = {
type : 'main'
};
loadCategoryApi(json);
}
}
const mapStateToProps = state => {
return { loadCategory: state.CategoryReducer.doc }
}
const mapDispatchToProps = {
loadCategoryApi
}
export default connect(mapStateToProps, mapDispatchToProps)(Navbar)
What I am doing wrong?
You have to create main reducer to handle the hydration. I explained this hydration process here.
In the file that you created the store, write main reducer
import reducers from "./reducers/reducers";
const reducer = (state, action) => {
// hydration is a process of filling an object with some data
// this is called when server side request happens
if (action.type === HYDRATE) {
const nextState = {
...state,
...action.payload,
};
return nextState;
} else {
// whenever we deal with static rendering or client side rendering, this will be the case
// reducers is the combinedReducers
return reducers(state, action);
}
};
then pass this reducer to the store
React-Redux doesn't update UI when store changes.
I expect {this.props.isLogged} to get changed dynamically when Change button is clicked.
I searched tons of materials but I cannot find why the text doesn't change when button is clicked.
Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { createStore } from "redux";
import reducers from "./reducers";
import { Provider } from "react-redux";
const store = createStore(
reducers);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js
import React from "react";
import { connect } from "react-redux";
import { loginUser } from "./actions";
class App extends React.Component {
changeText = () => {
this.props.loginUser();
};
render() {
return (
<div>
<span>{this.props.isLogged}</span>
<button onClick={this.changeText}>Change</button>
</div>
);
}
}
const mapStateToProps = (state /*, ownProps*/) => {
return {
isLogged: state.isLogged
};
};
const mapDispatchToProps = { loginUser };
export default connect(mapStateToProps, mapDispatchToProps)(App);
./src/reducers/index.js
import { combineReducers } from "redux";
import { LOGIN_USER } from "../actions";
function userReducer(state = { isLogged: false }, action) {
switch (action.type) {
case LOGIN_USER:
return { isLogged: !state.isLogged };
default:
return state;
}
}
const reducers = combineReducers({
userReducer
});
export default reducers;
./src/actions/index.js
export const LOGIN_USER = "LOGIN_USER";
export function loginUser(text) {
return { type: LOGIN_USER };
}
Check this out..
https://codesandbox.io/s/react-redux-doesnt-update-ui-vj3eh
const reducers = combineReducers({
userReducer //It is actually userReducer: userReducer
});
As you assiging your UserReducer to userReducer prop, you will have to fetch the same way in mapStateToProps
const mapStateToProps = (state /*, ownProps*/) => {
return {
isLogged: state.userReducer.isLogged
};
};
Also, isLogged prop is a Boolean variable.. So you are gonna have to use toString().
<span>{this.props.isLogged.toString()}</span>
Since you are using combineReducers, the isLogged boolean lives in state.userReducer.isLogged.
Consider changing combineReducers to combineReducers({ user: userReducer }) and accessing the flag with state.user.isLogged.
I am trying to return a token without an API but it keeps returning undefined. The purpose at the moment is just to optionally show two elements dependent on whether or not there is a token in the props, preparing it for later when I actually implement the API.
I have tried different saga effects and amended my code more times than I can count. There does not seem to be a lot of information regarding setting initial state so I was wondering if that may be the issue and I am trying to select state that does not exist? Though I believe this should be handled by the reducer?
My code is as follows:
redux/store.js:
import { connectRouter, routerMiddleware } from 'connected-react-router';
import { createBrowserHistory } from 'history';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
import reducers from './reducers';
import rootSaga from './sagas';
const history = createBrowserHistory();
const routeMiddleware = routerMiddleware(history);
const sagaMiddleware = createSagaMiddleware();
const middlewares = [thunk, sagaMiddleware, routeMiddleware];
const composeEnhancers =
typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
})
: compose;
const store = createStore(
combineReducers({
...reducers,
router: connectRouter(history),
}),
composeEnhancers(applyMiddleware(...middlewares))
);
sagaMiddleware.run(rootSaga);
export { store, history };
redux/reducers.js:
import Auth from './auth/reducer';
export default {
Auth
};
redux/saga.js:
import { all } from 'redux-saga/effects';
import authSaga from './auth/saga';
export default function* rootSaga(getState) {
yield all([
authSaga()
]);
};
redux/auth/actions.js:
export const checkAuthAction = (value) => ({
type: 'CHECK_AUTH',
value
});
redux/auth/reducer.js:
import { checkAuthAction } from "./actions";
const initialState = {
token: true
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'CHECK_AUTH':
return {
...state,
token: action.token
};
default:
return state;
};
};
export default reducer;
redux/auth/saga.js
import { select, take } from 'redux-saga/effects';
import { checkAuthAction } from './actions';
// Selectors
const getToken = (state) => state.token;
function* authSaga() {
const token = yield select(getToken);
console.log(token);
};
export default authSaga;
Edit: forgot to include the component itself.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
class Nav extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
return (
<nav className="nav">
{this.props.token ? (
<Col type="flex" align="center" className="nav__list__item" md={6}>
<Link to="/logout"><IntlMessages id="nav.logout.link" /></Link>
</Col>
) : (
<Col type="flex" align="center" className="nav__list__item" md={6}>
<Link to="/login"><IntlMessages id="nav.login.link" /></Link>
</Col>
)}
</nav>
);
};
}
const mapStateToProps = state => ({
token: state.token
});
const mapDispatchToProps = {
CHECK_AUTH: 'CHECK_AUTH'
};
export default connect(mapStateToProps, mapDispatchToProps)(Nav);
In order to get the token in the component you should change the mapStateToProps function:
const mapStateToProps = state => ({
token: state.Auth.token
});
In order to get it in the saga you should change the getToken selector
const getToken = (state) => state.Auth.token;
When authentication is done I can see the actions updating and the reducer updating - but then mapstatetoprops does not do anything with the new reducer state
The Store keeps logging the same state(initial state) on every update
import React from 'react';
import { bindActionCreators } from 'redux'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import { Redirect } from 'react-router-dom'
import firebase from 'firebase';
import { connect } from 'react-redux';
import { signIn, signOut } from '../reducer/actions'
import { auth } from '../firebase'
class LoginPage extends React.PureComponent {
// Configure FirebaseUI.
uiConfig = {'FirebaseUI Config'}
componentDidMount = () => {
auth.onAuthStateChanged((user) => { // gets user object on authentication
console.log('OnAuthStateChanged', user)
console.log('Check If Props Change in AuthChange', this.props.isauthed)
if (user) {
this.props.signIn(user)
} else {
this.props.signOut(user)
}
});
}
render() {
console.log('Check If Props Change in Render', this.props.isauthed)
if (!this.props.isauthed) {
return (
<div>
<h1>My App</h1>
<p>Please sign-in:</p>
<StyledFirebaseAuth uiConfig={this.uiConfig} firebaseAuth={firebase.auth()} />
</div>
);
}
return (
<Redirect to='/' />
)
}
}
export default (LoginPage);
JS that should dispatch and update the props
import { connect } from 'react-redux';
import { signIn, signOut } from '../reducer/actions'
import { bindActionCreators } from 'redux'
import LoginPage from './LoginPage';
const mapStateToProps = (state) => {
console.log('LOGINmapstatetoprops', state.Authed)
return {
isauthed: state.Authed.isauthed,
}
}
const mapDispatchToProps = (dispatch) => {
console.log('LOGINmapDISPATCHoprops')
return bindActionCreators({signIn, signOut}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage);
The reducer
import LoginPage from '../components/LoginPage';
import firebase from 'firebase';
const initialState = {
isauthed: false,
error: ''
}
const AuthReducer = (state = initialState, action) => {
console.log('this is an action', action)
switch (action.type) {
case 'IsSignedIn':
return {
...state,
isauthed: action.payload
}
break;
case 'IsNotSignedIn':
return {
...state,
isauthed: action.payload
}
break;
default: return state
}
}
export default AuthReducer;
This is the actions file
export const signIn = (user) => {
console.log('this is from actions', user)
return {
type: 'isSignedIn',
payload: true
}
}
export const signOut = (user) => {
console.log(user)
return {
type: 'isNotSignedIn',
payload: false
}
}
Redux Store
import { createStore } from 'redux';
import logger from 'redux-logger';
import { createEpicMiddleware } from 'redux-observable';
import {AllReducers} from './reducer/index';
//import rootEpic from './modules/rootEpic';
//const epicMiddleware = createEpicMiddleware(rootEpic);
const store = createStore(AllReducers);
store.subscribe(() => {
console.log('storeupdated', store.getState())
});
export default store;
CombineReducers Function
import {combineReducers} from 'redux'
import {AuthReducer} from './AuthReducer'
import {UserReducer} from './UserReducer'
export const AllReducers = combineReducers({
Authed: AuthReducer
User: UserReducer
})
Seems like you have a typo error between 'isSignedIn' and 'IsSignedIn' (first letter differ)
You could avoid that by using a variable declared in a separated file. I like to call it types.js for the types of actions:
// types.js
export const IS_SIGNED_IN = 'isSignedInOrEvenWhateverButYouBetterKeepAClearName'
// (same with all your types)
And then you can import this variable in your other files. You won't have typos anymore!
P.S: you can find all these ideas in redux documentation