Use a global state with Context - javascript

I'm currently trying to set a globalstate into my app. It's just about counting points, so a global "score".
Context seemed easy and I followed some tutorials, but it keeps sending errors.
In my app, I am using React and BrowserRouter to switch between pages.
App.jsx You can see, I tried to wrap with the Store Component
export default function App() {
return (
<main>
<Routes>
<Store>
<Route path="/" element={<Welcome />} />
<Route path="/whoareyou" element={<WhoAreYou />} />
<Route path="/questionone" element={<QuestionOne />} />
<Route path="/questiontwo" element={<QuestionTwo />} />
<Route path="/questionthree" element={<QuestionThree />} />
<Route path="/result" element={<Result />} />
</Store>
</Routes>
</main>
);
}
Points.js This is my Context file
import React, { useState, createContext } from "react";
const initialState = { points: 0 };
export const Context = createContext(initialState);
export const Store = ({ children }) => {
const [state, setState] = useState(initialState);
return (
<Context.Provider value={[state, setState]}>{children}</Context.Provider>
);
};
Component Here is my component, where I want to count a 1 into the "points" Score
export default function WhoAreYou() {
const [state, setState] = useContext(Context);
return (
<StyledSection variant="center">
<StyledNavLink to="/questionone">
<StyledButton
type="button"
variant="next-button"
onClick={() => {
console.log(state.points);
}}
>
NEXT
</StyledButton>
</StyledNavLink>
</StyledSection>
);
}
Errors
This are the error messages:
undefined is not an object (evaluating 'element.type.name')
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of App.
App
Router
BrowserRouter
To See the whole code, you can see this CodeSandBox:
https://codesandbox.io/s/empty-https-gpkq0l?file=/src/components/pages/WhoAreYou.jsx:581-1422
I would be very thankful if you guys could help!!

Import Store as named import (you exported it as named export)
import { Store } from "../src/components/context/Points";

Related

How do I pass props from from redux store

