React pass x state variables to y {props.children} - javascript

I'm using functional components. I have built my separate components and am placing them into my index.js file.
Right now the structure of the components is (pseudocode):
<App stateVariable1={x} stateVariable2={y} stateVariable3={z}>
<ChildOne props1={x} props2={y} />
<ChildTwo props1={z}/>
</App>
ChildOne and ChildTwo are currently rendered within App.js, and so passing the state variables to the two children is very easy.
I'd like to extract the rendered children into index.js and replace the two children with a {props.children} within App.js, to increase the SRP of the component and make it more reusable.
However I am struggling with understanding how to pass multiple state variables as multiple props to props.children within my index.js file.
I have read the react documentation for Render Props and that deals with one prop being passed to a single child, rather than multiple children which want to receive different props.
How do I go about achieving what I would like to do?
UPDATE
I have attempted to use render props as per the accepted answer here: How to pass props to {this.props.children}
My index.js looks like this:
ReactDOM.render(
<React.StrictMode>
<App>
{(stateVariable1) => (
<Fragment>
<ChildOne props1={stateVariable1} props2={stateVariable2} />
<ChildTwo props3={stateVariable3} />
</Fragment>
)}
</App>{' '}
</React.StrictMode>,
document.getElementById('root')
);
However all of the props/stateVariables through undefined errors, as of course they're not defined in index.js.
Where am I going wrong?
Update 2: Solution
I passed all of the state variables as arguments to the render props and this has solved the issue:
ReactDOM.render(
<React.StrictMode>
<App>
{(stateVariable1, stateVariable2, stateVariable3) => (
<Fragment>
<ChildOne props1={stateVariable1} props2={stateVariable2} />
<ChildTwo props3={stateVariable3} />
</Fragment>
)}
</App>{' '}
</React.StrictMode>,
document.getElementById('root')
);
Is there a way to deconstruct this so that I don't need to pass each argument into the callback? I am using functional components so state is stored in multiple variables rather than within the class components' state object.

I think you can avoid having to use props.children altogether.
If you have a structure like below, your SRP principle will still hold true.
App
Child 1
Child 2
What the above lines mean is that rather than going with render props pattern or having props.children, you can have your Child1 and Child2 as components within your App component.
Something like
const App = (props) => {
return (
<>
<Child1 {...props} />
<Child2 {...props} />
</>
);
}
If you want to make code more generic and have a single Children component, you can extract all children out to index.js file so that your App component remains untouched, but that might not be needed to be honest.

I solved my issue with some help from Tyblitz.
ReactDOM.render(
<React.StrictMode>
<App>
{({stateVar1, stateVar2, ...stateVars}) => (
<Fragment>
<ChildOne props1={stateVar1} props2={stateVar2} />
<ChildTwo {...stateVars} />
</Fragment>
)}
</App>{' '}
</React.StrictMode>,
document.getElementById('root')
);
I then added a useState() function to my App component and passed all state variables in as an object.
Finally, I passed this new state variable in as an argument to my return call:
return <div className='App'>{props.children(stateVars)}</div>;

Related

Matched leaf route at location "/" does not have an element react-router [duplicate]

Here is the code screenshot.
I want to render Homepage component but I want to wrap it into these MainLayout component.
The problem is that screen is blank and there is no error in Terminal but when I inspect the page it says "Matched leaf route at location "/" does not have an element", so guys I know this is version update syntax problem because I had same problem when I was writing component= {component } but syntax has been changed and I should have written element={<component />}.
So I believe this is also syntax problem but I've done research but couldn't solve. I believe I should change this
/* ... */ render = {() => (
<MainLayout>
<Homepage />
</MainLayout>
)}
somewhat into newer syntax but don't know how.
The Route components in react-router-dom v6 no longer take component or render props; the routed components are rendered on the element prop as JSX.
<Routes>
...
<Route
path="/"
element={(
<MainLayout>
<Homepage />
</MainLayout>
)}
/>
...
</Routes>

How do you combine/compose multiple Higher Order Components (react.js)?

I've been looking into the concept of HOC's, but I'm not sure how to have multiple without just having an ugly deep nesting.
For example, if I wanted to add a library's HOC, like Redux provider or Apollo provider, then have my own HOC's and so on. Like below, is this not just really inefficient and ugly?
(Just an example, not actually using doing this at the moment. All theoretical.)
ReactDOM.render(
<ApolloProvider client={ client }>
<ReduxProvider state={ state }>
<MyHOC xyz={ abc }>
<App />
</MyHOC>
</ReduxProvider>
</ApolloProvider>,
document.getElementById('root')
)
As compared to
ReactDOM.render(
<CombinedHOCs client={ client } state={ state } xyz={ abc }>
<App />
</CombinedHOCs>,
document.getElementById('root')
)
But the only way I can think of this is looping through supplied HOC's which would again still have to loop (e.g. map) through everything (depending on what HOC does).
So am I looking at this the wrong way or is there no way to avoid this, assuming that those HOC's would be needed for the given component?
I think it's ok to nest HOC like you did in the example. But if you're using redux and Apollo you should integrate it and keep data in one store. You'll get rid of one nesting and this will let you better track the different events that happen in your app.
ReactDOM.render(
<ApolloProvider store={store} client={client}>
<MyHOC xyz={abc}>
<App />
</MyHOC>
</ApolloProvider>,
document.getElementById('root')
)

location missing in props (ReactJS)

