In react, if I have a component that looks something along the lines of this:
const SomeComponent = (props) => {
return stuff
}
and I pass in props like this:
<SomeComponent foo={"x"}/>
How can I get the props object name of foo from the props within the component?
For example, so that I can use it in a function like:
const useObjectName = (xyz) => {
if (xyz){
//do something
}
}
const SomeComponent = (props) => {
const theObjectName = ???
useObjectName(theObjectName)
return stuff
}
You should be able to do that using Object.keys(props) which will return ["foo"].
Related
I am passing props to a functional component, but I keep getting an error:
const renderItem = ({ item }) => (
<CommentCellClass
key={item.key}
commentLikes={item.commentLikes}
.... more props
I try and access them in the CommentCellClass component:
const CommentCellClass = ({ props, navigation }) => {
const { key, commentLikes } = props;
But I get the following error:
TypeError: undefined is not an object (evaluating 'props.key')]
What am I doing wrong? The props are not null (I checked before I passed them to commentCellClass)
Sorry for the confusing name (CommentCellClass is a functional component). We are in the process of converting the class components to functional components in our app.
Where does navigation come from? I would expect your code to look like this:
const CommentCellClass = (props) => {
const { key, commentLikes } = props;
...
}
or
const CommentCellClass = ({ key, commentLikes }) => { ... }
I'm trying to implement my own custom way of passing props down to a component.
What I am trying to achieve is something similar to the connect()(Component) method, but with my own little twist.
Say I have a custom component, such as,
// presentationalComponent.js
const PresentationalComponent = ({
name,
loading,
}) => {
return (
<div>
This component is: {name}. The current state is: {loading}
</div>
)
}
and an Error component, such as,
// error.js
const Error = ({errors}) => {
return (
errors.forEach(error => {
<div>{error}</div>
})
)
}
I would want to wrap every component with a morph method, which would grab the requested state and also return a parent component.
So if I was to do,
const PresentationalComponent = ({ . . .
. . .
}
export default morph('layout', ['loading'])(PresentationalComponent)
I would want the morph method to return something like,
const getState = (reducer, values) =>
// lodash
_.pick(
useSelector((state) => state[reducer]),
values,
)
const ParentContainer = ({Component, requestedState: {reducer, fields}}) => {
const errors = useSelector(({ errors }) => errors)
const state = getState(reducer, fields)
if (errors.length) return <Error errors={errors} />
return <Component state={state} />
}
But I'm not sure how to go about this, because hooks like useSelector can only be used in functional components. I'm not sure how connect from Redux makes it work.
Ideally, if I was to do,
const Home = ({ state }) => {
<PresentationalComponent name='My presentational component!' />;
};
I would want to see This component is: My presentational component!. The current state is: false.
How can I achieve this with exporting my components such as morph('reducer', ['state'])(Component)?
I've tried something like,
const morph = (reducer, state) => (Component) => {
console.log('reducer', reducer);
console.log('state', state);
return Component;
};
export default morph;
But that didn't work.
import React, { useEffect, useState } from 'react';
import { Card } from 'components/Card';
import { dateFilter } from 'helpers';
import Chart from 'chart.js';
import 'chartjs-chart-matrix';
import chroma from 'chroma-js';
import moment from 'moment';
const WeeklyTrafficCard = (props) => {
const { start, end, data, store } = props;
const capacity = store && store.capacity;
var numberOfweeks = 0; //representing how many weeks back
const dateArray = [];
var today = moment();
while (numberOfweeks < 10) {
var from_date = today.startOf('week').format('MM/DD/YY');
var to_date = today.endOf('week').format('MM/DD/YY');
var range = from_date.concat(' ','-',' ',to_date);
dateArray.push(range);
today = today.subtract(7, 'days');
numberOfweeks++;
//console.log(dateArray);
}
const [each_daterange, setDateRange] = useState();
I have this Component called WeeklyTrafficCard and I want to use the variable, each_daterange, in another component, which imported WeeklyTrafficCard as below to send the get request, clearly I cannot use each_daterange directly right here, how I can work around it?
import React, { useContext, useEffect, useState } from 'react';
import { WeeklyTrafficCard } from './WeeklyTrafficCard';
import { AppContext } from 'contexts/App';
import { API_URL } from 'constants/index.js';
import { todayOpen, todayClose } from 'helpers';
import moment from 'moment';
const WeeklyTrafficCardContainer = (props) => {
const { API } = useContext(AppContext);
const { store = {} } = props;
const [data, setData] = useState([]);
const open = todayOpen(store.hours, store.timezone);
const close = todayClose(store.hours, store.timezone);
useEffect(() => {
async function fetchData() {
const result = await API.get(`${API_URL}/api/aggregates`, {
params: {
each_daterange,
every: '1h',
hourStart: 13,
hourStop: 4
},
});
You should use a useEffect(prop drilling) to pass your variable in your parent:
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const [myVar, setMyVar] = React.useState('');
return (
<div>
<Child setMyVar={setMyVar} />
{myVar}
</div>
);
};
const Child = ({setMyVar}) => {
const myChildVar = "Hello world !"
React.useEffect( () => setMyVar(myChildVar),[]);
return <div> This is the child</div>
}
render(<App />, document.getElementById("root"));
Here is the repro on stackblitz
Understanding of the Problem
You want to pass data up to the parent from the child.
Manage each_daterange in the parent:
Instead of creating your useState variable each_daterange in the child you can declare it in the parent and pass down it's setter function. For instance:
const WeeklyTrafficCardContainer = (props) => {
const [eachDateRange, setEachDateRange] = useState();
return (
<div>
{/* your return */}
<WeeklyTrafficCard setEachDateRange={setEachDateRange} />
</div>
)
}
If you need to display eachDateRange in the traffic card, or the traffic card needs to completely own that variable, you can create another state variable in the parent and pass a callback to the child (essentially what is above but now you have two different state variables).
The parent becomes
const WeeklyTrafficCardContainer = (props) => {
const [requestDateRange, setRequestDateRange] = useState();
const updateRequestDateRange = (dateRange) => {
setRequestDateRange(dateRange)
}
return (
<div>
{/* your return */}
<WeeklyTrafficCard updateDateRange={updateRequestDateRange} />
</div>
)
}
Then in your WeeklyTrafficCard call props.updateDateRange and pass it the date range whenever each_daterange changes.
Ciao, of course you need a global state manager. My preferred is react-redux. In few word, react-redux allows you to have a state that is shared in all your components. Sharing each_daterange between WeeklyTrafficCardContainer and WeeklyTrafficCard will be very easy if you decide to use it.
This is the more appropriate guide to quick start with react-redux. have a nice coding :)
Keep the value outside of the component, where both can access it. There are other ways to do this, but just as a simple example you could create a simple "store" to hold it and reference that store from each component that needs it:
class Store {
setDateRange (newDateRange) {
this._dateRange = newDateRange;
}
get dateRange () {
return this._dateRange;
}
}
export default new Store(); // singleton; everyone gets the same instance
import store from './Store';
const WeeklyTrafficCard = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
import store from './Store';
const WeeklyTrafficCardContainer = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
If you want store updates to trigger component re-renders you'd need to add some higher order component plumbing, like redux's connect, or some other mechanism for triggering updates:
// pseudocode; make store an event emitter and return
// a component that re-renders on store events
store.connect = Component => {
return props => {
React.useEffect(() => {
store.addEventListener( ... )
return () => store.removeEventListener( ... )
})
}
}
Or if the components share a common parent, you could lift the state to the parent and pass the information to each component as props. If either component updates the value, the parent state change will trigger a re-render of both components with the new value:
const Parent = () => {
const [dateRange, setDateRange] = React.useState();
return (
<>
<WeeklyTrafficCardContainer
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
<WeeklyTrafficCard
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
</>
);
}
Let's rephrase the objective here.
Objective: access each_daterange from WeeklyTrafficCard component in WeeklyTrafficCardContainer component.
Note: simply put, choose the following case based on your problem.
choose using prop if the variable is to be accessed by only one component
choose using context if the variable is to be accessed by more than one components
Solution Cases:
Case A: using prop.
Case A.1. WeeklyTrafficCard is the parent of WeeklyTrafficCardContainer
each_datarange being passed from WeeklyTrafficCard component as prop to WeeklyTrafficCardContainer component
working example for reference: codesandbox - variable passed as prop
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case A.2. WeeklyTrafficCard & WeeklyTrafficCardContainer are children of a parent, say WeeklyTraffic component
each_datarange will be present in WeeklyTraffic component which is shared among WeeklyTrafficCard component & WeeklyTrafficCardContainer component
// WeeklyTraffic.jsx file
const WeeklyTraffic = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCard eachDateRange={each_daterange} />
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case B: using context.
follow blog example found: blog - react context
this is preferred way to implement if the variable/variables is/are shared or need to be accessed by more than 1 components
I've created a validation function that I can call externally like so:
const isValid = validateChildren(this.props.children)
And I have a component I'd like to validate.
class MyComponent extends React.Component {
constructor(props) {
super(props)
}
isValid() {
// Validation will check against the render method in this component.
return true;
}
render() {
return false;
}
}
Within that function I'm using the component props to check for a validation function using React.Children. This looks something like this:
React.Children.map(children, (child) => {
// Validation here.
});
What I'd like to do in addition to checking for props, is to check for a internal class method of isValid and then fire it. That way in the case of MyComponent I could do the following:
if (child.current.isValid) child.current.isValid()
Is something like this possible in React? I'm trying to solve a performance issue with cloning the child elements that I'd like to avoid with this approach.
You can do this using forwardRef and the useImperativeHandle hook, as described here.
If you change the name in the App function, you'll see the validity change.
import React, { useState, useImperativeHandle, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
const validateNameProp = nameProp => {
return nameProp === "Colin";
};
let Child = ({ name, childRef }) => {
const [nameIsValid, setNameIsValid] = useState(false);
// We want to expose the isValid function so it can be called by parent.
useImperativeHandle(childRef, () => ({
isValid
}));
const isValid = () => {
setNameIsValid(true);
};
return (
<div ref={childRef}>
<h1>
Name is {name} and this name is: {nameIsValid ? "valid" : "invalid"}
</h1>
</div>
);
};
const App = () => {
const childRef = useRef();
const name = "Colin";
// Wait until component mounts so ref is not null.
useEffect(() => {
if (validateNameProp(name)) {
childRef.current.isValid();
}
}, []);
return <Child childRef={childRef} name={name} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I have a React component which calls a function, and i need to pass the injected mobx store and the components props into this function
const Component = inject("store")(observer(({store, props}) => {
return (
<div>
{_someRenderFunction(store, props)}
</div>
);
}));
It's used in the function like this
function _someRenderFunction(store, props) {
let data = store.data;
const { cookies } = props;
...
}
But i get the error Cannot read property 'cookies' of undefined
How can i pass both the store and component props to the _someRenderFunction?
The problem is this line:
({store, props}) => {};
Your component receives only props so the basic definition would be:
(props) => {}
Inject gives you your injected store inside the given props. So what you receive is:
const props = { store: STORE_INSTANCE }
With the object destructing you can extract properties from props. So this would also work:
({ store }) => {}
Here you are extracting the property store from your props. but in your example you are also extracting the property props from props. So in your case props should look like:
const props = { store: STORE_INSTANCE, props: PROPS_OBJECT }
This is not what you want. So object destructing is not what you want in this case. The following code should work:
const Component = inject("store")(observer((props) => {
return (
<div>
{_someRenderFunction(props)}
</div>
);
}));
function _someRenderFunction(props) {
const { cookies, store } = props;
let data = store.data;
...
}