I have a simple app that's using redux and react-router. I wrapped my app component in a provider tag so that it has access to the store. I connected (in App.js) the mapStateToProps and mapStateToDispatch in the App.js. I'm not sure how to pass the function I defined in App.js to a child component since I'm using route. I tried doing the render trick but it didn't work. If I can pass it to that CelebrityPage component, how would I receive it in the file? Any help would be appreciated.
This is my App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import './App.css';
import Clarifai from 'clarifai'
// import Particles from 'react-particles-js';
// import particlesOptions from './particleOptions'
import { Signin } from './components/signin/Signin';
import Register from './components/register/Register';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import { setSearchField } from './context/Actions'
import FacePage from './Containers/FacePage';
import CelebrityPage from './Containers/CelebrityPage';
import ControllerPage from './Containers/ControllerPage';
const mapStateToProps = state => {
return {
input: state.input
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleSearchChange: (event) => dispatch(setSearchField(event.target.value))
}
}
...
render() {
return (<Router>
<Switch >
<Route path='/celebrity' exact render={props => <CelebrityPage{...props} handleSearchChange={this.handleSearchChange} />} />
<Route path='/' exact component={Register} />
<Route path='/signin' exact component={Signin} />
<Route path='/contoller' exact component={ControllerPage} />
<Route path='/face-detection' exact component={FacePage} />
</Switch>
</Router>)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
If you are going to pass store actions and states into the child components, it means you are refusing to use the advantages of redux. The best approach should be connect any of your component that needs to access to the actions or state to the store. Doing connection at the root component level and passing the props to the child components is not a good solution.
I think what robert is saying is what you'd probably want to do. Don't try to pass your props inside of your <Route>. Instead do your connect mapDispatchToProps and your mapStateToProps inside your CelebrityPage Component.
Once you do the wrapping inside of the Celebrity Page component you should have access to the props and functions that you have defined.
...
// keep all the previous imports from your App.Js
render() {
// have your router like this
return (<Router>
<Switch >
<Route path='/celebrity' exact component ={CelebrityPage} />
<Route path='/' exact component={Register} />
<Route path='/signin' exact component={Signin} />
<Route path='/contoller' exact component={ControllerPage} />
<Route path='/face-detection' exact component={FacePage} />
</Switch>
</Router>)
}
}
export default App
Example Celebrity page
import React from 'react'
import { connect } from 'react-redux'
class CelebrityPage extends React.Component {
// put your mapStateToProps and mapDispatch function heres instead of app.js
mapStateToProps() {
}
mapDispatchToProps {
// bind your handlesearch function to props here
}
render() {
return (
<div>
<input />
<button onClick={this.props.handleSearchChange}/>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CelebrityPage)

Pascal case error for dynamically adding component name to Reactjs Route's render method

I am trying to get a component to render dynamically. The name for the component is getting pulled from the state. The name is in pascal case but still it throws a pascal case error. For instance, the value that I am trying on is "HeroBanner". If I put this directly, it works fine. If get this dynamically via state and then assigning it to a variable, it backfires with pascal case error.
Here's my code
import '#babel/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Route, Switch, Redirect, withRouter} from 'react-router-dom'
import { createBrowserHistory } from 'history';
import HeroBanner from './IssueList.jsx';
import IssueTable from './IssueTable.jsx';
import {PropTypes} from 'prop-types';
import {Header} from './Header.jsx';
import Demo from './Cropcrop.jsx';
import {Receiver} from 'react-file-uploader';
const contentNode = document.querySelector("#contents");
const NoMatch = () => <p>404 no result</p>
class App extends React.Component{
constructor(){
super();
this.state = {
currentLocation: ""
}
this.changeLocation = this.changeLocation.bind(this)
}
changeLocation(e){
this.setState({
currentLocation: e
})
}
render(){
const Component= this.state.currentLocation === ''? "HeroBanner" : this.state.currentLocation
return(
<div>
<BrowserRouter>
<Header/>
<Redirect from="/" to="/selection"></Redirect>
<Switch>
<Route exact path={"/"+ Component} render={(props) => <Component />} />
<Route exact path="/selection" render={(props) => <IssueTable {...props} onChange={this.changeLocation} />} />
<Route path="/*" component={NoMatch}/>
</Switch>
</BrowserRouter>
<Component/>
</div>
)
}
}
const RoutedApp = () => (
<App/>
);
ReactDOM.render(<RoutedApp />, contentNode);
if(module.hot){
module.hot.accept();
}
You're not allowed to specify component dynamically like this in React (specifying a string instead of a type):
render={(props) => <Component />}
It should be something like:
const Component = this.state.currentLocation === '' ? HeroBanner : SomeOtherComponent;
I had a similar situation in the project I'm working on and ended up with creating a map between a certain string value (in my case it was the name of a tab, coming from the URL) and component's type:
const componentsMap = {
home: HomeComponent,
about: AboutComponent,
...
}
const Component = componentsMap[myStringValue];
That last piece of code is allowed as you're refering to the component's type, not to some ordinary string which can't be used as valid JSX.
As for routing, I noticed you just set some string value in the state and expect routing to happen. This is not the case as react-router-dom listens for changes in the browser's url/path and you should probably perform manual routing in your case to move to another <Route> view. In the example below I perform navigation in the onDropdownChange handler upon dropdown change.
The way I like to minimize repetition of strings representing route names and component names is to have the dynamic value as param in the URL. Then a <Subroute> component handles all the mapping for that group of routes (you might not need that additional level of nesting, depends on size of app). In the sandbox I created you can see the map I was talking about in the comments. You select the appropriate component based on the tab param in the URL (which holds the value selected from the dropdown).
Example here
EDIT for a question in the comments (11 Aug 2020):
You can also use a neat trick to customize what gets rendered by a route, like this (without having to use render of Route):
<Route exact path={path}>
<ComponentOne {...someProps} />
<ComponentTwo {...someOtherProps} />
<p>Some html too</p>
</Route>
To use render instead (maybe less readable, I prefer the first option, just pass the necessary JSX, wrapped in Fragment if needed):
<Route
exact
render={routeProps => (
<Fragment>
<ComponentOne {...someProps} />
<ComponentTwo {...someOtherProps} />
<p>Some html too</p>
</Fragment>
)}
/>
While the above answer is absolutely correct. Here's how I followed the above the logic.
import '#babel/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Route, Switch, Redirect, withRouter} from 'react-router-dom'
import { createBrowserHistory } from 'history';
import HeroBanner from './IssueList.jsx';
import IssueTable from './IssueTable.jsx';
import {PropTypes} from 'prop-types';
import {Header} from './Header.jsx';
import Demo from './Cropcrop.jsx';
import {Receiver} from 'react-file-uploader';
const contentNode = document.querySelector("#contents");
const NoMatch = () => <p>404 no result</p>
const componentList={
HeroBanner: HeroBanner
}
class RoutedApp extends React.Component{
constructor(){
super();
this.state = {
currentLocation: ""
}
this.changeLocation = this.changeLocation.bind(this)
}
changeLocation(e){
console.log(e)
this.setState({
currentLocation: e
})
}
render(){
const Component= this.state.currentLocation === '' ? NoMatch : componentList[this.state.currentLocation]
console.log(Component)
return(
<div>
<BrowserRouter>
<Header/>
<Redirect from="/" to="/selection"></Redirect>
<Switch>
<Route exact path={"/"+ this.state.currentLocation} component={Component} />
<Route exact path="/selection" render={(props) => <IssueTable {...props} onChange={this.changeLocation} />} />
<Route path="/*" component={NoMatch}/>
</Switch>
</BrowserRouter>
</div>
)
}
}
ReactDOM.render(<RoutedApp />, contentNode);
if(module.hot){
module.hot.accept();
}

react context value undefined

I am following a tutorial, and i'm having an issue with react context
I have my context component that looks like this
import React from 'react'
export const firebaseAuth = React.createContext()
const AuthProvider = props => {
const { children } = props
return (
<firebaseAuth.Provider
value={{
test: 'context is working',
}}
>
{children}
</firebaseAuth.Provider>
)
}
export default AuthProvider
And I am trying to test my context inside of my Router component, unfortunately test is undefined and i'm not sure how to debug it
import React, { useContext } from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import AuthProvider, { firebaseAuth } from './database/provider/AuthProvider'
const Router = () => {
const test = useContext(firebaseAuth)
console.log(test)
return (
<BrowserRouter>
<AuthProvider>
<Switch>
<Route path="/login/" component={SignIn} />
<Route component={NotFound} />
</Switch>
</AuthProvider>
</BrowserRouter>
)
}
export default Router
I know this question has been asked a bunch of time on here, and I have looked at those threads and still cannot find a solution.
any ideas?
Thanks
In order to get a value from context, there needs to be a provider somewhere higher up the component tree. So anything that's a descendant of <AuthProvider> can call useContext(firebaseAuth) and see {test: 'context is working'}. But Router is outside of <AuthProvider>, so it will only get the default value, which in your case is undefined.

module.default is undefined dynamic import

I'm trying to use React.lazy to lazy load a React Component.
The component looks like this:
function App() {
const HubScreen = React.lazy(() => import("./screens/hub").then((mod) => {
console.log(mod.default)
return mod.default;
}));
return (
<BrowserRouter>
<MuiThemeProvider theme={theme}>
<MainContaier>
<div id="screen">
<CssBaseline />
<Switch>
<React.Suspense fallback={<h1>Loading...</h1>}>
<Route exact path="/" component={HomeScreen} />
<Route path="/hub" render={() => <HubScreen />} />
</React.Suspense>
</Switch>
</div>
</MainContaier>
</MuiThemeProvider>
</BrowserRouter >
)
}
And this is the component I'm importing
import React from "react";
function HubScreen() {
return (
<div>Hi</div>
);
}
export default HubScreen;
When I navigate to /hub I see the value of mod.default as undefined. Along with my Chrome window becoming completely unresponsive, requiring a force stop.
I know that my path to the module ./screens/hub, is correct because, if I put a fake path like ./screens/hube then webpack gives me the error:
Module not found: Error: Can't resolve './screens/hube' in '/home/travis/Workspace/avalon/assets/js'
I'm stumped haven't found a similar problem anywhere.
This answer gave me some insight as to why my browser was hanging up. However I still seem to have the same root problem; the undefined module.default. After changing the root component to this:
const HubScreen = React.lazy(() => import("./screens/hub"));
function App() {
return (
<BrowserRouter>
<MuiThemeProvider theme={theme}>
<MainContaier>
<div id="screen">
<CssBaseline />
<Switch>
<React.Suspense fallback={<h1>Loading...</h1>}>
<Route exact path="/" component={HomeScreen} />
<Route path="/hub" component={HubScreen} />
</React.Suspense>
</Switch>
</div>
</MainContaier>
</MuiThemeProvider>
</BrowserRouter >
)
}
I get the War:
Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Object]
Your code should look like:
const MyComponent = lazy(() => import('./MyComponent'))
And then the error:
Uncaught Error: Element type is invalid. Received a promise that resolves to: undefined. Promise elements must resolve to a class or function.
Which I have taken to mean that undefined is being returned from the import resolution, as the console.log seems to confirm.
Also another possibility of getting this error is by missing the default export in your Component which becomes a named export.
This syntax is only supported for Default exports.
const HubScreen = React.lazy(() => import("./screens/hub"));
import React, {useState} from 'react';
const ChangeName = (props) => {
return (
<div>
<p>Name: {props.name}</p>
<p>Email: {props.email}</p>
<div>
<button onClick={()=>props.changeStatus()}>Change Details</button>
</div>
</div>
)
}
export default ChangeName; ====> By missing the default Export
First off, move your const HubScreen outside of your component. When App() rerenders, it will cause an infinate loop to keep trying to load HubScreen over and over again. Secondly, just use () => import... and let React.lazy use the default component exported. Additionally, you should not need to use render for the route. Just provide the component:
const HubScreen = React.lazy(() => import("./screens/hub"));
function App() {
return (
<BrowserRouter>
<MuiThemeProvider theme={theme}>
<MainContaier>
<div id="screen">
<CssBaseline />
<Switch>
<React.Suspense fallback={<h1>Loading...</h1>}>
<Route exact path="/" component={HomeScreen} />
<Route path="/hub" component={HubScreen} />
</React.Suspense>
</Switch>
</div>
</MainContaier>
</MuiThemeProvider>
</BrowserRouter >
)
}

Converting stateless React component having arguments to stateful

Inside my React JS project, I am working on the PrivateRoutes.
I have gone through this example of private routing and authenticating using react-router-dom.
https://reacttraining.com/react-router/web/example/auth-workflow
According to this documentation, they have created a PrivateRoute as a stateless component.
But my requirement is to convert it to stateful React component as I want to connect my PrivateRoute component to redux store.
Here is my code.
stateless component
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {auth} from './Authentication';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
export default PrivateRoute;
I converted this component to stateful React component like this.
stateful React component
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {auth} from './Authentication';
import {connect} from 'react-redux';
class PrivateRoute extends React.Component {
render({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props =>
this.props.customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
}
}
export default connect(state => state)(PrivateRoute);
Here, I am reading the data from redux store to check whether the user is authenticated or not.
But the way I am converting the stateless component to stateful isn't correct.
Am I passing the arguments render({ component: Component, ...rest }) correctly?
Will connecting the PrivateRoute with redux store create any problem with props as state=>state will map state to props as well as ...rest will have props object?
Not sure what is happening inside the code.
Update
AppRouter.js
import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import {TransitionGroup, CSSTransition} from 'react-transition-group';
import PrivateRoute from './PrivateRoute';
import HomePage from './../components/HomePage';
import AboutUs from './../components/AboutUs';
import ContactUs from './../components/ContactUs';
import PageNotFound from './../components/PageNotFound';
import RestaurantList from '../components/RestaurantList';
import RestaurantMenu from '../components/RestaurantMenu';
import UserDetails from '../components/UserDetails';
import OrderConfirmation from '../components/OrderConfirmation';
import CustomerAccount from '../components/CustomerAccount';
import Logout from '../components/sections/Logout';
export default () => {
return (
<BrowserRouter>
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition key={location.key} timeout={300} classNames="fade">
<Switch location={location}>
<Route path="/" component={HomePage} exact={true}/>
<Route path="/about" component={AboutUs} />
<Route path="/contact" component={ContactUs} />
<Route path="/restaurants" component={RestaurantList} />
<Route path="/select-menu" component={RestaurantMenu} />
<PrivateRoute path="/user-details" component={UserDetails} />
<PrivateRoute path="/order-confirmation" component={OrderConfirmation} />
<PrivateRoute path="/my-account" component={CustomerAccount} />
<PrivateRoute path="/logout" component={Logout} />
<Route component={PageNotFound} />
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
</BrowserRouter>
);
}
In general, converting a stateless functional component (SFC) to a Component is done like this:
Create the class shell for it.
Copy the SFC's body to the render method. If the SFC was an arrow function, add a return as necessary to render.
Change any references to props in the render method to this.props (or just add const { props } = this; at the top). SFCs receive their props in their arguments, but a component receives them as arguments to its constructor; the default constructor will save them as this.props.
In your case, it's using destructuring on its arguments, so you could do the same with this.props on the right-hand side of the destructuring:
const { component: Component, ...rest } = this.props;
That's it. In your code, you've added parameters to the render function, but it doesn't get called with any arguments, and you've only changed props to this.props a bit haphazardly (including changing auth.isAuthenticated to this.props.customer.isAuthenticated for some reason).
So applying 1-3 above:
// #1 - the shell
class PrivateRoute extends React.Component {
// #2 - `render`, with the body of the SFC inside
render() {
// #3 - destructure `this.props`
const { component: Component, ...rest } = this.props;
// #2 (part 2) - add `return`
return <Route
{...rest}
render={props =>
auth.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>;
}
}
Your stateful component should be:
class PrivateRoute extends React.Component {
render() {
const { component: Component, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
this.props.customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
}
}
Please see that there is some issue in render parameter of Route. Here you have props as function param but still using this.props.customer, don't know the use case hence please fix it as per your application.
Apart from it Component and all the other data is already there in props of the component. It won't be available in parameter of render method in component. Same destructuring as available in stateless component can be written in render method as shown in code above.
Will connecting the PrivateRoute with redux store create any problem with props?
Yes, it would. The way you have connected to the store will make store data available in props of component but external props passed to component will not be available.
For that you have to handle it in mapStateToProps function:
const mapStateToProps = (state, ownProps) => ({
...state,
...ownProps
});
Here mapStateToProps has second parameter which has the external own props passed to component. So you have to return it as well to make it available in component props.
Now connect would be like:
export default connect(mapStateToProps)(PrivateRoute);
I was having two queries.
1) How to convert to Stateful Functional Component?
2) After connecting to the redux store will the props create a problem?
My first query was solved by the answer provided by T.J.Crowder.
For a second query, I tried connecting the redux store to the PrivateRoute and I did get the data I was looking for.
Here is the code which worked for me.
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {connect} from 'react-redux';
class PrivateRoute extends React.Component {
render() {
const { component: Component, ...rest } = this.props;
const {customer} = this.props;
return <Route
{...rest}
render={props =>
customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>;
}
}
export default connect(state => state)(PrivateRoute);
Using this code I got the data that is coming from the routes, as well as the redux state inside the props.
This is getting data coming from the routes
const { component: Component, ...rest } = this.props;
This is the data coming from the redux store.
const {customer} = this.props;
#T.J.Crowder has already written how to convert stateless component to stateful component in those 3 steps. so i will just write about connecting component to redux store like you did.
I think connected components should always define mapStateToProps and explicitly declare which data they depend on from the state.
because the connected component rerenders if the connected property changes. so it would be a bad idea to connect the whole application state to a component. as it would mean that wheneever anything changes in application state rerender all connected components.
better we define explicitly like the following that we depend on a property called data (or anything you have) from the state. so in this case this component will only rerender if state.data changes it wont rerender if state.xyz changes.
and this way you can take state.data and name it as you wish so it would not conflict with any existing props of the component.
const mapStateToProps = (state, ownProps) => ({
data: state.data
});
export default connect(mapStateToProps)(PrivateRoute);

Categories

Resources