I'm kind of new to reactjs and I'm learning step by step. I'm facing a problem and that is when I try to access the location parameter in the props it returns me undefined. I tried to find the solution but most of the people have written I have to add my component in the router but I'm wrapping it in the router but still, I don't have access to location parameter
export const abcdContainer = withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(abcd));
I'm trying to access the location parameter in the and component but the problem is there is no location parameter in it. Can somebody tell me what is it that is going wrong
Please if anybody know what is wrong please tell me I have spent half of my day and I can't figure it out
CODE AND VERSION ADDED WITH URL
Router version => 2.8.1
URL : http://localhost:3000/somePage?someParam=cm9oYW5qYWxpbHRlYWNoZXJAZ21haWwuY29t
abcdContainer.js
const mapStateToProps = (state, ownProps) => {
// some code over here
}
const mapDispatchToProps = (dispatch, ownProps) => {
// some code over here
};
export const abcdContainer = withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(abcd));
abcd.jsx
class abcd extends Component {
constructor(props, context) {
super(props, context);
this.state = {
// setting state over here
};
}
abcdFunction(){
if (this.props.location.query.someParam !== undefined){ // trying to extract someParam value from location
// some code over here
}
}
render() {
// some code over here
}
}
export default CSSModules(abcd, styles, { allowMultiple: true });
Here is the flow. The router redirect to the container and then the container redirect to the real component
Route.js
export const Routes = ({ store }) => (
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={aContainer}>
<IndexRoute component={IContainer} />
// some routes
<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
// some routes
</Route>
// some routes
</Router>
</Provider>
);
Routes.propTypes = {
store: React.PropTypes.object.isRequired,
};
<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
If you use an inline arrow function to render your component why you don't just pass the props directly to the component? Then you will not need withRouter(). Like this:
<Route path="/openAbcd" component={props => (<abcdContainer caller="aaaaaaa" {...props} />)} />
Also the docs of react-router v2.8.1 says about withRouter():
A HoC (higher-order component) that wraps another component to provide props.router.
It doesn't provide location but router as a prop. I recommend you to update react-router to v4 or at least v3.
EDIT: "But why were the props not being inserted implicitly?":
React knows two types of components. Stateless functional components and class-based components. Functional components are functions that accept a single props object argument with data and return a React element. Take a look at this line of your code again:
<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
You passed an arrow function () => (<abcdContainer caller="aaaaaaa" />) to your <Route> element which is an inline definition of a functional component that takes props as a parameter and returns a rendered React element, in this case this is your <abcdContainer>. But as you can see you omitted the props parameter in your function by defining it with empty parenthesis: () => (<AComponent />). React does not automatically pass props to child components that are rendered inside a functional component. When wrapping your <abcdContainer> into an inline functional component you are responsible for passing props to it yourself.
But if you pass the class/variable of your class-based/functional component to the component prop of your <Route> element like you did it in your other routes then it will render this component an implicitly pass props to it because it isn't wrapped in a functional component:
// this will directly render <aContainer> and pass the props to it
<Route path="/" component={aContainer}>
What you did is creating a "functional unnamed wrapper component" that doesn't take any props and also doesn't pass any props further down.
And note that you should not use withRouter() extensively. This HOC is only there to inject a router into components that do not get rendered by a matching route itself and are e.g. much deeper down your component tree. In your case you do not need it.

Apollo + React Router 4 SSR issue

Not sure if this is an issue with React Router v4, the React Apollo client or my implementation.
But with <ApolloProvider> as the top-level HOC, i.e:
const ComponentsWithData = await getDataFromTree(
<ApolloProvider client={apolloClient}>
<StaticRouter location={ctx.request.url} context={route}>
<App />
</StaticRouter>,
</ApolloProvider>,
);
const html = ReactDOMServer.renderToString(ComponentsWithData);
... I get:
Warning: Failed prop type: Invalid prop children of type array supplied to ApolloProvider, expected a single ReactElement.
in ApolloProvider
Error React.Children.only expected to receive a single React element child.
And flipped around, with React Router's <StaticRouter> as the top, i.e.:
const ComponentsWithData = await getDataFromTree(
<ApolloProvider client={apolloClient}>
<StaticRouter location={ctx.request.url} context={route}>
<App />
</StaticRouter>,
</ApolloProvider>,
);
... I then get:
A <Router> may have only one child element
Rendering works fine in the browser (with React Router's <BrowserRouter>), but fails on the server.
It also works well in React Router v3 due to doing all of the route matching outside of the React hierarchy, and not declaratively inside of it.
This was actually a user error. I expected getDataFromTree() to return a Promise that resolved to the original component chain (with the correctly injected GraphQL data props), but it actually just waits for the data to be ready.
The correct format is:
const Components = (
<StaticRouter location={ctx.request.url} context={route}>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</StaticRouter>
);
await getDataFromTree(Components);
const html = ReactDOMServer.renderToString(Components);

Multiple child routes in React-router

I am trying to implement a router where a Route has two parallel/independent sub-routes and I wonder if this is possible with react-router. The image below illustrates what I want to achieve. It represents an event-editor with two components: (1) a list of events (2) an editor for one event. I think their states should both be part of the URL somehow.
One extra constraint: [child1] should be aware of the state of [child2], since it needs to show the item that is currently edited in a different color or so.
If this is not possible with react-router, how would you implement this? Thanks!
Look this. By doing this you can create a parallel control of routes, without harming root.
/**at index.html*/
< div id="root"></div>
< div id="helper"></div>
/**at index.js*/
ReactDOM.render(
(
<BrowserRouter>
<App/>
</BrowserRouter>
),
document.getElementById('root')
);
ReactDOM.render(
(
<BrowserRouter>
<Helper />
</BrowserRouter>
),
document.getElementById('helper')
);
/**at Component Helper*/
render() {
const location = { pathname: '/2' }
return (<Switch location={location}>
<Route exact path='/2' component={Welcome} />
</Switch>);
}
const Welcome = () => <h3>welcome helper</h3>
//I'm using router V4 to switch of the routers.

Categories

Resources