List props of a nested react component - javascript

I want to enumerate children props of a nested component without passing them over.
Let's take a look at this example (pseudo code)
# JSX
<Root>
<NodeWrapper />
</Root>
# NodeWrapper component
function NodeWrapper() {
return <InnerNode myPropName="myPropValue" />
}
# Root component
function Root({children}) {
// children.props > lists all NodeWrapper props
// how to get a hold of InnerNode props, so that Root can detect prop `myPropName`?
}
The only way I found so far is to pass myPropName to NodeWrapper. Is there a way to grab myPropName value from within Root component without passing it down from Root to InnerNode through NodeWrapper?
I understand InnerNode will be available only when NodeWrapper is rendered, that is not the case as Root is being rendered and InnerNode is not rendered yet (i.e., it is a component and not yet an instance).
I think this question hides some React concept I am missing.
EDIT: Please note that my question is not to avoid prop drilling. Prop drilling and contexts are techniques to pass data down the component tree. What I want to do is quite the opposite: read a nested component props from the Root. The usage of Root.children gives me only NodeWrapper props, but I do actually would like to get InnerNode props from within Root component.

I think you are trying to avoid props drilling that is passing props to children where it is not directly needed but it is needed inside some nested component. For this I would recommend to use Context it is great way to avoid prop drilling here how you can configure it
import './App.css';
import { useContext } from 'react';
// In the login Component
const InnerComponent = () => {
const authContext = useContext(MyContext);
const handleLogin = () => {
authContext.onAuthChange(true); // this will make the user login that change the value of auth to true
}
return (
<div>Login JSX</div>
)
}
const MyContext = React.createContext(null);
const NodeWrapper = () => <InnerComponent />
function App() {
const [auth, setAuth] = React.useState(true);
const handleAuthChange = (newAuthState) => {
setAuth(newAuthState);
}
return (
<MyContext.Provider value={{
auth,
onAuthChange: handleAuthChange
}}>
<NodeWrapper />
</MyContext.Provider>
);
}
export default App;

Related

Props are not passing from parent to child components in React Functional component

Hi developers I'm just a beginner in React.js. I tried to print props by passing from parent to child.
This is app.js file
import React from "react";
import Hooks from "./components/ReactHooks1";
import Hooks2 from "./components/ReactHooks2";
const App = () => {
return (
<div>
<h1>
Welcome to React App
</h1>
<Hooks2 title2={"Welcome"}/>
</div>
)
}
export default App
This is child component file
import React from 'react';
const Hooks2 = (props) => {
console.log(props);
}
export default Hooks2;
I just try to print props but it shows an empty object. what am I doing wrong please help me on this
You should return something or null to parent component from child, when you're using it in parent component. This will solve your problem
export const Hooks2 = (props) => {
console.log(props);
return <></>;
}
#Rasith
Not sure why would you want to do this, but if you're trying to pass a child component that would print something to the console. In this case you need to destructure the component's props. Here's an article about it from MDN.
This is how I would do it:
const CustomComponent = ({title}) => {
console.log(title)
}
const App = () => {
return (
<>
<h1>Hello World</h1>
<CustomComponent title={"Welcome"}/>
</>
);
};
For the title to be printed to the console, no need to add a return statement to the child component. Again, not sure why you would do this, but there you go.
Well trying to console.log title certainly would not work because what you are passing is called title2. Also your child component is not returning anything.
First, you have to return anything from your child component( even a fragment )
You can access title2 in the child component with any of these methods:
1- using props object itself
const Hooks2 = (props) => {
console.log(props.title2);
return;
}
2- you can also destructure props in place to access title2 directly
const Hooks2 = ({title2}) => {
console.log(title2);
return ;
}
You have to use destructuring in your ChildComponent, to grab your props directly by name:
const Hooks2 = ({title2}) => {
console.log(title2);
}
You can read a little bit more about it in here: https://www.amitmerchant.com/always-destructure-your-component-props-in-react/

How to access particular child state from parent with multiple children?

