Remove a prop from cloned React component - javascript

Goal: I need to perform some post-processing of React components, and that involves removing some props. I tried to use React.cloneElement passing {propToRemove: undefined} as the second argument, but the prop is not removed, just set to undefined. I could use React.createElement, but according to the docs, that would lose refs, which is a serious drawback.
A contrived example not doing anything useful, just to test:
const removeUnknownPropWithClone = (el) => {
return React.cloneElement(el, {unknownProp: undefined})
};
const App = (props) =>
removeUnknownPropWithClone(
<div unknownProp="1">Hello</div>
);
This shows the error message: "React does not recognize the unknownProp prop on a DOM element". Indeed, the prop is set to undefined, but it's still there. I need to completely remove it.
Runnable snippet (open the console to see the error messages)
Related question (but not answered there): React - Remove prop from child
Relevant source code: https://github.com/facebook/react/blob/master/packages/react/src/ReactElement.js#L293

Very simple with JSX (inside a Children.map() function):
const { filtered, ...rest } = child.props
const clone = <child.type key={child.key} ref={child.ref} {...rest} />

Sometimes it's good to look at sources ;)
cloneElement doesn't let remove prop - they are copied and overwritten. No option for deleting or passing function.
But looking a bit higher we can see:
export function cloneAndReplaceKey(oldElement, newKey) {
const newElement = ReactElement(
oldElement.type,
newKey,
oldElement.ref,
oldElement._self,
oldElement._source,
oldElement._owner,
oldElement.props,
);
return newElement;
}
Looks easy to extend but ReactElement isn't exported :]
... but it looks like ref, key and trimmed props can be copied/passed (by config) to createElement. Check if it will be sufficient.

We solved it this way
const { propToRemove, ...childProps } = child.props
const filteredChild = { ...child, props: childProps }
return React.cloneElement(filteredChild, additionalProps)

You could copy the element props to another object and delete the undesired prop in the new object
const removeUnknownPropWithClone = (el) => {
const props = { ...el.props }
delete props['unknownProps']
return React.cloneElement(el, props)
};

Related

Uncaught Error: Objects are not valid as a React child (found: object with keys {nombre, email}) [duplicate]

