In my react app, I have a withAuth HOC which checks if the user is authenticated before the wrapped component is loaded. It looks up the redux store to check auth status and loads the component, if isAuth is true. However, I can't access redux store from the HOC. when I try, I get the following error message on the browser. Any help to overcome this problem is highly appreciated.
withAuth.js
import React, { useState, useEffect } from 'react';
import { setAuth } from '../actions/Actions';
import { connect } from 'react-redux';
function withAuth(WrappedComponent) {
return (props) => {
const [isAuth, setAuth] = useState(false);
useEffect(() => {
if (props.isAuth) {
setAuth(true);
} else {
props.history.push('/catalog');
}
}, [props.history]);
return isAuth ? <WrappedComponent {...props} /> : <p>Loading..</p>
};
}
const mapStateToProps = (state) => {
return {
isAuth: state.isAuth,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setAuth: (status) => dispatch(setAuth(status)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(withAuth);
You cannot pass a HOC to connect, you have to pass a function component:
export default function withAuth(WrappedComponent) {
const Component = (props) => {
const [isAuth, setAuth] = useState(false);
useEffect(() => {
if (props.isAuth) {
setAuth(true);
} else {
props.history.push('/catalog');
}
}, [props.history, props.isAuth]);//forgot dependency here
return isAuth ? (
<WrappedComponent {...props} />
) : (
<p>Loading..</p>
);
};
const mapStateToProps = (state) => {
return {
isAuth: state.isAuth,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setAuth: (status) => dispatch(setAuth(status)),
};
};
return connect(
mapStateToProps,
mapDispatchToProps
)(Component);
}
Related
Action
export const VERIFY = () => dispatch => {
dispatch({type: "VERIFY"})
};
Reducer
const signedReducer = (state=user, action) => {
console.log(action);
switch(action.type){
case "VERIFY": {
return {...state, email: "example#gmail.com"};
}
default: {
return state;
}
}
}
_app.js code
import { wrapper } from '../redux/store';
function MyApp({ Component, pageProps }) {
return <>
<Component {...pageProps}/>
</>
}
MyApp.getInitialProps = async(appContext) => {
let { pageProps } = appContext
pageProps = {};
if(appContext.Component.getInitialProps){
pageProps = await appContext.Component.getInitialProps(appContext.ctx);
}
return {
pageProps,
};
};
export default wrapper.withRedux(MyApp);
& finally pages/home.js
import { useEffect } from "react";
import PrivateLayout from "../components/PrivateLayout/PrivateLayout";
import { connect } from "react-redux";
import { VERIFY } from "../redux/actions/signActions";
function Home() {
// console.log(user);
// useEffect(() => {
// }, [user]);
return (
<div >
{ true ?
<h1>Logged In</h1>
:
<h1>Please login again</h1>
}
</div>
)
}
const mapStateToProps = state => ({
user: state
})
const mapDispatchToProps = {
VERIFY: VERIFY
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Please check, I've put an console.log statement in the reducer.
Whenever I run the code the console.log statement display these action type only
##redux/INIT6.z.d.a.h.7
##redux/PROBE_UNKNOWN_ACTIONq.x.h.3.5.d
But never takes the action VERIFY.
Looked across the internet but haven't found any solution regarding this. Why?
This should solve your problem:
Also you need to call VERIFY in your component.
Action:
export const VERIFY = () => ({type: "VERIFY"});
reference: https://react-redux.js.org/using-react-redux/connect-mapdispatch
First of all I rarely use functional component, but this time I required to use it. So, I have this component called Login that use redux :
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getLoginData } from "../../redux/actions/LoginActions";
function Login() {
useEffect(() => {
const { getLoginData } = this.props;
getLoginData("test");
}, []);
return (
<div>
<h1>Login</h1>
</div>
);
}
const mapStateToProps = (state) => ({
login: state.login,
});
const mapDispatchToProps = (dispatch) => ({
getLoginData: (value) => dispatch(getLoginData(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
It produce error since this is undefined. But, if I change it to class component like this:
import React from "react";
import { connect } from "react-redux";
import { getLoginData } from "../../redux/actions/LoginActions";
class Login extends React.Component {
componentDidMount() {
const { getLoginData } = this.props;
getLoginData("test");
}
render() {
return (
<div>
<h1>Login</h1>
</div>
);
}
}
const mapStateToProps = (state) => ({
login: state.login,
});
const mapDispatchToProps = (dispatch) => ({
getLoginData: (value) => dispatch(getLoginData(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
It will worked as expected(the redux is also worked). The question is, How can I pass this.props to functional component?
Function components get their props passed in as the argument to that function:
function Login(props) {
useEffect(() => {
props.getLoginData("test");
}, []);
// ...
}
// Or with destructuring:
function Login({ login, getLoginData }) {
useEffect(() => {
getLoginData("test");
}, []);
// ...
}
That said, if you're using a function component, then it's simpler to use hooks instead of connect:
function Login() {
const login = useSelector(state => state.login);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getLoginData("test"));
}, []);
//...
}
// Note that there is no mapStateToProps/mapDispatchToProps/connect here
export default Login;
As per the React docs, you pass a props object to the function and access the values as attributes of props.
So, for your implementation, you'd do it like this:
function Login(props) {
useEffect(() => {
props.getLoginData("test");
}, []);
return (
<div>
<h1>Login</h1>
</div>
);
}
Or, you could replace function Login(props) with function Login({getLoginData}) to unwrap the value and replace props.getLoginData("test") with getLoginData("test").
I'm attempting to import a function defined in a 'context' file so I can access the user data across the whole app easily.
When importing it, and calling it in my 'useEffect' however, it's claiming it isn't a function - when it clearly is.
For reference, here is the component which I'm calling the context function from:
import { Context } from '../context/authContext';
const LoadingScreen = ({ navigation }) => {
const { checkSignedIn } = useContext(Context)
useEffect(() => {
checkSignedIn()
}, [])
return (
<View style={styles.mainView}>
<ActivityIndicator style={styles.indicator} />
</View>
)
}
Here is the context file itself (in full):
import createDataContext from './createDataContext';
import { navigate } from '../navigationRef';
import { Magic } from '#magic-sdk/react-native';
const m = new Magic('API key');
const authReducer = (state, reducer) => {
switch (action.type) {
case 'checkComplete':
return { ...state, userDetails: action.payload };
default:
return state;
}
};
const checkSignedIn = dispatch => {
return async () => {
// Set variable which calls Magic to check if signed in
const checkMagic = await m.user.isLoggedIn();
// If user signed in, redirect to dashboard and save details in state
if (checkMagic === true) {
const { issuer, email } = await m.user.getMetaData();
dispatch({
type: 'checkComplete',
payload: [issuer, email]
});
navigate('authorisedFlow');
// If user not signed in, redirect to welcome page and set state as null
}
else {
navigate('loginFlow');
}
};
};
export const { Context, Provider } = createDataContext( authReducer, { checkSignedIn }, { userDetails: null } );
Here is the 'helper' function called 'createDataContext' which iterates over the dispatch and defines it without having to repeat create context code:
import React, { useReducer } from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key].dispatch;
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
)
};
return { Context, Provider }
};
I don't think the navigate helper is necessary to show here, but happy to put in if needed. It seems to be more of a problem with the function itself!
If anybody can point out what I'm doing wrong here it'd be much appreciated!
Please let me know if any of that is unclear.
I followed a guide that showed how I could access and manage global state with just a few lines in a file called state.js:
import React, {createContext, useContext, useReducer} from 'react';
export const StateContext = createContext();
export const StateProvider = ({reducer, initialState, children}) =>(
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
This is App.js
import React, {useEffect} from 'react'
import { StateProvider } from './state';
import ToggleButton from './ToggleButton';
import axios from 'axios'
const App = () => {
const initialState = {
loading: true,
navbarOpen: false,
pages: [],
album: []
};
useEffect(() => {
const requestOne = axios.get('http://example.com/pages');
const requestTwo = axios.get('http://example.com/album');
axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => {
const responseOne = responses[0];
const responseTwo = responses[1];
// What do i do here, how do i set this data?
})).catch(err => console.log(err))
}, []);
const reducer = (state, action) => {
switch (action.type) {
case 'toggleNavbar':
return {
...state,
navbarOpen: action.toggle
};
default:
return state;
}
};
return (
<StateProvider initialState={initialState} reducer={reducer}>
<ToggleButton />
</StateProvider>
);
}
export default App;
And this is my button that dispatches the toggle event: ToggleButton.js
import React from 'react'
import { useStateValue } from './state';
const ToggleButton = () => {
const [{ navbarOpen }, dispatch] = useStateValue();
console.log(navbarOpen)
return (
<button
onClick={() => dispatch({
type: 'toggleNavbar',
toggle: !navbarOpen
})}
>
Toggle
</button>
);
}
export default ToggleButton;
The toggle is working and I really like this because I can manipulate state without having to pass down functions as props all the way where I want it.
The problem is, since the "initialState" is an object that is passed down to the StateProvider, how do I save the axios response to the initial state, right now I feel confused because I have either setState() or any hook to call.
Any solutions?
First of all, you must make use of useReducer within the functional component directly instead of calling it in provider value while rendering
const initialState = {
loading: true,
navbarOpen: false,
pages: [],
album: []
};
const reducer = (state, action) => {
switch (action.type) {
case 'toggleNavbar':
return {
...state,
navbarOpen: action.toggle
};
case 'updatePagesAndAlbums': {
...state,
...actions.updatedState
}
default:
return state;
}
};
export const StateProvider = ({children}) =>{
const state = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
{children}
</StateContext.Provider>
);
}
export const useStateValue = () => useContext(StateContext);
Also StateProvider must wrap the App Component and the reducer must have a handler condition for updating state
const App = () => {
const [state, dispatch] = useStateValue();
useEffect(() => {
const requestOne = axios.get('http://example.com/pages');
const requestTwo = axios.get('http://example.com/album');
const
axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => {
const responseOne = responses[0];
const responseTwo = responses[1];
dispatch({type: 'updatePagesAndAlbums', updatedState: { pages: responseOne, albums: responseTwo }});
})).catch(err => console.log(err))
}, []);
return (
<ToggleButton />
);
}
export default () => <StateProvider><App /><StateProvider>;
I'm a beginner of react & react-native.
I'm using react 16, react-thunk, react-redux.
I'm trying to fetch categories that I already made from firestore.
At first, I called action using connect(), and then, I typed action using thunk also fetched data from firestore.
Finally, I returned new states in reducer.
Definitely, I'm not aware of redux process, so please give some tips.
Here's my code. Thank you.
CategoryImageList.js (Component)
...
class CategoryImageList extends Component {
componentWillMount() {
this.props.getCategory();
}
renderImages() {
return this.state.categories.map(category =>
<CategoryImageCard key={category.imgName} category={category}/>
);
}
render() {
return (
<ScrollView>
{/*{this.renderImages()}*/}
</ScrollView>
);
}
}
export default connect(null, {getCategory})(CategoryImageList);
category.js (action)
...
export const getCategory = () => {
return (dispatch) => { //using redux-thunk here... do check it out
getCategories()
.then(querySnapshot => {
const test = [];
querySnapshot.forEach((doc) => {
test.push(
{
imgName : doc.data()['imgName'],
name : doc.data()['name']
});
});
dispatch({ type: GET_CATEGORY, payload: test} );
});
};
};
CategoryReducers.js (reducer)
...
const categoryInitialState = {
name: [],
imgName: []
}
export const CategoryReducer = (state = categoryInitialState, action) => {
switch (action.type) {
case GET_CATEGORY:
console.log(action);
return { ...state, categoryImg: {
name: action.payload.name,
imgName: action.payload.imgName
}};
default:
return state;
}
}
App.js
...
type Props = {};
export default class App extends Component<Props> {
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<View style={{flex:1}}>
<Header headerText={'FoodUp'}/>
<CategoryImageList />
</View>
</Provider>
);
}
}
reducers/index.js
import { combineReducers } from 'redux';
import { CategoryReducer } from './CategoryReducer';
export default combineReducers({
categories: CategoryReducer
});
UPDATED
Firebase.js
const config = {
...
};
firebase.initializeApp(config);
const db = firebase.firestore();
const storage = firebase.storage();
const settings = {timestampsInSnapshots: true};
db.settings(settings);
export const getCategories = () => {
return db.collection('categories').get();
}
export const getCategoryImg = (categoryName, imgName) => {
const ref = storage.ref(`category/${categoryName}/${imgName}`);
return ref.getDownloadURL();
}
You have to add mapstateToProps to your connect like,
const mapStateToProps = (state: any) => {
return {
name: state.categories.name,
imageName:state.categories.imageName
};
}
export default connect(mapStateToProps)(CategoryImageList)
And then, you will be able to access the name and image name like,
this.props.name and this.props.imageName
Edit: To dispatch GET_CATEGORY you can either use mapDispatchToProps or do the getCategory and dispatch from within your component like,
import {getCategory} from './category'
componentWillMount() {
this.props.getCategory(this.props.dispatch);
}
and change the getCategory function as,
export const getCategory = (dispatch) => {
...
dispatch({ type: GET_CATEGORY, payload: test} );
...
}
mapStateToProps has the Store state as an argument/param (provided by react-redux::connect) and its used to link the component with the certain part of the store state. in your case, you can use like this. and you can use name, imgName as a props in your component
const mapStateToProps = ({categories}) => {
const { name, imgName } = categories;
return {name, imgName};
};
export default connect(mapStateToProps, {getCategory})(CategoryImageList);