React not rendering multiple components through factory function - reactjs

The problem is the following, i have a factory:
import React from 'react';
export default (componentFactory) =>
{
return Component =>
{
class Factory extends React.Component
{
render()
{
let { state, props } = this;
return componentFactory({ state, props }, Component);
}
}
return Factory;
};
}
and then a simple Page component wrapper:
import React from 'react';
const Page = ({children, appState}) =>
(
<section>
{React.cloneElement(children, {appState})}
</section>
);
export default Page;
So, in order to create a HomePage, i use HomeFactory likewise:
import React from 'react';
import Factory from '../Factory';
import HeroBanner from '../Banner/HeroBanner';
const HomeFactory = ({state, props}, HomePage) =>
{
return <HomePage {...props} {...state} >
<HeroBanner />
</HomePage>;
};
export default Factory(HomeFactory);
The final code for the HomePage component that is being mounted, is then as follows:
import React from 'react';
import Page from '../Page';
import HomeFactory from '../HomeFactory';
const HomePage = ({children, appState}) =>
{
return (
<Page appState={appState}>
{children}
</Page>
);
};
export default HomeFactory(HomePage);
The problem seems to be the HomeFactory component, in which i put:
return <HomePage {...props} {...state} >
<HeroBanner />
</HomePage>;
as per example given, and this works 100% correctly, however, when i try to put multiple components in form of array or just many components nested in, like:
return <HomePage {...props} {...state} >
<HeroBanner />
<HeroBanner />
<HeroBanner />
</HomePage>;
I am getting the following error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
Basically, the Page wrapper component is getting properties passed in as children of its parent component HomePage, and these are taken from the factory method.
The question is what is happening implicitly with JSX that I cannot do the following?

In your Page component you need to use cloneElement along with Children.map when you pass multiple children (this.props.children is then a special array-like object)
See this answer: stackoverflow.com/a/38011851/3794660 for more details.

Related

How and where do I use the data from an Apollo Client?

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

Reactjs - how to pass props to Route?