In my component's render function I have:
render() {
const items = ['EN', 'IT', 'FR', 'GR', 'RU'].map((item) => {
return (<li onClick={this.onItemClick.bind(this, item)} key={item}>{item}</li>);
});
return (
<div>
...
<ul>
{items}
</ul>
...
</div>
);
}
everything renders fine, however when clicking the <li> element I receive the following error:
Uncaught Error: Invariant Violation: Objects are not valid as a React
child (found: object with keys {dispatchConfig, dispatchMarker,
nativeEvent, target, currentTarget, type, eventPhase, bubbles,
cancelable, timeStamp, defaultPrevented, isTrusted, view, detail,
screenX, screenY, clientX, clientY, ctrlKey, shiftKey, altKey,
metaKey, getModifierState, button, buttons, relatedTarget, pageX,
pageY, isDefaultPrevented, isPropagationStopped, _dispatchListeners,
_dispatchIDs}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from
the React add-ons. Check the render method of Welcome.
If I change to this.onItemClick.bind(this, item) to (e) => onItemClick(e, item) inside the map function everything works as expected.
If someone could explain what I am doing wrong and explain why do I get this error, would be great
UPDATE 1:
onItemClick function is as follows and removing this.setState results in error disappearing.
onItemClick(e, item) {
this.setState({
lang: item,
});
}
But I cannot remove this line as I need to update state of this component
I was having this error and it turned out to be that I was unintentionally including an Object in my JSX code that I had expected to be a string value:
return (
<BreadcrumbItem href={routeString}>
{breadcrumbElement}
</BreadcrumbItem>
)
breadcrumbElement used to be a string but due to a refactor had become an Object. Unfortunately, React's error message didn't do a good job in pointing me to the line where the problem existed. I had to follow my stack trace all the way back up until I recognized the "props" being passed into a component and then I found the offending code.
You'll need to either reference a property of the object that is a string value or convert the Object to a string representation that is desirable. One option might be JSON.stringify if you actually want to see the contents of the Object.
So I got this error when trying to display the createdAt property which is a Date object. If you concatenate .toString() on the end like this, it will do the conversion and eliminate the error. Just posting this as a possible answer in case anyone else ran into the same problem:
{this.props.task.createdAt.toString()}
I just got the same error but due to a different mistake: I used double braces like:
{{count}}
to insert the value of count instead of the correct:
{count}
which the compiler presumably turned into {{count: count}}, i.e. trying to insert an Object as a React child.
Just thought I would add to this as I had the same problem today, turns out that it was because I was returning just the function, when I wrapped it in a <div> tag it started working, as below
renderGallery() {
const gallerySection = galleries.map((gallery, i) => {
return (
<div>
...
</div>
);
});
return (
{gallerySection}
);
}
The above caused the error. I fixed the problem by changing the return() section to:
return (
<div>
{gallerySection}
</div>
);
...or simply:
return gallerySection
React child(singular) should be type of primitive data type not object or it could be JSX tag(which is not in our case). Use Proptypes package in development to make sure validation happens.
Just a quick code snippet(JSX) comparision to represent you with idea :
Error : With object being passed into child
<div>
{/* item is object with user's name and its other details on it */}
{items.map((item, index) => {
return <div key={index}>
--item object invalid as react child--->>>{item}</div>;
})}
</div>
Without error : With object's property(which should be primitive, i.e. a string value or integer value) being passed into child.
<div>
{/* item is object with user's name and its other details on it */}
{items.map((item, index) => {
return <div key={index}>
--note the name property is primitive--->{item.name}</div>;
})}
</div>
TLDR; (From the source below) : Make sure all of the items you're rendering in JSX are primitives and not objects when using React. This error usually happens because a function involved in dispatching an event has been given an unexpected object type (i.e passing an object when you should be passing a string) or part of the JSX in your component is not referencing a primitive (i.e. this.props vs this.props.name).
Source - codingbismuth.com
Mine had to do with forgetting the curly braces around props being sent to a presentational component:
Before:
const TypeAheadInput = (name, options, onChange, value, error) => {
After
const TypeAheadInput = ({name, options, onChange, value, error}) => {
I too was getting this "Objects are not valid as a React child" error and for me the cause was due to calling an asynchronous function in my JSX. See below.
class App extends React.Component {
showHello = async () => {
const response = await someAPI.get("/api/endpoint");
// Even with response ignored in JSX below, this JSX is not immediately returned,
// causing "Objects are not valid as a React child" error.
return (<div>Hello!</div>);
}
render() {
return (
<div>
{this.showHello()}
</div>
);
}
}
What I learned is that asynchronous rendering is not supported in React. The React team is working on a solution as documented here.
Mine had to do with unnecessarily putting curly braces around a variable holding a HTML element inside the return statement of the render() function. This made React treat it as an object rather than an element.
render() {
let element = (
<div className="some-class">
<span>Some text</span>
</div>
);
return (
{element}
)
}
Once I removed the curly braces from the element, the error was gone, and the element was rendered correctly.
For anybody using Firebase with Android, this only breaks Android. My iOS emulation ignores it.
And as posted by Apoorv Bankey above.
Anything above Firebase V5.0.3, for Android, atm is a bust. Fix:
npm i --save firebase#5.0.3
Confirmed numerous times here
https://github.com/firebase/firebase-js-sdk/issues/871
I also have the same problem but my mistake is so stupid. I was trying to access object directly.
class App extends Component {
state = {
name:'xyz',
age:10
}
render() {
return (
<div className="App">
// this is what I am using which gives the error
<p>I am inside the {state}.</p>
//Correct Way is
<p>I am inside the {this.state.name}.</p>
</div>
);
}
}
Typically this pops up because you don't destructure properly. Take this code for example:
const Button = text => <button>{text}</button>
const SomeForm = () => (
<Button text="Save" />
)
We're declaring it with the = text => param. But really, React is expecting this to be an all-encompassing props object.
So we should really be doing something like this:
const Button = props => <button>{props.text}</button>
const SomeForm = () => (
<Button text="Save" />
)
Notice the difference? The props param here could be named anything (props is just the convention that matches the nomenclature), React is just expecting an object with keys and vals.
With object destructuring you can do, and will frequently see, something like this:
const Button = ({ text }) => <button>{text}</button>
const SomeForm = () => (
<Button text="Save" />
)
...which works.
Chances are, anyone stumbling upon this just accidentally declared their component's props param without destructuring.
Just remove the curly braces in the return statement.
Before:
render() {
var rows = this.props.products.map(product => <tr key={product.id}><td>{product.name}</td><td>{product.price}</td></tr>);
return {rows}; // unnecessary
}
After:
render() {
var rows = this.props.products.map(product => <tr key={product.id}><td>{product.name}</td><td>{product.price}</td></tr>);
return rows; // add this
}
I had the same problem because I didn't put the props in the curly braces.
export default function Hero(children, hero ) {
return (
<header className={hero}>
{children}
</header>
);
}
So if your code is similar to the above one then you will get this error.
To resolve this just put curly braces around the props.
export default function Hero({ children, hero }) {
return (
<header className={hero}>
{children}
</header>
);
}
I got the same error, I changed this
export default withAlert(Alerts)
to this
export default withAlert()(Alerts).
In older versions the former code was ok , but in later versions it throws an error. So use the later code to avoid the errror.
This was my code:
class App extends Component {
constructor(props){
super(props)
this.state = {
value: null,
getDatacall : null
}
this.getData = this.getData.bind(this)
}
getData() {
// if (this.state.getDatacall === false) {
sleep(4000)
returnData("what is the time").then(value => this.setState({value, getDatacall:true}))
// }
}
componentDidMount() {
sleep(4000)
this.getData()
}
render() {
this.getData()
sleep(4000)
console.log(this.state.value)
return (
<p> { this.state.value } </p>
)
}
}
and I was running into this error. I had to change it to
render() {
this.getData()
sleep(4000)
console.log(this.state.value)
return (
<p> { JSON.stringify(this.state.value) } </p>
)
}
Hope this helps someone!
If for some reason you imported firebase. Then try running npm i --save firebase#5.0.3. This is because firebase break react-native, so running this will fix it.
In my case it was i forgot to return a html element frm the render function and i was returning an object . What i did was i just wrapped the {items} with a html element - a simple div like below
<ul>{items}</ul>
Just remove the async keyword in the component.
const Register = () => {
No issues after this.
In my case, I added a async to my child function component and encountered this error. Don't use async with child component.
I got this error any time I was calling async on a renderItem function in my FlatList.
I had to create a new function to set my Firestore collection to my state before calling said state data inside my FlatList.
My case is quite common when using reduce but it was not shared here so I posted it.
Normally, if your array looks like this:
[{ value: 1}, {value: 2}]
And you want to render the sum of value in this array. JSX code looks like this
<div>{array.reduce((acc, curr) => acc.value + curr.value)}</div>
The problem happens when your array has only one item, eg: [{value: 1}].
(Typically, this happens when your array is the response from server so you can not guarantee numbers of items in that array)
The reduce function returns the element itself when array has only one element, in this case it is {value: 1} (an object), it causes the Invariant Violation: Objects are not valid as a React child error.
You were just using the keys of object, instead of the whole object!
More details can be found here: https://github.com/gildata/RAIO/issues/48
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class SCT extends Component {
constructor(props, context) {
super(props, context);
this.state = {
data: this.props.data,
new_data: {}
};
}
componentDidMount() {
let new_data = this.state.data;
console.log(`new_data`, new_data);
this.setState(
{
new_data: Object.assign({}, new_data)
}
)
}
render() {
return (
<div>
this.state.data = {JSON.stringify(this.state.data)}
<hr/>
<div style={{color: 'red'}}>
{this.state.new_data.name}<br />
{this.state.new_data.description}<br />
{this.state.new_data.dependtables}<br />
</div>
</div>
);
}
}
SCT.propTypes = {
test: PropTypes.string,
data: PropTypes.object.isRequired
};
export {SCT};
export default SCT;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
If you are using Firebase and seeing this error, it's worth to check if you're importing it right. As of version 5.0.4 you have to import it like this:
import firebase from '#firebase/app'
import '#firebase/auth';
import '#firebase/database';
import '#firebase/storage';
Yes, I know. I lost 45 minutes on this, too.
I just put myself through a really silly version of this error, which I may as well share here for posterity.
I had some JSX like this:
...
{
...
<Foo />
...
}
...
I needed to comment this out to debug something. I used the keyboard shortcut in my IDE, which resulted in this:
...
{
...
{ /* <Foo /> */ }
...
}
...
Which is, of course, invalid -- objects are not valid as react children!
I'd like to add another solution to this list.
Specs:
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-redux": "^5.0.6",
"react-scripts": "^1.0.17",
"redux": "^3.7.2"
I encountered the same error:
Uncaught Error: Objects are not valid as a React child (found: object
with keys {XXXXX}). If you meant to render a collection of children,
use an array instead.
This was my code:
let payload = {
guess: this.userInput.value
};
this.props.dispatch(checkAnswer(payload));
Solution:
// let payload = {
// guess: this.userInput.value
// };
this.props.dispatch(checkAnswer(this.userInput.value));
The problem was occurring because the payload was sending the item as an object. When I removed the payload variable and put the userInput value into the dispatch everything started working as expected.
If in case your using Firebase any of the files within your project.
Then just place that import firebase statement at the end!!
I know this sounds crazy but try it!!
I have the same issue, in my case,
I update the redux state, and new data parameters did not match old parameters, So when I want to access some parameters it through this Error,
Maybe this experience help someone
My issue was simple when i faced the following error:
objects are not valid as a react child (found object with keys {...}
was just that I was passing an object with keys specified in the error while trying to render the object directly in a component using {object} expecting it to be a string
object: {
key1: "key1",
key2: "key2"
}
while rendering on a React Component, I used something like below
render() {
return this.props.object;
}
but it should have been
render() {
return this.props.object.key1;
}
If using stateless components, follow this kind of format:
const Header = ({pageTitle}) => (
<h1>{pageTitle}</h1>
);
export {Header};
This seemed to work for me
Something like this has just happened to me...
I wrote:
{response.isDisplayOptions &&
{element}
}
Placing it inside a div fixed it:
{response.isDisplayOptions &&
<div>
{element}
</div>
}

Reactjs closure when passing state to component

I got a react functional component:
const DataGrid = (props) =>
{
const [containerName, setContainerName] = useState("");
const [frameworkComponents, setFrameworkComponents] = useState(
{customLoadingOverlay: LoadingOverlayTemplate,
customNoRowsOverlay: UxDataGridCustomNoRows,
editButton: params => <ViewAndDeleteSetting {...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting}/>,
});
useEffect(async () =>
{
if(props.containerName && props.containerName !== "")
{
setContainerName(props.containerName);
}
},[props.containerName]);
.
.
.
const onDeleteSetting = async (settingKey) =>
{
console.log("ON DELETE AND CONTAINER NAME:");
console.log(containerName); //HERE THE CONTAINER NAME IS EMPTY
...
}
return (
<UxDataGrid
frameworkComponents={frameworkComponents}/>
);
The container name inside useEffect exists and is not empty. As you can see in the comment in onDeleteSetting, the containerName is empty when this callback is invoked. I tried adding this to the useEffect after setContainerName:
setFrameworkComponents({customLoadingOverlay: LoadingOverlayTemplate,
customNoRowsOverlay: UxDataGridCustomNoRows,
editButton: params => <ViewAndDeleteSetting {...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting}/>,
});
That didn't work.
How can I get the name inside the callback? There is no special need to leave that frameworkComponents struct in the state.. it can also be moved to somewhere else if you think its better
Try this in your useEffect, update the onDeleteSetting function with the new containerName when it's updated
.....
useEffect(async() => {
if (props.containerName && props.containerName !== "") {
setContainerName(props.containerName);
// move this function here
const onDeleteSetting = async(settingKey) => {
console.log("ON DELETE AND CONTAINER NAME:");
// use props.containerName since the state update is async
console.log(props.containerName);
...
}
// update your components with the updated functions
setFrameworkComponents(prevComponents => ({
...prevComponents,
editButton: params =>
<ViewAndDeleteSetting
{...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting}
/>,
}));
}
}, [props.containerName]);
.....
This should provide the updated state with the updated function, if it works, I can add more details.
You almost certainly shouldn't be storing it in state. Props are essentially state controlled by the parent. Just use it from props. Copying props to state is usually not best practice.
If you're looking at one of the very rare situations where it makes sense to set derived state based on props, this page in the documentation tells you how to do that with hooks. Basically, you don't use useEffect, you do your state update right away.
Here's a full quote from the linked documentation:
How do I implement getDerivedStateFromProps?
While you probably don’t need it, in rare cases that you do (such as implementing a <Transition> component), you can update the state right during rendering. React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive.
Here, we store the previous value of the row prop in a state variable so that we can compare:
function ScrollView({row}) {
const [isScrollingDown, setIsScrollingDown] = useState(false);
const [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row changed since last render. Update isScrollingDown.
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
This might look strange at first, but an update during rendering is exactly what getDerivedStateFromProps has always been like conceptually.
If you did it the same way they did in that example, your component would still render with containerName set to the default state (""), it's just that it will then almost immediately re-render with the updated containerName. That makes sense for their example of a transition, but you could avoid that by making the prop's initial value the state's initial value, like this:
const DataGrid = (props) => {
const [containerName, setContainerName] = useState(props.containerName); // *** ONLY USES THE INITIAL PROP VALUE
const [frameworkComponents, setFrameworkComponents] = useState(
// ...
});
// *** Updates the state value (on the next render) if the prop changes
if (containerName !== props.containerName) {
setContainerName(props.containerName);
}
// ...
};
Every time the containerName prop changes, though, your component will render twice, which brings us back full circle to: Don't store it in state, just use it from props. :-)
Stepping back and looking at the component as a whole, I don't think you need any state information at all, but if your goal is to avoid having the frameworkComponents you pass UxDataGrid change unnecessarily, you probably want useMemo or React.memo rather than state.
For instance, with useMemo (but keep reading):
const DataGrid = ({containerName}) => {
const frameworkComponents = useMemo(() => {
const onDeleteSetting = async (settingKey) => {
console.log("ON DELETE AND CONTAINER NAME:");
console.log(containerName);
// ...
};
return {
customLoadingOverlay: LoadingOverlayTemplate,
editButton: params => <ViewAndDeleteSetting {...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting} />,
};
}, [containerName]);
return (
<UxDataGrid frameworkComponents={frameworkComponents} />
);
};
But if componentName is your only prop, it may well be even simpler with React.memo:
const DataGrid = React.memo(({containerName}) => {
const onDeleteSetting = async (settingKey) => {
console.log("ON DELETE AND CONTAINER NAME:");
console.log(containerName);
// ...
};
return (
<UxDataGrid frameworkComponents={{
customLoadingOverlay: LoadingOverlayTemplate,
editButton: params => <ViewAndDeleteSetting {...params}
openAddConfigurationsWindow={openAddConfigurationsWindow}
onDeleteSetting={onDeleteSetting} />,
}} />
);
});
React.memo memoizes your component, so that your component function is only ever called again when the props change. Since everything in the component needs to update based on the componentName prop changing, that looks like a good match (but I don't know what UxDataGrid is).
The problem was with how I tried passing props to ViewAndDeleteSetting. If you want to pass prop to a cell rendered component, you shouldn't be doing it in frameworkComponents, but rather you need to do it in the column definition like this:
useEffect(() =>
{
let columns = [{headerName: '', cellRenderer: 'editButton', width: 90, editable: false,
cellRendererParams: {
openAddConfigurationsWindow: openAddConfigurationsWindow,
onDeleteSetting: onDeleteSetting
}},
.. other columns
]
setColumnDefinition(columns);
},[props.containerName]);
The columns with the cellRendererParams do gets recreated in the useEffect when the name changes, and then the component can access this params regularly via its props

React prop type checking, how to state "any component"?

Well consider an element with the following idea:
function TheElement(props) {
const {SubElement, value} = props;
const display = value/2;
return <><div>some header</div>
<div><SubElement data={display}/></div>
</>
}
TheElement.propTypes = {
value: PropTypes.number.isRequired,
SubElement: PropTypes.element.isRequired
}
Used in a case like:
function Displayer(props) {
generateElement = function() {
//something to dynamically generate the element
return (props) => return <div>{props.value}</div>
}
return <div>
<TheElement value={10} SubElement={generateElement()}/>
</div>
}
While the code works, the console does display a warning:
Warning: Failed prop type: Invalid prop SubElement of type function supplied to TheElement, expected a single ReactElement.
So how can I tell react that a property is "any element"? If I would give it a PropTypes.func as type it would fail on class-like methods, but even if I make it accept both func and object it isn't useful. As it wouldn't help me prevent providing a random function that doesn't collapse to a react renderable.
You get the warning because (props) => return <div>{props.value}</div> is a component, not an element. An element would be something you get as a result of <SubElement />. Try the following, you will not get a warning anymore:
function TheElement(props) {
const { SubElement, value } = props;
const display = value/2;
return (
<>
<div>some header</div>
<div>{SubElement}</div>
</>
)
}
TheElement.propTypes = {
value: PropTypes.number.isRequired,
SubElement: PropTypes.element.isRequired
}
function Displayer(props) {
generateElement = function() {
return <div>Some Text</div>
}
return (
<div>
<TheElement value={10} SubElement={generateElement()}/>
</div>
)
}
To validate components, I believe the best you can do is use Proptypes.func. That's because there is no way for prop-types to know if a function will result in a valid react element - that will be determined when the bracket syntactic sugar is applied (i.e. <YourComponent /> is evaluated). You can look at the prop-types source code to see that it is only looking at the props you pass to the component (i.e. the un-evaluated functions).
You could write a custom validator that evaluates the prop you pass and then makes sure it returns a valid react element, but that would be too inefficient no? Besides, you'd have to know what props to pass to the component during validation time. Just for fun, assuming your component takes no props, you could do something like this:
customProp: function(props, propName, componentName) {
if (!React.isValidElement(props[propName]({}))) {
return new Error()
}
}
There is more information about react components and elements here.

How to update a specific object within storage with React/Redux?

I'm considering using Redux for my app, but there's a common use case that I'm not sure how to handle with it. I have a component that displays some object and allows the user to edit it. Every action will create a shallow copy of the object, but what then? How is the component supposed to know how to update the storage with it? In the samples I see that the component is passed a key instead of the actual object, but doesn't that break the concept of incapsulation, since a component isn't supposed to know where it's state/props come from? I want the component to be fully reusable, so it receives an object and information on how to update it in a more general form, which seems to be awkward to implement with Redux (I'm going to have to pass write callbacks to every component, and then chain them somehow).
Am I using Redux wrong, or is there a more suitable alternative for this use case? I'm thinking of making one myself (where every state object knows it's owner and key via some global WeakMap), but I don't want to be reinventing the wheel.
For instance, if my storage looks like this:
Storage = {
items: {
item1: { ... },
item2: { ... },
...
},
someOtherItems: {
item1: { ... },
...
},
oneMoreItem: { ... },
};
I want to be able to display all item objects with the same component. But the component somehow has to know how to write it's updated item back to the storage, so I can't just pass it item1 as key. I could pass a callback that would replace a specific item in the (cloned) storage, but that doesn't work well if, for instance, I have a component that displays a list of items, since I would have to chain those callbacks somehow.
This is a common use case, and yes - you're missing the point here. react/redux makes this really easy.
I usually structure it as follows: Components receive a modelValue object prop and changeValue function prop. The former is the current value, the latter is the function we call to change the value. These props are going to be supplied by redux.
Now we write a connect hoc (higher order component), a simple example might look like this:
const mapStateToProps = (state, ownProps) => {
return {
modelValue: _.get(state, ownProps.model),
};
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
changeValue: (val) => dispatch({
type: "your/reducer/action",
model: ownProps.model,
value: val,
})
};
};
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
...dispatchProps,
...ownProps,
};
};
const MyConnectedComponent = connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyGenericComponent);
This is an example where we pass in a model string to the hoc, and it wires up modelValue and changeValue for us. So now all we need to do is pass in a model like "some.javascript.path" to our component and that's where it will get stored in the state. MyGenericComponent still doesn't know or care about where it's stored in the state, only MyConnectedComponent does.
Usage would be as follows:
<MyConnectedComponent model="some.path.in.the.state" />
And inside MyGenericComponent just consume modelValue for the current value, and execute changeValue to change the value.
Note that you need to also wire up a redux reducer to handle your/reducer/action and actually do the update to the state, but that's a whole other topic.
Edit
You mentioned that you need sub components to be aware of the parent state, this can be achieved by passing model via context. The following examples are using recompose:
const mapStateToProps = ...
const mapDispatchToProps = ...
const mergeProps = ...
const resolveParentModel = (Component) => {
return (props) => {
// we have access to 'model' and 'parentModel' here.
// parentModel comes from parent context, model comes from props
const { parentModel, model } = props;
let combinedModel = model;
// if our model starts with a '.' then it should be a model relative to parent.
// else, it should be an absolute model.
if (model.startsWith(".")) {
combinedModel = parentModel + model;
}
return <Component {...props} model={combinedModel} />;
}
}
const myCustomHoc = (Component) => (
// retrieve the current parent model as a prop
getContext({ parentModel: React.PropTypes.string })(
// here we map parent model and own model into a single combined model
resolveParentModel(
// here we map that combined model into 'modelValue' and 'changeValue'
connect(mapStateToProps, mapDispatchToProps, mergeProps)(
// we provide this single combined model to any children as parent model so the cycle continues
withContext({ parentModel: React.PropTypes.string }, (props) => props.model)(
Component
)
)
)
)
);
In summary, we pass a context value parentModel to all children. Each object maps parent model into it's own model string conditionally. Usage would then look like this:
const MyConnectedParentComponent = myCustomHoc(MyGenericParentComponent);
const MyConnectedSubComponent = myCustomHoc(MyGenericSubComponent);
<MyConnectedParentComponent model="some.obj">
{/* the following model will be resolved into "some.obj.name" automatically because it starts with a '.' */}
<MyConnectedSubComponent model=".name" />
</MyConnectedParentComponent>
Note that nesting this way could then go to any depth. You can access absolute or relative state values anywhere in the tree. You can also get clever with your model string, maybe starting with ^ instead of . will navigate backwards: so some.obj.path and ^name becomes some.obj.name instead of some.obj.path.name etc.
Regarding your concerns with arrays, when rendering arrays you almost always want to render all items in the array - so it would be easy enough to write an array component that just renders X elements (where X is the length of the array) and pass .0, .1, .2 etc to each item.
const SomeArray = ({ modelValue, changeValue }) => (
<div>
{modelValue.map((v, i) => <SomeChildEl key={i} model={"." + i} />)}
<span onClick={() => changeValue([...modelValue, {}])} >Add New Item</span>
</div>
);

Passing jsx content to redux state and rendering it from there

I am having trouble figuring out how to pass JSX to my redux state i.e. for modal component that is used globally and has its redux state where one parameter is content such content can be updated to include JSX code.
At the moment I am getting it to render correct content however it doesn't seem that functions are called correctly and I am also getting following error:
invariant.js:38 Uncaught Error: Objects are not valid as a React child
(found: object with keys {dispatchConfig, _targetInst,
isDefaultPrevented, isPropagationStopped, _dispatchListeners,
_dispatchInstances, nativeEvent, type, target, currentTarget, eventPhase, bubbles, cancelable, timeStamp, defaultPrevented,
isTrusted, view, detail, screenX, screenY, clientX, clientY, ctrlKey,
shiftKey, altKey, metaKey, getModifierState, button, buttons,
relatedTarget, pageX, pageY}). If you meant to render a collection of
children, use an array instead or wrap the object using
createFragment(object) from the React add-ons. Check the render method
of styled.div.
With a lot of following errors:
warning.js:36 Warning: This synthetic event is reused for performance
reasons. If you're seeing this, you're accessing the property
nativeEvent on a released/nullified synthetic event. This is set to
null. If you must keep the original synthetic event around, use
event.persist(). See https://fbme(replaced this as so doesn't allow links to fb)/react-event-pooling for more
information.
Example implementation:
Function called from a page to show modal and add contents to it
onToggleModal = () => {
this.props.modalToggle(
(<TopUp account={getSession().accounts[0] || {}} />)
);
}
Where this.props.modalToggle is a redux action like this:
export const modalToggle = (content = '') => ({
type: MODAL_TOGGLE,
payload: content
});
I then try to render such content inside my Modal container:
return (
<div>{this.props.content}</div>
)
I imported React into my reducer, in hopes to resolve jsx issues, but had no luck. It also seems like components are passed to reducer as some sort of weird objects.
Redux state can only contain plain objects and data types, so a JSX object would probably cause issues. Also it's most likely not a good idea to have a JSX object (which is basically a view) as part of your state.
Instead, you should pass around whatever data is required to render the final view. I think this would work:
onToggleModal = () => {
this.props.modalToggle(getSession().accounts[0] || {});
}
export const modalToggle = (account = {}) => ({
type: MODAL_TOGGLE,
account: account
});
return (
<div><TopUp account={account} /></div>
)
Hey it's been a while now but for the record I stumbled upon the same use case you are explaining in the reply to the accepted answer.
You can achieve this by adding a property of JsxElement | ReactElement | HTMLElement type in your modalSlice state:
export interface modalState {
active: boolean;
component?: ReactElement;
}
reducers: {
updateModalComponent: (state, value: PayloadAction<ReactElement>) => {
state.component = value.payload;
},
}
Then your modal component file could look something like this:
import { ReactElement } from 'react';
import './modal.scss';
interface Props {
active: boolean,
children: ReactElement | JsxElement | HTMLElement,
}
export const Modal = (props: Props) => {
return (
<div id="modal-container">
{
props.active &&
<div id="overlay">
<div id="modal-content-container">
{props.children}
</div>
</div>
}
</div>
)
}
Finally use it anywhere!
const element = <OtherComponent />;
dispatch(updateModalComponent(element));
Cheers!

Categories