Starting with ReactJS and NextJS
I have the following Layout component and using context to set values from the children component
export interface AuthContextModel {
title: string;
description: string;
showSignup: boolean;
}
export const AuthContext = createContext(null);
export const AuthLayout = ({children}) => {
const [authContext, setAuthContext] = useState<AuthContextModel>();
return (
<>
<Head>
<title>Authentication</title>
<HeadComponent/>
</Head>
<AuthContext.Provider value={setAuthContext}>
<h4>{authContext?.title}</h4>
<p className="text-muted mb-4">Sign in to continue to Chatvia.</p>
{children}
</AuthContext.Provider>
</>
)
}
And the Login page extends the Layout
export default function Login(props) {
const setAuthContext = useContext(AuthContext);
useEffect(() => {
setAuthContext({
title: 'Sign In'
})
}, [])
return (
<AuthLayout>
<form onSubmit={handleSubmit}>
...
</form>
</AuthLayout>
)
}
But the setAuthContext in the Login function is giving the following error
TypeError: setAuthContext is not a function
How can I update the context variable from the children component?
When you mount the Login, that component is not a child of AuthLayout. As a result, Login will not gain access to AuthContext.
If you wanted Login to have access to AuthContext, it would need to be mounted something like this:
<AuthLayout>
<Login />
</AuthLayout>
Right now, you're mounting it like this:
<AuthLayout>
{/* Something else */}
</AuthLayout>
<Login />
The consumer (Login) is not a child of the provider (AuthLayout), so the consumer currently can't get anything from that context unless it is a child of the provider.
https://reactjs.org/docs/context.html
Related
I am working on a react application.
I am trying to create login and register functionality.
I have a Authorized.tsx component which looks like this
export const Authorized = (props: authorizedProps) => {
const [isAuthorized, setIsAuthorized] = useState(true);
const { claims } = useContext(AuthContext);
useEffect(() => {
if (props.role) {
const index = claims.findIndex(
claim => claim.name === 'role' && claim.value === props.role)
setIsAuthorized(index > -1);
} else {
setIsAuthorized(claims.length > 0);
}
}, [claims, props.role]);
return (
<>
{isAuthorized ? props.authorized : props.notAuthorized}
</>
);
};
interface authorizedProps {
authorized: ReactElement;
notAuthorized?: ReactElement;
role?: string;
}
This component hides and shows diffrent kind of components depending on if the user is authorized or not.
I am using this component to only show the Login.tsx component for users that are not logged in. I dont want anyone who is not logged in to be able to visit the website.
In my Index.tsx I am using the Authorized.tsx component like this
const Index = () => {
const [claims, setClaims] = useState<claim[]>([
// { name: "email", value: "test#hotmail.com" },
]);
return (
<div>
<BrowserRouter>
<AuthContext.Provider value={{ claims, update: setClaims }}>
<Authorized authorized={<App />} notAuthorized={<Login />} />
</AuthContext.Provider>
</BrowserRouter>
</div>
);
};
All the authorized users will be able to visit the site, everyone else will be asked to log in.
However, the problem I have is when I tried adding the Register.tsx component into the Login.tsx component as a navigational link.
I wish to be able to navigate between Register and Login
This is how the Login.tsx component looks like
export const Login = () => {
return (
<>
<h3>Log in</h3>
<DisplayErrors errors={errors} />
<AuthForm
model={{ email: "", password: "" }}
onSubmit={async (values) => await login(values)}
BtnText="Log in" />
<Switch>
<Route path="/register">
<Register />
</Route>
<Link to='/register'>Register</Link>
</Switch>
</>
);
};
But what actually happends when I press the 'Register' link is that the Register component gets added below the Login component
Before pressing the 'Register' link
After pressing the 'Register' link
I understand it has something to do with the Authorized.tsx component in Index.tsx.
That I am telling it to only show the Login component when not authorized.
But I dont know how I could fix it so I will be able to navigate between the Login and the Register
All help I could get would be much appreciated!
Thanks
With the current implementation you are rendering a Login component that then also renders a route for a Register component to be rendered on. Login remains mounted and rendered the entire time. From what you describe you want to render Login and Register each on their own route.
Abstract both these components into a parent component that manages the route matching and rendering.
Example
const Unauthenticated = () => (
<Switch>
<Route path="/register" component={Register} />
<Route component={Login} />
</Switch>
);
...
export const Login = () => {
...
return (
<>
<h3>Log in</h3>
<DisplayErrors errors={errors} />
<AuthForm
model={{ email: "", password: "" }}
onSubmit={login}
BtnText="Log in"
/>
<Link to='/register'>Register</Link>
</>
);
};
...
const Index = () => {
const [claims, setClaims] = useState<claim[]>([
// { name: "email", value: "test#hotmail.com" },
]);
return (
<div>
<BrowserRouter>
<AuthContext.Provider value={{ claims, update: setClaims }}>
<Authorized
authorized={<App />}
notAuthorized={<Unauthenticated />}
/>
</AuthContext.Provider>
</BrowserRouter>
</div>
);
};
I am trying to redirect user in case user is not authenticated and vice versa
so, I have the directory structure as follow
myproject
src
App.js
UserContext.js
routes
index.js
route.js
pages
Dashboard
index.js
authentication
login.js
In my app.js i do a call and get my authentication token
and set auth to true and pass it in user context but it has the default values and i cannot redirect currently redirecting with only window.location.href
my code for usercontext.js
import { createContext } from "react";
export const UserContext = createContext(null)
APP.js
const App = props => {
const [user,setUser] = React.useState(null)
var [auth,setAuth] = React.useState(false)
const isAuthenticated = ()=>
{
var isAdmin = true;
axios.get(`/verifyToken`).then((response)=>{
console.log(response.data.auth)
setUser({...response.data.user})
setAuth(response.data.auth)
console.log(response.data.user)
})
}
useEffect(() => {
isAuthenticated()
console.log(auth)
},[]);
function getLayout() {
let layoutCls = VerticalLayout
switch (props.layout.layoutType) {
case "horizontal":
layoutCls = HorizontalLayout
break
default:
layoutCls = VerticalLayout
break
}
return layoutCls
}
const Layout = getLayout()
return (
<React.Fragment>
<Router>
<Switch>
<UserContext.Provider value={{user,setUser,auth,setAuth,isAuthenticated}}>
{publicRoutes.map((route, idx) => (
<Authmiddleware
path={route.path}
layout={NonAuthLayout}
component={route.component}
key={idx}
isAuthProtected={auth}
exact
/>
))}
{authProtectedRoutes.map((route, idx) => (
<Authmiddleware
path={route.path}
layout={Layout}
component={route.component}
key={idx}
isAuthProtected={auth}
exact
/>
))}
</UserContext.Provider>
</Switch>
</Router>
</React.Fragment>
)
}
My index.js file has component and routes names array which i am looping above
and this is my route.js
const Authmiddleware = ({
component: Component,
layout: Layout,
isAuthProtected,
...rest
}) => (
<Route
{...rest}
render={props => {
return (
<Layout>
<Component {...props} />
</Layout>
)
}}
/>
)
Authmiddleware.propTypes = {
isAuthProtected: PropTypes.bool,
component: PropTypes.any,
location: PropTypes.object,
layout: PropTypes.any,
}
export default Authmiddleware;
So, now If in my dashboard.js I try to access user on wan tto redirect if auth is false it only has default values of user and auth
I am fetching as follows in dashboard.js
import {UserContext} from '../../UserContext'
const {user,setUser,auth,setAuth,isAuthenticated} = React.useContext(UserContext)
React.useEffect(()=>{
if(auth == false){
window.location.href='/login'
//IT TAKES ME LOGIN EVERYTIME AT IT IS ONLY GETTING DEFAULT VALUE THAT IS FALSE
},[])
WHAT I HAVE TRIED
If i place the isAuthenticated() function call in every component it works
but that would be like so many lines of code same in every component
What is the way to go with?
Anyone facing the same issue I resolved it by
bringing out
<UserContext.Provider></UserContext.Provider>
outside the switch
<UserContext.Provider value={{user,setUser,auth,setAuth,isAuthenticated}}>
<Switch>
</Switch>
</UserContext.Provider value={{user,setUser,auth,setAuth,isAuthenticated}}>
I FOUND THE REASON HERE: https://coderedirect.com/questions/324089/how-to-use-context-api-with-react-router-v4
The reason posted in answer here was that Switch expects routes directly.
I have been trying to get familiar with React. I have two components. The API call is being made on the parent component and I'm trying to pass the data down to the child component.
This is my parent component:
export default function LandingPage(props) {
const [posts, setPosts] = useState([]);
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/posts").then((response) => {
setPosts(response.data);
});
}, []);
const classes = useStyles();
const { ...rest } = props;
return (
<div>
<div className={classNames(classes.main, classes.mainRaised)}>
<div className={classes.container}>
{/* HERE IS THE CHILD COMPONENT BELOW */}
<ProductSection posts={posts} />
</div>
</div>
<Footer />
</div>
);
}
This is my Child component:
export default function ProductSection(props) {
const classes = useStyles();
return (
<div className={classes.section}>
{/* HERE IS THE CONSOLE LOG */}
{console.log(props.posts[0])}
{/* HERE IS THE RENDER */}
<Typography>{props.posts[0]}</Typography>
</div>
);
}
ProductSection.propTypes = {
posts: PropTypes.arrayOf(
PropTypes.shape({
userId: PropTypes.number,
id: PropTypes.number,
title: PropTypes.string,
body: PropTypes.string,
})
),
};
Thanks in advance, kinda new to this.
The data is object, it would be more clear if typography component is shared.
Although I tried solving it without the Typography and its working after adding conditional for the non existing postData as data will be fetched and will wait for promise to serve.
It is working.
App.js -> Parent
ProductSection.js -> Child
Refer this sandbox which will help you with code.
See this sandbox to get more clarity:
LINK
I guess probably your data in props.posts[0] is object, so I would try with JSON.stringify(props.posts[0],null,2).
I'm trying to get the redux state value in the same file as where I use the provider.
For some reason it seems it cannot find the value.
const MyApp = ({ Component, pageProps }: AppProps)=> {
const isDark = useSelector<ThemeState, ThemeState["isDark"]>(state => state.isDark)
const dispatch = useDispatch()
return (
<>
<Provider store={ThemeStore}>
<div className={isDark ? 'dark' : 'white'}>
<Player />
<Component {...pageProps} />
</div>
</Provider>
</>
)
}
export default MyApp
This gives an error:
Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
When using the same useSelector and dispatch inside my nav component, it does work.
Any idea how I could make it work inside the _app.js file?
You can not use state in the provider like that, you need to go at least one layer deeper or just use what you're passing to the state directly not from calling to useSelector, try this:
function Child({ children }) {
const isDark = useSelector<ThemeState, ThemeState["isDark"]>(state => state.isDark)
return <div className={isDark ? "dark" : "white"}>{children}</div>;
}
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<Provider store={ThemeStore}>
<Child>
<Player />
<Component {...pageProps} />
</Child>
</Provider>
);
};
export default MyApp;
I'm trying to add simple authentication to a React+Typescript app using private routes and context. I have a simple login component with a button that just sets a boolean var authenticated in the context to true. The private routes should check this var and redirect to the login component if it's not true otherwise show the specified component. The problem is authenticated seems to always be false and I'm always redirected to the login page.
When I debug it I can see the setAuthenticated function in AuthContextProvider is called when the login button is clicked. But if I then click any of the links to private routes authenticated is always false.
Here's my App.tsx:
function App() {
return (
<AuthContextProvider>
<Router>
<Link to="/">Home</Link>
<Link to="/projects">Projects</Link>
<div>
<Route path="/login" component={Login} />
<PrivateRoute path="/" exact component={Home} />
<PrivateRoute path="/projects" component={Projects} />
</div>
</Router>
</AuthContextProvider>
);
}
export default App;
PrivateRoute.tsx:
interface PrivateRouteProps extends RouteProps {
// tslint:disable-next-line:no-any
component: any;
}
const PrivateRoute = (props: PrivateRouteProps) => {
const { component: Component, ...rest } = props;
return (
<AuthContextConsumer>
{authContext => authContext && (
<Route {...rest}
render={ props =>
authContext.authenticated === true ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
)}
</AuthContextConsumer>
);
};
export default PrivateRoute;
AuthContext.tsx:
export interface AuthContextInterface {
authenticated: boolean,
setAuthenticated(newAuthState: boolean):void
}
const ctxt = React.createContext<AuthContextInterface>({
authenticated: false,
setAuthenticated: () => {}
});
export class AuthContextProvider extends React.Component {
setAuthenticated = (newAuthState:boolean) => {
this.setState({ authenticated: newAuthState });
};
state = {
authenticated: false,
setAuthenticated: this.setAuthenticated,
};
render() {
return (
<ctxt.Provider value={this.state}>
{this.props.children}
</ctxt.Provider>
);
}
}
export const AuthContextConsumer = ctxt.Consumer;
Login.tsx:
function Login() {
return (
<AuthContextConsumer>
{({ authenticated, setAuthenticated }) => (
<div>
<p>Login</p>
<form>
<input type="text" placeholder="Username"/>
<input type="password" placeholder="Password"/>
<button onClick={event => {
setAuthenticated(true);
}}>Login</button>
</form>
</div>
)}
</AuthContextConsumer>
);
}
export default Login;
My suspicious is that there's something wrong with the state definition in AuthContextProvider. If I change authenticatedin here to true I see the opposite behaviour, I never see the login page. Should this be something dynamic?
Or, in the onClick callback, set event.preventDefault() so it doesn't submit the form.
The problem turned out to be that the app was reloading every time the login button was pressed, and therefore lost the state in the AuthContext.
The reason for this is that in my Login component I had a button inside a form, which automatically submits the form and reloads the page.
The solution is to either remove the form tags, or in the button specify the attribute type="button".