I’m learning React Navigation using React-Router-Dom. I have created a simple app to illustrate the problem:
Inside App.js I have a Route, that points to the url “/” and loads the functional Component DataSource.js.
Inside DataSource.js I have a state with the variable name:”John”. There is also a buttonwith the onclick pointing to a class method that’s supposed to load a stateless component named ShowData.js using Route.
ShowData.js receives props.name.
What I want to do is: when the button in DataSource.js is clicked, the url changes to “/showdata”, the ShowData.js is loaded and displays the props.name received by DataSource.js, and DataSource.js goes away.
App.js
import './App.css';
import {Route} from 'react-router-dom'
import DataSource from './containers/DataSource'
function App() {
return (
<div className="App">
<Route path='/' component={DataSource}/>
</div>
);
}
export default App;
DataSource.js
import React, { Component } from 'react';
import ShowData from '../components/ShowData'
import {Route} from 'react-router-dom'
class DataSource extends Component{
state={
name:' John',
}
showDataHandler = ()=>{
<Route path='/showdata' render={()=><ShowData name={this.state.name}/>}/>
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
}
export default DataSource;
ShowData.js
import React from 'react';
const showData = props =>{
return (
<div>
<p>{props.name}</p>
</div>
)
}
export default showData;
I have tried the following, but, even though the url does change to '/showdata', the DataSource component is the only thing being rendered to the screen:
DataSource.js
showDataHandler = ()=>{
this.props.history.push('/showdata')
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
<Route path='/showdata' render={()=>{<ShowData name={this.state.name}/>}}/>
</div>
)
}
I also tried the following but nothing changes when the button is clicked:
DataSource.js
showDataHandler = ()=>{
<Route path='/showdata' render={()=>{<ShowData name={this.state.name}/>}}/>
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
How can I use a nested Route inside DataSource.js to pass a prop to another component?
Thanks.
EDIT: As user Sadequs Haque so kindly pointed out, it is possible to retrieve the props when you pass that prop through the url, like '/showdata/John', but that's not what I'd like to do: I'd like that the url was just '/showdata/'.
He also points out that it is possible to render either DataSource or ShowData conditionally, but that will not change the url from '/' to '/showdata'.
There were multiple issues to solve and this solution worked as you wanted.
App.js should have all the routes. I used Route params to pass the props to ShowData. So, /showdata/value would pass value as params to ShowData and render ShowData. And then wrapped the Routes with BrowserRouter. And then used exact route to point / to DataSource because otherwise DataSource would still get rendered as /showdata/:name has /
DataSource.js will simply Link the button to the appropriate Route. You would populate DataSourceValue with the appropriate value.
ShowData.js would read and display value from the router prop. I figured out the object structure of the router params from a console.log() of the props object. It ended up being props.match.params
App.js
import { BrowserRouter as Router, Route } from "react-router-dom";
import DataSource from "./DataSource";
import ShowData from "./ShowData";
function App() {
return (
<div className="App">
<Router>
<Route exact path="/" component={DataSource} />
<Route path="/showdata/:name" component={ShowData} />
</Router>
</div>
);
}
export default App;
DataSource.js
import React, { Component } from "react";
import ShowData from "./ShowData";
class DataSource extends Component {
state = {
name: " John",
clicked: false
};
render() {
if (!this.state.clicked)
return (
<button
onClick={() => {
this.setState({ name: "John", clicked: true });
console.log(this.state.clicked);
}}
>
Go!
</button>
);
else {
return <ShowData name={this.state.name} />;
}
}
}
export default DataSource;
ShowData.js
import React from "react";
const ShowData = (props) => {
console.log(props);
return (
<div>
<p>{props.name}</p>
</div>
);
};
export default ShowData;
Here is my scripts on CodeSandbox. https://codesandbox.io/s/zen-hodgkin-yfjs6?fontsize=14&hidenavigation=1&theme=dark
I figured it out. At least, one way of doing it, anyway.
First, I added a route to the ShowData component inside App.js, so that ShowData could get access to the router props. I also included exact to DataSource route, so it wouldn't be displayed when ShowData is rendered.
App.js
import './App.css';
import {Route} from 'react-router-dom'
import DataSource from './containers/DataSource'
import ShowData from './components/ShowData'
function App() {
return (
<div className="App">
<Route exact path='/' component={DataSource}/>
{/* 1. add Route to ShowData */}
<Route path='/showdata' component={ShowData}/>
</div>
);
}
export default App;
Inside DataSource, I modified the showDataHandler method to push the url I wanted, AND added a query param to it.
DataSource.js
import React, { Component } from 'react';
class DataSource extends Component{
state={
name:' John',
}
showDataHandler = ()=>{
this.props.history.push({
pathname:'/showdata',
query:this.state.name
})
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
}
export default DataSource;
And, finally, I modified ShowData to be a Class, so I could use state and have access to ComponentDidMount (I guess is also possible to use hooks here, if you don't want to change it to a Class).
Inside ComponentDidMount, I get the query param and update the state.
ShowData.js
import React, { Component } from 'react';
class ShowData extends Component{
state={
name:null
}
componentDidMount(){
this.setState({name:this.props.location.query})
}
render(){
return (
<div>
<p>{this.state.name}</p>
</div>
)
}
}
export default ShowData;
Now, when I click the button, the url changes to '/showdata' (and only '/showdata') and the prop name is displayed.
Hope this helps someone. Thanks.

Redirect with props - React Router - Cannot read property 'props' of undefined

I want to redirect to a specific component (named SingleBook) and send along some props (bookId). I am using: "react-router-dom": "^5.1.2"
Following the guide https://reacttraining.com/react-router/web/api/Redirect, I should write something like:
return <Redirect
to={{
pathname: "/book",
state: { "bookId": "bookId" }
}}
/>
And in my routes:
<Route path="/book" render={(props) => <SingleBook {...props}/>}/>
Then, on my redirected-to component I should be able to grab the props by doing somethign like:
const bookId = this.props.location.state.bookId
HOWEVER
It DOES redirect correctly to the <SingleBook> but I get the error:
Cannot read property 'props' of undefined
I tried also to define my routes as:
<Route path="/book" component={SingleBook}/>
but I get the same error
SingleBook component
import React, {useEffect} from "react"
import Grid from '#material-ui/core/Grid';
function SingleBook({props}) { // I tried also without {props}
const bookId = this.props.location.state.bookId
console.log(bookId)
return(
<Grid>
single book {bookId}
</Grid>
)
}
export default SingleBook
Since you're using functional components you don't have access to this, you get props as argument to the functional component. So this code will work:
function SingleBook(props) { // Access props here without the {}
const bookId = props.location.state.bookId; // No `this`
console.log(bookId)
return(
<Grid>
single book {bookId}
</Grid>
)
}
export default SingleBook
import React from "react"
import Grid from '#material-ui/core/Grid';
const SingleBook = () => {
let {location: {state : {bookId} = {}} = {}} = this.props;
let bookId = bookId;
console.log(bookId)
return(
<Grid>
single book {bookId}
</Grid>
)
}
export default SingleBook

ReactJS check the render method of `Provider` ContextAPI

Hi I'm trying to use Context API and this is my code
App.js
import {Provider} from './components/contexApi/context';
class App extends Component {
render() {
return (
<Provider>
<div className="App">
<Header />
<TodoList />
</div>
</Provider>
);
}
}
And this is my context.js file
context.js
import React, { Component } from 'react'
const Context = React.createContext;
export class Provider extends Component {
state = {
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
export const Consumer = Context.Consumer;
After I save the file the main page says:
Error: Element 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 Provider.
React.createContext is a function and you are not calling it.
https://reactjs.org/docs/context.html#reactcreatecontext

React-router custom prop not passing to component. ternary operator not working correctly

In React i have my App.js page where i keep my states. I'm importing user1.js component to App.js, and in user1.js component i have a link button that takes me to path /user2.
When i click the button, React will set state property called testValue to true and in user2.js page ternary operator should choose the first value - test works because of that. But for some reason it does not work.
Any help?
APP.JS
import React, { Component } from 'react';
import './App.css';
import User1 from './components/user1';
class App extends Component {
constructor(props){
super(props);
this.state = {
testValue:false
};
}
change = () => {
this.setState({
testValue:true
},() => {
console.log(this.state.testValue)
});
}
render() {
return (
<div className="App">
<User1 change={this.change}/>
</div>
);
}
}
export default App;
USER1.JS
import React from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import User2 from './user2.js';
const User1 = (props) => {
return(
<BrowserRouter>
<div>
<Link to ="/user2">
<button onClick={props.change}>Next page</button>
</Link>
<Switch>
<Route path="/user2" exact component={User2}/>
</Switch>
</div>
</BrowserRouter>
); // end of return
};
export default User1;
USER2.JS
import React from 'react';
const User2 = (props) => {
console.log(props)
return(
<div>
{props.testValue ?
<p>test works</p>
:
<p>test does not work</p>
}
</div>
);
};
export default User2;
This is what i expected - test works
This is what i got - test does not work
You want to pass a custom property through to a component rendered via a route. Recommended way to do that is to use the render method.
<Route path="/user2" exact render={(props) => <User2 {...props} testValue={true} />} />
I think a valid inquiry here would be what are you wanting to pass through as an extra prop? whats the use case here? You may be trying to pass data in a way you shouldn't (context would be nice :D).

Resources