Let's say I have a parent component in React with 3 separate child components of the same component class (meaning, within class Parent I have 3 Child components). How do I access each child's state within the Parent component?
My initial thoughts are to have a separate variable for each Child's state that I want to access (I only want to access the filled variable for each child). But I feel that this is certainly a sloppy solution to something that is already in place with React, so would appreciate any pointers.
Example code below for illustration purposes.
const Parent = (props) => {
return (
<div>
<Child/>
<Child/>
<Child/>
</div>
);
}
const Child = (props) => {
const [filled, setFilled] = useState(false);
return (
<div></div>
);
}
Perhaps the better question is how do I access the particular child? And once accessed, how do I access its filled state (callback function)? I've read about useRef, is that where I should be looking here?
If what you are trying to do is report back to the parent the childs's state, you can do that by passing down from the parent, via props, a function to report that state back, as such:
const Parent = (props) => {
const reportChildState = (value) =>{
//do something with the child filled state
}
return (
<div>
<Child reportState={reportChildState}/>
<Child reportState={reportChildState}/>
<Child reportState={reportChildState}/>
</div>
);
}
const Child = (props) => {
//in here you can call props.reportChildState(filled)
const [filled, setFilled] = useState(false);
return (
<div></div>
);
}
As far as I'm aware there isnt a way to access a child components state from the parent. The only solutions are to either pass the state object into the child as a prop, using the context API or using a thirst party state management such as redux.
I wouldnt use useref for accessing a child's state.
You actually wouldn't directly access the children to retrieve their states. What you want here is a Context that encompases the parent component. Check out the React Context API. In short, you can create a "context" that contains states that need to be shared between multiple components. Once the context is made, a Provider is also created with that context. This provider is a component that accepts a value prop. This prop contains an object of all the state values and setter functions within the context. Child components of a Provider component can use the useContext hook to retrieve the values and functions from the value prop of the Provider.
Code example:
MyContext.js
import React, {createContext, useState} from 'react';
// Create the context and give it default values
export const MyContext = createContext(defaultValuesObject);
// We create a component that wraps around the provider, and is stateful. The states and their setters are placed into the provider, which is then returned.
const MyProvider = (props) => {
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const dataToShare = {
firstName,
setFirstName,
lastName,
setLastName,
}
// Return the context provider with the data already inside, and fill the children.
return (
<MyContext.Provider value={dataToShare}>
{props.children}
</MyContext.Provider>
)
}
We have the provider now. In your parent component's code, use the useContext hook to give the component access to the values and functions stored inside the provider component.
MyParentComponent.jsx
import React, {useContext} from 'react';
import MyProvider, { MyContext } from 'MyContext'
const MyComponent = (props) => {
// useContext returns the value stored in the provider so we can use it and the functions inside. The context is maintained inside the states in the provider.
const providerValue = useContext(MyContext)
// OR
const { firstName, setFirstName, lastName, setLastName } = useContext(MyContext)
return (
<div>
// Provide the first child component with the values and functions from the context
<MyChildComponentOne someProp={providerValue} />
// Provide the second child component with the values and functions from the context
<MyChildComponentTwo someProp={providerValue} />
// Provide the second third component with the values and functions from the context
<MyChildComponentThree someProp={providerValue} />
<div/>
)
}
Now, we still won't be able to use the context without a provider, which MUST wrap around the component(s) calling useContext for that specific context. Assuming the ParentComponent is used inside of App.jsx:
App.jsx
...imports and whatever other code you have in here
/// The jsx for the App component or whatever component calls MyParentComponent
return <div>
<MyProvider>
<MyParentComponent>
</MyProvider>
</div>
To re-iterate, you are taking the state OUT of the child/parent components, and putting them INTO the Provider created by the Context. Child components of the Provider can call useContext and gain access to the data and functions in the Provider's value prop.
There is no direct way to pass information from the child to the parent, only the other way around.
But that means you can also pass functions from the parent to the child! One common pattern for achieving what you need would be to pass the state set function to the child, so it can alter the parent's state. Like so:
const Parent = (props) => {
const [childStates, setChildStates] = useState({ child1: '', child2: '', child3: '' })
return (
<div>
<Child
state={childStates.child1}
setState={(val) => setChildStates((prev) => ({ ...prev, child1: val }))}
/>
<Child
state={childStates.child2}
setState={(val) => setChildStates((prev) => ({ ...prev, child2: val }))}
/>
<Child
state={childStates.child3}
setState={(val) => setChildStates((prev) => ({...prev, child3: val }))}
/>
</div>
);
}

Pass Props to Outlet in React Router v6

