I can't change the value of the state of the store using the reducer. I'm making an app which has a login-functionality. When a person opens up the app, depending on the fact if he is logged in or not, it should show the right screen. The problem I'm having right now is it doesn't seem to be able to change the store state out of another screen. Anybody who can help me?
import {createStore} from "redux";
const initialState = {
value: false
}
function reducer(state= initialState, action) {
const newState = {...state};
if(action.type === 'login') {
console.log("hahaha you logged in");
newState.value = true;
}
else if(action.type ==='logout') {
console.log("hahaha you logged out")
newState.value = false;
}
return newState;
}
const store = createStore(reducer);
export default store;
This is the store, this should change the value accordingly.
When the login button is pressed on loginscreen it should call the reducer function.
import React, { useRef, useState } from 'react';
import { StyleSheet, Text, View, TextInput, TouchableOpacity, Image, Dimensions, AsyncStorage } from 'react-native';
import axios from "axios";
import store from "../routes/store"
function LoginScreen({navigation}, props) {
const win = Dimensions.get('window');
const [email,setEmail] = useState('');
const [password, setPassword] = useState('');
const { auth, setAuth } = useAuth();
const [errMsg, setErrMsg] = useState('');
const logInCheck = async (e) => {
console.log("Ingelogd");
store.dispatch({type: 'login'})
}
return(
<Root>
<View style={styles.container}>
<TouchableOpacity style={styles.loginBtn} onPress{logInCheck}>
<Text style={styles.loginText}>LOGIN</Text>
</TouchableOpacity>
</View>
</Root>
)
}
This is the code which should render the right screen depending on the fact if the person is logged in!
import React, { useState, useReducer } from "react";
import { createStore } from 'redux';
import { View,Text } from "react-native";
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import LoginScreen from "../screens/LoginScreen";
import * as SecureStore from 'expo-secure-store';
import axios from "axios";
import Tabs from "./Tabs";
import store from "./store";
import ForgotPasswordScreen from "../screens/ForgotPassword";
const AuthStack = () => {
function rendFunc() {
console.log(store.getState());
return(
<AuthStack.Navigator>
{!store.getState()? (
<AuthStack.Screen name="Tabs" component={Tabs} options={{headerShown : false}}/>
) : (
<>
<AuthStack.Screen
name = "LoginScreen"
component={LoginScreen}
/>
<AuthStack.Screen
name = "ForgotPassword"
component={ForgotPasswordScreen},
/>
</>
)
}
</AuthStack.Navigator>
);
}
return (
rendFunc()
);
store.subscribe(rendFunc);
};
export default AuthStack;
The problem with your code is in react re-render rather than the redux store not updating. The store is updating properly but your react component is not aware of any change that has occurred in the store so no re-render is happening.
Firstly you need to add subscriptions to the redux store listener in useEffect i.e. when the component is mounted and later unsubscribe to prevent memory leakages. The redux subscribe function takes in a function to handle whenever state change has occurred in the redux store.
In this function, you can create a state using useState to create a re-render of the component.
use the below code in the authstack and it should work fine.
if any more queries you can contact me # wizcoderzcorp#gmail.com
import React, { useState, useReducer, useEffect } from "react";
import { createStore } from 'redux';
import { View,Text } from "react-native";
import { createNativeStackNavigator } from '#react-navigation/native-
stack';
import LoginScreen from "../screens/LoginScreen";
import * as SecureStore from 'expo-secure-store';
import axios from "axios";
import Tabs from "./Tabs";
import store from "./store";
import ForgotPasswordScreen from "../screens/ForgotPassword";
const AuthStack = () => {
const [loginState, setLoginState] = useState(false)
const handleReduxStateChange = () =>{
setLoginState(store.getState().value)
}
useEffect(()=>{
const unsubscribe = store.subscribe(handleReduxStateChange);
return unsubscribe()
},[])
function rendFunc() {
console.log(store.getState());
return(
<AuthStack.Navigator>
{!store.getState().value? (
<AuthStack.Screen name="Tabs" component={Tabs} options=
{{headerShown : false}}/>
) : (
<>
<AuthStack.Screen
name = "LoginScreen"
component={LoginScreen}
/>
<AuthStack.Screen
name = "ForgotPassword"
component={ForgotPasswordScreen},
/>
</>
)
}
</AuthStack.Navigator>
);
}
return (
rendFunc()
);
};
export default AuthStack;
Related
I am setting up firebase user authentication for a reactjs app. Everything's been working so far but when I write const {signup} = useAuth() in the file that actually renders the login page, the page turns blank.
There is a context file set up with:
import React, { useContext, useState, useEffect} from 'react'
import {auth} from '../firebase'
const AuthContext = React.createContext()
function useAuth() {
return useContext(AuthContext)
}
function AuthProvider({children}) {
const[currentUser, setCurrentUser]= useState()
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email,password)
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
})
return unsubscribe
},[]);
const value = {
currentUser,
signup
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
export {useAuth, AuthProvider}
With my RenderPage everything is working correctly, it's just a basic JSX/HTML return function with
import {useAuth, AuthProvider} from '../Contexts/AuthContext'
import React, { useState, useRef } from "react";
function RenderPage() {
return(
<AuthProvider>
<h1>Generic text</h1>
<AuthProvider>)}
export default RenderPage;
And everything works perfectly,
But then when I put in the line const {signup} = useAuth() to make it:
import {useAuth, AuthProvider} from '../Contexts/AuthContext'
import React, { useState, useRef } from "react";
function RenderPage() {
const {signup} = useAuth()
return(
<AuthProvider>
<h1>Generic text</h1>
<AuthProvider>
);
The page turns blank.
Does anyone have any idea why this might be? (I already checked the firebase API key and set it up on the realtime database)
Thanks in advance
I am trying to change the value of a state defined in the store using redux. I defined Slice, store and component according to react redux doc, however, when I trigger the event I obtain the error:
"object(...) is not a function"
This is the slice definition
import { createSlice } from "#reduxjs/toolkit";
export const sliceCambiadorDeVista = createSlice({
name:"cambiadorDeVista",
initialState:{
value:30,
},
reducers:{
cambiarVista:(state, action)=>{
state.value = action.payload
}
}
})
export const cambiarVista = sliceCambiadorDeVista.actions;
export const vistaActual = (state) => state.cambiadorDeVista.value;
export default sliceCambiadorDeVista.reducer;
This is the store definition
import {configureStore} from "#reduxjs/toolkit"
import cambiadorDeVistaReducer from "../redux/actions/SwitchSections/switchSections"
export default configureStore({
reducer:{
cambiadorDeVista:cambiadorDeVistaReducer,
}
})
and this is a part of the component definition.
import * as React from 'react';
import Box from '#mui/material/Box';
import { ThemeProvider } from '#emotion/react';
import theme from "../themeConfig"
import { ListItem, ListItemText, ListSubheader } from '#mui/material';
import AddCircleIcon from "#mui/icons-material/AddCircle"
import WbSunnyIcon from '#mui/icons-material/WbSunny';
import OtherHousesIcon from '#mui/icons-material/OtherHouses';
import ElectricalServicesIcon from '#mui/icons-material/ElectricalServices';
import { ListItemIcon , List} from '#mui/material';
import { makeStyles } from '#mui/styles';
import { useDispatch, useSelector } from 'react-redux';
import { cambiarVista, vistaActual } from '../redux/actions/SwitchSections/switchSections';
const LeftAside = () => {
const dispatch = useDispatch()
const vistaActualState = useSelector(vistaActual)
const changeVistaHandler = ()=>{
dispatch(()=>cambiarVista(3))
}
return (
<ThemeProvider theme = {theme}>
<Box sx={{display:{xs:"none",md:"flex"}}}>
<List
sx={{display:{xs:"none",md:"flex"}, width:"100%", bgcolor:"secondary",flexDirection:"column", borderRight:1}}
subheader ={<ListSubheader>Etapas {vistaActualState}</ListSubheader>}
>
<ListItem>
<ListItemIcon>
<AddCircleIcon />
</ListItemIcon>
<ListItemText id="section-basic-data-label"
primary = "Datos Basicos"
disableTypography ="true"
className={classes.textAsideItems}
onClick={changeVistaHandler}
/>
</ListItem>
Error is generated inside of event handler changeVistaHandler. Problem seens to be related to reducer parameter.
I am trying to use AppLoading on Expo to preload data from firebase, before the app goes to the homepage. I keep receiving an error.
"Error: could not find react-redux context value; please ensure the component is wrapped in a
< Provider > "
import React, { useState } from "react";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import ReduxThunk from "redux-thunk";
import productsReducer from "./store/productReducer";
import createdProducts from "./store/createdProductReducer";
import storeName from "./store/StoreNameReducer";
import authReducer from "./store/authReducer";
import { useDispatch } from "react-redux";
import * as ProdActions from "./store/productActions";
import AppLoading from "expo-app-loading";
import InventoryNavigator from "./navigation/InventoryNavigator";
const rootReducer = combineReducers({
products: productsReducer,
availableProducts: createdProducts,
auth: authReducer,
storeName: storeName,
});
const store = createStore(rootReducer, applyMiddleware(ReduxThunk));
export default function App() {
const [fireBLoaded, setFireBLoaded] = useState(false);
const dispatch = useDispatch();
const fetchFirebase = () => {
dispatch(ProdActions.fetchAvailableProducts());
dispatch(ProdActions.fetchStoreName());
dispatch(ProdActions.fetchProducts());
};
if (!fireBLoaded) {
return (
<AppLoading
startAsync={fetchFirebase}
onFinish={() => setFireBLoaded(true)}
onError={console.warn}
/>
);
} else {
return (
<Provider store={store}>
<InventoryNavigator />
</Provider>
);
}
}
what I have tried:
const fetchFirebase = async () => {
any help would be greatly appreciated, I am still new to React Native.
The error tells that there is no Redux.Provider when fetching from Firebase.
To fix it, you should also wrap you <AppLoading ... /> into that <Provider store={store}> ....
It should look like following:
<Provider store={store}>
<AppLoading ... />
<Provider/>
Your fetchFirebase function should be async
Like this -
const fetchFirebase = async () => {
// Perform Aysnc operations here...
dispatch(ProdActions.fetchAvailableProducts());
dispatch(ProdActions.fetchStoreName());
dispatch(ProdActions.fetchProducts());
};
I don't see any other errors here Other than this one
I've made two different windows with react and provide same store. But if i change store data in one window, second window doesn't changed. And idk know how to synchronize.(All Reducers and Actions made as on default React project)
First provide(index.js):
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {Provider} from 'react-redux';
import store from "../redux/store";
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById("root"))
First App.js:
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {addNumberAction} from "../redux/addNumber";
export default function App() {
const {number} = useSelector(state=>state.testPage)
const dispatch = useDispatch();
let changeNumber = number
return (
<>
<h1>First</h1>
<button onClick={()=>dispatch(addNumberAction(++changeNumber))}>{number}</button>
</>
)
}
Second provide(index.js):
import React from 'react';
import ReactDOM from 'react-dom'
import App from "./App";
import {Provider} from "react-redux";
import store from "../redux/store";
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById("root2")
)
Second App.js:
import React, {useEffect} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {addNumber, addNumberAction} from "../redux/addNumber";
export default function App() {
const {number} = useSelector(state=>state.testPage)
const dispatch = useDispatch();
let changeNumber = number
return (
<>
<h1>Second</h1>
<button onClick={()=>dispatch(addNumberAction(++changeNumber))}>{number}</button>
</>
)
}
They are not the same store. Each time you use <Provider store={store}> it basically create a store for any components inside it. Calling two <Provider store={store}> will create 2 independent store.
I tried to configure redux state to share state between browser’s windows. At the end, I found info, how it can be with hook.
For example,
import React, { useState, useEffect } from "react";
function HelloStorage() {
const [name, setName] = useState("");
useEffect(() => {
localStorage.setItem("name", name);
}, [name]);
useEffect(() => {
const onReceiveMessage = (e) => {
const { key, newValue } = e;
if (key === "name") {
setName(newValue);
}
};
window.addEventListener("storage", onReceiveMessage);
return () => {
window.removeEventListener("storage", onReceiveMessage);
};
}, []);
const handleChange = (e) => {
setName(e.target.value);
};
return <input value={name} onChange={handleChange} />;
}
For this link you can find information, how create a custom React Hook to share state between browser tabs without Redux state.
You can use redux-state-sync for that (https://www.npmjs.com/package/redux-state-sync).
Lets say you create the store currently like this:
import { configureStore } from '#reduxjs/toolkit';
export const store = configureStore({
reducer
});
All you need is adding the middleware to the store:
import { configureStore } from '#reduxjs/toolkit';
import { createStateSyncMiddleware, initMessageListener } from 'redux-state-sync'
export const store = configureStore({
reducer,
middleware: [createStateSyncMiddleware()],
});
initMessageListener(store);
Starting with GamePage, it provides 2 routes which renders the components GameList and GameDetailPage. Both work fine at first but When i refresh the page for Gamelist component, it still rerenders the page but when i refresh the page for GameDetailPage, i get the error TypeError: Cannot read property 'Location' of undefined. I do not understand why it is unable to fetch data from state whenever i refresh.
gamepage.jsx
import React from "react";
import GamesList from "../../components/games-list/game-list.component";
import { Route } from "react-router-dom";
import GameDetailPage from "../gamedetailpage/gamedetailpage.component";
import {firestore,convertCollectionsSnapshotToMap} from '../../firebase/firebase.utils'
import {connect} from 'react-redux'
import {updateFootballGames} from '../../redux/games/games.actions'
class GamePage extends React.Component {
unsubscribeFromSnapshot=null;
//whenever the component mounts the state will be updated with the football games.
componentDidMount(){
const {updateFootballGames}=this.props
const gameRef=firestore.collection('footballgames')
gameRef.onSnapshot(async snapshot=>{
const collectionsMap=convertCollectionsSnapshotToMap(snapshot)
updateFootballGames(collectionsMap)
})
}
render() {
const { match } = this.props;
return (
<div className="game-page">
<h1>games page</h1>
<Route exact path={`${match.path}`} component={GamesList} />
<Route path={`${match.path}/:linkUrl`} component={GameDetailPage}
/>
</div>
);
}
}
const mapStateToProps=state=>({
games:state.games.games
})
const mapDispatchToProps=dispatch=>({
updateFootballGames:collectionsMap=>
dispatch(updateFootballGames(collectionsMap))
})
export default connect(mapStateToProps, mapDispatchToProps)(GamePage);
gamedetailpage.component.jsx
import React from "react";
import { connect } from "react-redux";
import GamePreview from '../../components/game-preview/game-preview.component'
import GameDetails from '../../components/game-details/game-details.component'
const GameDetailPage = (props) => {
const {games, match} = props
const urlparam =match.params.linkUrl
// const games_array = Object.entries(games)
const gameObj=games[urlparam]
console.log('prop',gameObj)
return (
<div className="game-list">
<GameDetails game = {gameObj}/>
</div>
);
};
const mapStateToProps = (state) => ({
games: state.games.games,
});
export default connect(mapStateToProps)(GameDetailPage);
game_details.component.jsx
import React from 'react';
const GameDetails = (props) => {
console.log(props.game.Location)
return(
<div>
Location:{props.game.Location}
<br/>
Price:{props.game.Price}
</div>
)
}
export default GameDetails;
gamelist.component.jsx
import React from "react";
import './game-list.styles.scss'
import GamePreview from "../game-preview/game-preview.component";
import {connect} from 'react-redux'
const GameList=(props)=>{
const {games}=props
console.log(games)
const game_list=Object.entries(games)
console.log(game_list)
return (
<div className="game-list">
{game_list.map(game =>
<GamePreview game = {game[1]}/>)}
</div>
);
}
const mapStateToProps=state=>({
games:state.games.games
})
export default connect(mapStateToProps)(GameList);
gamepreview.component.jsx
import React from "react";
import "./game-preview.styles.scss";
import { withRouter, Route } from "react-router-dom";
import GamePreviewDetail from "../game-preview-detail/game-preview-detail.component";
const GamePreview = (props) => {
const { Location, Time, linkUrl, Price } = props.game;
const { history, match } = props;
return (
<div
className="game-preview"
onClick={() => history.push(`${match.url}/${linkUrl}`)}
>
<div className="game-preview-image">
<p>Picture goes here</p>
</div>
{/* <GamePreviewDetail name = {Location} price={Price}/> */}
<p>Location:{Location}</p>
<p>Price:{Price}</p>
</div>
);
};
export default withRouter(GamePreview);
app.js
import React from 'react';
import './App.css';
//import dependencies
import { Route, Switch } from "react-router-dom";
//import pages
import HomePage from './pages/homepage/homepage'
import GamesPage from './pages/gamespage/gamespage'
import SignInSignUp from './pages/signin-signup-page/signin-signup-page'
import GameDetailPage from "./pages/gamedetailpage/gamedetailpage.component";
import Header from './components/header/header.component';
import { auth, createUserProfileDocument } from './firebase/firebase.utils';
class App extends React.Component{
constructor() {
super();
this.state = {
currentUser: null
}
}
unsubscribeFromAuth = null
componentDidMount() {
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
// check if the snapshot has changed (subscribe)
// get the user that we just created or that already exists in the db
userRef.onSnapshot(snapshot => {
this.setState({
currentUser: {
id: snapshot.id,
...snapshot.data()}
})
})
} else {
this.setState({currentUser: userAuth})
}
})
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render(){
return(
<div>
<Header currentUser = {this.state.currentUser}/>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/games" component={GamesPage} />
<Route exact path="/signin" component={SignInSignUp} />
</Switch>
</div>
)
}
}
export default App;
I would try using useParams hook instead. Then capturing any changes of linkUrl with useEffect hook. Also introducing gameObj with useState.
useParams returns an object of key/value pairs of URL parameters. Use it to access match.params of the current <Route>.
If you're familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
Try to modify <GameDetailPage /> component as the following:
import React, { useState, useEffect } from 'react';
import { useParams } from "react-router-dom";
// other imports
const GameDetailPage = (props) => {
const { games } = props;
let { linkUrl } = useParams();
const [ gameObj, setGameObj ] = useState(null);
useEffect(() => {
if (games) {
const newGameObj = games[linkUrl];
console.log('game object', newGameObj);
setGameObj(newGameObj);
}
}, [games, linkUrl]);
return <div className="game-list">
{ gameObj && <GameDetails game={ gameObj } /> }
</div>
}
+1 - null check:
Also you can see a null check in the return statement for gameObj which helps rendering only that case once you have a value in games array with found linkUrl value.
I hope this helps!