My project uses React-Redux Provider.
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
and
class App extends Component {
componentDidMount(){
API.getCategories().then((categories)=>{
this.props.dispatch(addCategories(categories))
})
API.getAllPosts().then(posts => {
console.log('getAllPosts', posts)
})
}
render() {
return (
<div className="App">
<Route exact path="/" render={()=>{
return (
<div>
{
this.props.categories.map((category)=>{
return (
<Link key={category.name} to={`/category/${category.name}`} params={{category: category.name}} >{category.name}</Link>
)
})
}
</div>
)
}}
/>
<Route path="/category/:category" component={Category} />
</div>
);
}
}
function mapStateToProps(x) {
return {
categories: x.categories
}
}
// export default App;
export default withRouter(connect(
mapStateToProps,
)(App))
From the above code and based on my experience from a previous project, the Category component's this.props should have a dispatch method that I can call the actions with but for some reason it is not there.
This is my Category Component:
class Category extends Component {
componentDidMount(){
console.log('this.props of Category', this.props)
var category = this.props.match.params.category
API.getPosts(category).then((posts)=>{
console.log('after getPosts', posts)
this.props.dispatch(addAllPosts(posts))
})
}
render(){
return <p>Category</p>
}
}
export default Category
What am I missing here???
You need to use the connect function from react-redux on your Category component so it has access to dispatch.
export default connect()(Category)
Also, it might just be simplified for SO, but App does not need to be wrapped in withRouter. This is only required if you need the router props injected into the component. Route does this automatically for any component it renders, which is why you don't need it on Category.
export default connect(mapStateToProps)(App)
Related
How and where do I have to put that query so that I can map through it and display the categories in a header. I'm a noob and all the apollo documentation is made with hooks and functional components, but I have to do this assignment with class based components and I just can't figure it out.
index.js:
const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache: new InMemoryCache(),
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById("root")
);
App.js:
class App extends Component {
render() {
return (
<>
<Header />
</>
);
}
}
export default App;
Header.js:
class Header extends Component {
render() {
return (
<header>
<ul className="nav-list">
//Display categories here with map in a <li className="nav-item">
</ul>
</header>
)
}
}
query I need for header elements :
const QUERY = gql`
query getCategories {
categories {
name
}
}
`;
Hooks don't work with class components - but you can wrap your class components and pass the hook result as props to the class component:
import React from 'react';
import { useScreenWidth } from '../hooks/useScreenWidth';
export const withQueryHOC = (Component, query) => {
return (props) => {
const { loading, error, data } = useQuery(query);
return <Component loading={loading} error={error} data={data} {...props} />;
};
};
This is called a Higher-Order Component
Where Component is the class component you want to wrap. This way you can use class components but still have access to the hooks.
You should export you component like so:
export default withQueryHOC(YourComponentHere);
In your component you should access loading, error and data through this.props
I am trying to implement role based authentication as seen in this tutorial
REACT AUTHENTICATION TUTORIAL
This is my function for react-router-dom
<Switch>
<Route exact path="/addcloth" component={Authorization(AddCloth, [1], role, [storelist, sectionlist])} />
<Switch />
And this is my authorization function
export default function Authorization(WrappedComponent, allowedRoles, userType, property) {
return class WithAuthorization extends React.Component {
render() {
if (allowedRoles.includes(userType)) {
let Component = <WrappedComponent />;
// some code to add property elements into Component
return Component;
} else {
return (
<AccessDenied />
);
}
}
};
};
As storelist and sectionlist are 2 props for AddCloth component and I am trying to pass that into AddCloth. In the tutorial he didnt mention about the same.
You are almost there. You need to send it as object.
<Switch>
<Route exact path="/addcloth" component={Authorization(AddCloth, [1],
role, {storelist, sectionlist})} />
<Switch />
In HOC, destructure the props and assign to component.
export default function Authorization(WrappedComponent, allowedRoles, userType, props) {
return class WithAuthorization extends React.Component {
render() {
if (allowedRoles.includes(userType)) {
let Component = <WrappedComponent {...props} />;
// some code to add property elements into Component
return Component;
} else {
return (
<AccessDenied />
);
}
}
};
};
I have a onClick function to navigate to other page. I tried this.props.history.push("/SecondPage/ID/") and some examples but nothing worked out.
I have the component like this:
export class MainPage extends Component {
constructor(props) {
super(props);
}
render(){
return (
<div id="main" onClick={this.NavigatetoOtherPage.bind(this)}>
)
}
NavigatetoOtherPage(){
let ID = this.props.ID; // I need to pass the ID as a parameter.
//Here I need to navigate to other page using. I can use window.location.href but I need to use react router.
}
}
export default connect(state => {
return {
ID: state.Reducer.ID,
};
})(MainPage)
My app.js file like this
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Route exact path='/' component={MainPage}/>
<Route path='/SecondPage/:ID/' component = {SecondPage} />
</Provider>
);
}
}
My index.js page like this
export function renderPage() {
ReactDOM.render(
<Router>
<App />
</Router>
, document.getElementById('root'));
}
renderPage();
How can I navigate to second page without window.location.href
You can use the useHistory hook or the Link component given you are using react-router-dom
import React from "react";
import { useHistory, Link } from "react-router-dom";
// Then in your component
const MainPage = (props) => {
/**
* hooks
*/
const history = useHistory();
/**
* function
*/
const handleNavigation = () => {
let ID = props.ID; // I need to pass the ID as a parameter.
history.push(`/dashboard/${ID}`)
}
return (
<button id="main" onClick={() => history.push("/")}> Go to / </button>
<button id="main" onClick={() => handleNavigation()}> Go to dynamic page
</button>
<Link to={`/dashboard/${props.ID}`} className="some-styling">
Using Link
</Link>
);
};
// I have merged both implementations
export default MainPage;
// Edited: Based on the comment, the issue is "The history is not coming in the props."
// Then you could use `withRouter` HOC, and then there will be
// the `history` object in the wrapped component's props.
import {withRouter} from 'react-router-dom';
class MainPage extends React.Component {
render(){
console.log(this.props.history) // history object
return(<div />)
}
}
export default withRouter(MainPage)`
Wrote down a small sandbox. I guess this is what you are trying to achieve.
https://codesandbox.io/s/practical-tereshkova-ilbig?file=/src/App.js
My website have a few pages that is protected by login. My current solution to this is:
in app.js:
<div className="app">
<Provider store={store}>
<Router history={appHistory} onUpdate={fireTracking}>
<Route name="main" component={AppHandler}>
<Route name="home" path="/" component={HomePageHandler}/>
</Route>
</Router>
</Provider>
</div>
And then my HomePageHandler is:
export default class HomePageHandler extends BaseAuthorizedComponent {
render() {
return (
<div>hello</div>
)
}
}
As the HomePageHandler extends BaseAuthorizedComponent, which is defined as:
class BaseAuthorizedComponent extends Component {
componentWillMount() {
if (!this.props.user.signed_in) {
this.context.router.push('/signin')
}
}
}
HomePageHandler.contextTypes = {
router: React.PropTypes.object.isRequired,
}
function select(state) {
return {
user: state.user,
}
}
export default connect(select)(BaseAuthorizedComponent)
The redux's user object has a flag that indicates if the user is logged in or not. The idea is that on the homepage, before the component is mounted, the BaseAuthorizedComponent would have checked and redirect to signin page if user is not logged in. My idea is to let every page that requires authorization to extend BaseAuthorizedComponent.
However the following error happens when trying to load the homepage:
Error: Could not find "store" in either the context or props of "Connect(BaseAuthorizedComponent)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(BaseAuthorizedComponent)".
No idea how can I fix the problem while keeping the advantage of a single place to check authorization. Any thoughts? Thanks!
First of all you is better to use composition instead of inheritance https://reactjs.org/docs/composition-vs-inheritance.html
Next, you can add "push" action creator from react-router-redux (https://github.com/reactjs/react-router-redux) to mapDispatchToProps function:
function composeAuth = (ComposedComponent) => {
class BaseAuthorizedComponent extends React.Component {
// We use componentDidMount instead of componentWillMount, cause componentWillMount is deprecated https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b
componentDidMount() {
if (!this.props.user.signed_in) {
this.props.push('/signin');
}
}
render() {
if (!this.props.user.signed_in) {
return null;
}
return <ComposedComponent {...this.props} />
}
}
return connect(state => ({user: state.user}), {push})(BaseAuthorizedComponent);
}
class HomePageHandler extends React.Component {
render() {
return (
<div>hello</div>
)
}
}
export default composeAuth(HomePageHandler);
How about this:
class CheckAuth extends React.Component{
state = {
auth: false
}
render(){
return(
{this.state.auth ? <div>Authorized user</div> : <div>Unauthorized user</div>}
)
}
}
function mapStateToProps(state){
return{
auth: state.auth
}
}
export default connect(mapStateToProps)(CheckAuth);
And then include it in your other components like so:
import CheckAuth from './CheckAuth';
...
class Home extends React.Component{
render(){
return(
<div>
<CheckAuth />
Hello world!!
</div>
)
}
}
export default Home;
After more research, the easiest way to satisfy my requirement is:
in an util file:
export function requireAuth(nextState, replace) {
// use your own method to check if user is logged in or not
if (!isLoggedIn()) {
replace({pathname: '/signin'});
}
}
and then import this method in the app.js file and use it:
<div className="app">
<Provider store={store}>
<Router history={appHistory} onUpdate={fireTracking}>
<Route name="main" component={AppHandler}>
<Route name="home" path="/" component={HomePageHandler} onEnter={requireAuth}/>
</Route>
</Router>
</Provider>
</div>
In this way if the user requires auth(isLoggedIn() is false), then it will redirect the page to /signin.
after some research, the best way I've seen is this:
<Route name="name"
path="/path"
component={THeWorkHandler}
onEnter={requireAuth}/>
And the requireAuth is put in a helper file:
export function requireAuth(nextState, replace) {
if (!(//logic to see if user is logged in )) {
replace({pathname: '/user/signin'});
}
}
This way if a onEnter requireAuth determines that the user is not authenticated, it will redirect to /user/signin page.
I'm looking to make this piece of code work and didn't get it through the docs or the example in the source code in the redux-router project.
I've this code (started with root in /frontend for migration reasons):
class App extends Component {
render() {
const links = [
'/frontend',
'/frontend/season',
'/frontend/episode'
].map(l =>
<p>
<Link to={l}>{l}</Link>
</p>
);
console.log('render');
return (
<div>
<h1>App Container</h1>
{links}
{this.props.children}
</div>
);
}
}
App.propTypes = {
children: PropTypes.node
};
function mapStateToProps(state) {
return {
routerState: state.router
};
}
connect(mapStateToProps)(App);
const rootReducer = combineReducers({
episode,
router: routerStateReducer
});
const store = compose(
reduxReactRouter({ createHistory})
)(createStore)(rootReducer,initialState);
class Root extends Component {
render() {
return (
<div>
<ReduxRouter history={history}>
<Route path="/frontend" component={App}>
<Route name="episode" path="episode" component={EpisodeApp} />
<Route name="season" path="season" component={SeasonApp} />
</Route>
</ReduxRouter>
</div>
);
}
}
React.render(
<Provider store={store}>
{() => <Root/>}
</Provider>
, document.getElementById('root'));
The thing is that when I press the links nothing changes and the App doesn't re-renders its children, but when I go back and forth using the browser navigation it does work. Where am I screwing this up?
Thanks a lot!
Update:
Replace this line:
<ReduxRouter history={history}>
by this one (so removing history object):
<ReduxRouter>
Make it work, not sure why.