I can't manage to pass props to my Outlet components in the new react-router v6. I tried the straightforward solution:
render() {
return (
<Outlet name="My name" />
);
}
And that correctly renders the child component, however no props are passed to the child. None of the examples provided by the React team (or anyone else for that matter) display Outlets with props, so I'm worried it's not actually a thing. Is there another way I'm not finding or am I using Output components incorrectly?
Edit: Seems there's no straightforward way to pass props, see answer below.
You can do it with
outlet context
This is now possible (from version 6.1.0) with the context prop
<Outlet context={}/>
github issue
react router outlet docs
An alternative option here is to use Context API to share props from your parent view to your child view.
const Context = React.createContext({})
function ParentView () {
const outlet = useOutlet()
return (
<Context.Provider value={{ foo: 'bar' }}>
<h1>Parent View</h1>
{outlet}
</Context.Provider>
)
}
function ChildView () {
const props = React.useContext(Context)
return (
<div>child view {props.foo}</div>
)
}
Another option (untested) may be to use React.cloneElement to clone outlet and add props to it.
When using functional component declare the name in the parent component like this.
function Parent() {
const const name='Your name'
return <Outlet context={[name]} />;
}
Then in the child component do this
//import this
import { useOutletContext } from "react-router-dom";
function Child() {
const [name] = useOutletContext();
return <p >{name}</p>;
}
one way i did it and it works well is to create a reach context, if you know how to use react context, this will be easy for you.
In a separate file create Context.js to prevent require loop
const AdminStoreContext = React.createContext();
and then export it
export{AdminStoreContext}
then in another file create a consumer and provider of the context, and then import the context you've creates
import { AdminStoreContext } from "../../contexts";
class AdminStoreContextProvider extends React.Component {
constructor(props) {
super(props);
this.state = { ===vlaues you want to share }
}
render() {
return (
<AdminStoreContext.Provider
value={{
...this.state,//===spread the value you want to share
}}>
{
this.props.children
}
</AdminStoreContext.Provider>
);
}
}
const AdminStoreContextConsumer = AdminStoreContext.Consumer;
export { AdminStoreContextConsumer, AdminStoreContextProvider }
you can wrap your app with the context
<AdminStoreContextProvider>
<app/>
<AdminStoreContextProvider />
you can use either the consumer or the context to get the values for the purpose of Outlet, we use the context
once again import it
import { AdminStoreContext } from "../../contexts";
const route[{
path: 'consumer',
element: <MyMainComponent AdminStoreContext ={AdminStoreContext } />,
children: [
{ path: 'account', element: <MySubComponent1 /> },
{ path: 'purchaseHistory', element: <MySubComponent2 /> }
]
},
then in your MySubComponent1 or MySubComponent2
get the value from the props and use
const { AdminStoreContext } = props;
const context = React.useContext(AdminStoreContext )
and from the context you an get your values, hope this is helpfull
context.//get any value you put on the state
Unfortunately after digging for a while it looks like there's no straightforward way to do this and no plans to change it (at least for now), based on this GitHub issue's response https://github.com/ReactTraining/react-router/issues/7495.
You have to define where you want to use the name prop when defining the Outlet component
const outlet = ( props ) => {
return (
<h1>{props.name}</h1>
);
};

React memo keeps rendering when props have not changed

I have a stateless functional component which has no props and populates content from React context. For reference, my app uses NextJS and is an Isomorphic App. I'm trying to use React.memo() for the first time on this component but it keeps re-rendering on client side page change, despite the props and context not changing. I know this due to my placement of a console log.
A brief example of my component is:
const Footer = React.memo(() => {
const globalSettings = useContext(GlobalSettingsContext);
console.log('Should only see this once');
return (
<div>
{globalSettings.footerTitle}
</div>
);
});
I've even tried passing the second parameter with no luck:
const Footer = React.memo(() => {
...
}, () => true);
Any ideas what's going wrong here?
EDIT:
Usage of the context provider in _app.js looks like this:
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
...
return { globalSettings };
}
render() {
return (
<Container>
<GlobalSettingsProvider settings={this.props.globalSettings}>
...
</GlobalSettingsProvider>
</Container>
);
}
}
The actual GlobalSettingsContext file looks like this:
class GlobalSettingsProvider extends Component {
constructor(props) {
super(props);
const { settings } = this.props;
this.state = { value: settings };
}
render() {
return (
<Provider value={this.state.value}>
{this.props.children}
</Provider>
);
}
}
export default GlobalSettingsContext;
export { GlobalSettingsConsumer, GlobalSettingsProvider };
The problem is coming from useContext. Whenever any value changes in your context, the component will re-render regardless of whether the value you're using has changed.
The solution is to create a HOC (i.e. withMyContext()) like so;
// MyContext.jsx
// exported for when you really want to use useContext();
export const MyContext = React.createContext();
// Provides values to the consumer
export function MyContextProvider(props){
const [state, setState] = React.useState();
const [otherValue, setOtherValue] = React.useState();
return <MyContext.Provider value={{state, setState, otherValue, setOtherValue}} {...props} />
}
// HOC that provides the value to the component passed.
export function withMyContext(Component){
<MyContext.Consumer>{(value) => <Component {...value} />}</MyContext.Consumer>
}
// MyComponent.jsx
const MyComponent = ({state}) => {
// do something with state
}
// compares stringified state to determine whether to render or not. This is
// specific to this component because we only care about when state changes,
// not otherValue
const areEqual = ({state:prev}, {state:next}) =>
JSON.stringify(prev) !== JSON.stringify(next)
// wraps the context and memo and will prevent unnecessary
// re-renders when otherValue changes in MyContext.
export default React.memo(withMyContext(MyComponent), areEqual)
Passing context as props instead of using it within render allows us to isolate the changing values we actually care about using areEqual. There's no way to make this comparison during render within useContext.
I would be a huge advocate for having a selector as a second argument similar to react-redux's new hooks useSelector. This would allow us to do something like
const state = useContext(MyContext, ({state}) => state);
Who's return value would only change when state changes, not the entire context.
But I'm just a dreamer.
This is probably the biggest argument I have right now for using react-redux over hooks for simple apps.

Why can't I curry a react component?

I've been getting started with react-redux and finding it a very interesting way to simplify the front end code for an application using many objects that it acquires from a back end service where the objects need to be updated on the front end in approximately real time.
Using a container class largely automates the watching (which updates the objects in the store when they change). Here's an example:
const MethodListContainer = React.createClass({
render(){
return <MethodList {...this.props} />},
componentDidMount(){
this.fetchAndWatch('/list/method')},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(oId){
this.props.fetchObject(oId).then((obj) => {
this._unwatch = this.props.watchObject(oId);
return obj})}});
In trying to supply the rest of the application with as simple and clear separation as possible, I tried to supply an alternative 'connect' which would automatically supply an appropriate container thus:
const connect = (mapStateToProps, watchObjectId) => (component) => {
const ContainerComponent = React.createClass({
render(){
return <component {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
this.props.fetchObject(watchObjectId).then((obj) => {
this._unwatch = this.props.watchObject(watchObjectId);
return obj})}
});
return reduxConnect(mapStateToProps, actions)(ContainerComponent)
};
This is then used thus:
module.exports = connect(mapStateToProps, '/list/method')(MethodList)
However, component does not get rendered. The container is rendered except that the component does not get instantiated or rendered. The component renders (and updates) as expected if I don't pass it as a parameter and reference it directly instead.
No errors or warnings are generated.
What am I doing wrong?
This is my workaround rather than an explanation for the error:
In connect_obj.js:
"use strict";
import React from 'react';
import {connect} from 'react-redux';
import {actions} from 'redux/main';
import {gets} from 'redux/main';
import {isFunction, omit} from 'lodash';
/*
A connected wrapper that expects an oId property for an object it can get in the store.
It fetches the object and places it on the 'obj' property for its children (this prop will start as null
because the fetch is async). It also ensures that the object is watched while the children are mounted.
*/
const mapStateToProps = (state, ownProps) => ({obj: gets.getObject(state, ownProps.oId)});
function connectObj(Wrapped){
const HOC = React.createClass({
render(){
return <Wrapped {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
const {fetchObject, watchObject, oId} = this.props;
fetchObject(oId).then((obj) => {
this._unwatch = watchObject(oId);
return obj})}});
return connect(mapStateToProps, actions)(HOC)}
export default connectObj;
Then I can use it anywhere thus:
"use strict";
import React from 'react';
import connectObj from 'redux/connect_obj';
const Method = connectObj(React.createClass({
render(){
const {obj, oId} = this.props;
return (obj) ? <p>{obj.id}: {obj.name}/{obj.function}</p> : <p>Fetching {oId}</p>}}));
So connectObj achieves my goal of creating a project wide replacement for setting up the connect explicitly along with a container component to watch/unwatch the objects. This saves quite a lot of boiler plate and gives us a single place to maintain the setup and connection of the store to the components whose job is just to present the objects that may change over time (through updates from the service).
I still don't understand why my first attempt does not work and this workaround does not support injecting other state props (as all the actions are available there is no need to worry about the dispatches).
Try using a different variable name for the component parameter.
const connect = (mapStateToProps, watchObjectId) => (MyComponent) => {
const ContainerComponent = React.createClass({
render() {
return <MyComponent {...this.props} obj={this.state.obj} />
}
...
fetchAndWatch() {
fetchObject(watchObjectId).then(obj => {
this._unwatch = watchObject(watchObjectId);
this.setState({obj});
})
}
});
...
}
I think the problem might be because the component is in lower case (<component {...this.props} />). JSX treats lowercase elements as DOM element and capitalized as React element.
Edit:
If you need to access the obj data, you'll have to pass it as props to the component. Updated the code snippet

Categories