Passing a Nested Object as React.Component Props - javascript

I'm sure this was asked before but I can't find it. I'm trying to pass a nested object as props into a React.Component class but I keep getting this error:
react-dom.development.js:13231 Uncaught Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater}). If you meant to render a collection of children, use an array instead.
I have an App.tsx file created like this:
import React from "react";
export type Child = {
id: number
}
export type Parent = {
id: number,
child: Child
}
export class Card extends React.Component<Parent> {
render() {
return <div>
Parent ID: {this.props.id}
Child ID: {this.props.child.id}
</div>;
}
}
function App() {
const data: any = {
"id": 1,
"child": {
"id": 2
}
}
const card: Card = new Card(data);
return (
<div className="app">
{card}
</div>
);
}
export default App;
Is this sort of thing not possible? It seems like it should be but maybe I am missing something. Is there a way to make something like this work or a correct pattern that I am not using? Thanks!

Theere is no "one" correct way to pass an object down to a child component, there are multiple approaches, one of which is the following (using your example):
import React from "react";
export type Child = {
id: number
}
export type Parent = {
id: number,
child: Child
}
export class Card extends React.Component<Parent> {
render() {
return <div>
Parent ID: {this.props.id}
Child ID: {this.props.child.id}
</div>;
}
}
function App() {
const data: any = {
"id": 1,
"child": {
"id": 2
}
}
return (
<div className="app">
<Card {...data} />
</div>
);
}
export default App;

I'm pretty sure for this to work you'd have to change
<div className="app">
{card}
</div>
TO:
<div className="app">
<card />
</div>
BUT, like Jack said in the comments, you shouldn't be instantiating React components like this anyway. Instead of doing:
const card: Card = new Card(data);
You should just do:
<div className="app">
<Card {...data} />
</div>

Related

.map undefined on array of object

I am passing array of object as a prop from App.js -> searchResult -> TrackList.js. But when I apply .map function on the array of object it shows Cannot read property 'map' of undefined . I have tried different ways to solve this but none of them worked. In console I am getting the value of prop. And my TRackList.js component is rendering four times on a single run. Here is the code
App.js
this.state = {
searchResults: [
{
id: 1,
name: "ritik",
artist: "melilow"
},
{
id: 2,
name: "par",
artist: "ron"
},
{
id: 3,
name: "make",
artist: "zay z"
}
]
return ( <SearchResults searchResults={this.state.searchResults} /> )
In Searchresult .js
<TrackList tracked={this.props.searchResults} />
In TrackList.js
import React from "react";
import Track from "./Track";
export default class TrackList extends React.Component {
constructor(props) {
super();
}
render() {
console.log("here", this.props.tracked);
return (
<div>
<div className="TrackList">
{this.props.tracked.map(track => {
return (<Track track={track} key={track.id} />);
})}
</div>
</div>
);
}
}
Here is the full code -- https://codesandbox.io/s/jamming-ygs5n?file=/src/components/TrackList.js:0-431
You were loading the Component TrackList twice. One time with no property passed, that's why it was first set in console then looks like it's unset, but it's just a second log. I have updated your code. Take a look here https://codesandbox.io/s/jamming-ddc6l?file=/src/components/PlayList.js
You need to check this.props.tracked.map is exists before the loop.
Solution Sandbox link:https://codesandbox.io/s/jamming-spf7f?file=/src/components/TrackList.js
import React from "react";
import Track from "./Track";
import PropTypes from 'prop-types';
export default class TrackList extends React.Component {
constructor(props) {
super();
}
render() {
console.log("here", typeof this.props.tracked);
return (
<div>
<div className="TrackList">
{this.props.tracked && this.props.tracked.map(track => {
return <Track track={track} key={track.id} />;
})}
</div>
</div>
);
}
}
TrackList.propTypes = {
tracked: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
artist: PropTypes.string,
}))
};
You need to check this.props.tracked value before implementing the map function.
you can simply check using this.props.tracked && follow your code.
You should add searchResults={this.state.searchResults} in your app.js to Playlist, take it in Playlist with props, and then set it in TrackList from Playlist (tracked={props.searchResults}).
Also, Typescript helps me not to do such mistakes.
Also, add a key prop to your component that you return in the map function.

React - How to let this functional component change div styles?

I am a bit of a react newbie so please be gentle on me. I am trying to build an education app, and code is to render multiple choice answer boxes:
export default function AnswerBox(props: any) {
return (
<div className="answer-container">
<ul>
{props.answers.map((value: any) => {
return (
<li className="answer-box" key={value.letter} id={value.letter}>
<input className="answer-textbox" type="checkbox" onChange={() => console.log('selected: ', value.letter)}></input>
<span className="answer-letter"><b>{value.letter})</b></span>
{value.answer}
</li>)
})
}
</ul>
</div>
)
}
As you can see, the function takes a object of arrays and iterates through arrays to display the Question Letter (e.x. 'A') and Question Answer in an unordered list.
So all of this is good, but I'd like the list element to be highlighted or have the answerbox div changed when it is actually selected. And I havent found a good way to do that other than to change the component into a stateful component and use a state var to track which box is ticked.
But when I changed it to a stateful component last night, I really struggled to pass in the list of objects to render.
How can I have a stateful class that also accepts props like a regular functional component?
To begin, you pass props to all types of components in a similar fashion, regardless if it's stateful or not.
<Component prop={someValue}/>
The only difference is how you would access them.
For class-based components you would access them through the props property of the class this.props. i.e.
class Component extends React.Component {
constructor(props) {
// you need to call super(props) otherwise
// this.props will be underfined
super(props);
}
...
someFunction = (...) => {
const value = this.props.prop;
}
}
If you're using TypeScript, you need to describe to it the structure of your props and state like this
interface iComponentProps {
prop: string;
};
interface iComponentState { ... };
export default class Component extends React.Component<iComponentProps, iComponentState> {
...
}
if your component takes in props and/or state and you're unsure of their structure, pass in any for the one you're unsure of.
On the other hand, if I understood your question correctly, you could do something like this:
I also made a demo of the simple app I made to address your other question.
In summary, you can have your AnswerBox component maintain an array of indexes that pertain to each of its choices and have it updated every time a choice is selected (or clicked) by using setState
You can also check out the useState hook to make your functional component stateful.
App.js
import React from "react";
import "./styles.css";
import Question from "./Question";
export default function App() {
const questionData = [
{
question: "Some Question that needs to be answered",
choices: ["Letter A", "Letter B", "Letter C"]
},
{
question: "Another Question that needs to be answered",
choices: ["Letter A", "Letter B", "Letter C"]
}
];
return (
<div className="App">
{questionData.map(question => (
<Question questionText={question.question} choices={question.choices} />
))}
</div>
);
}
Question.js
import React from "react";
import AnswerBox from "./AnswerBox";
const Question = ({ questionText, choices }) => {
return (
<div className={"question-container"}>
<p className={"question-text"}>{questionText}</p>
<AnswerBox choices={choices} />
</div>
);
};
export default Question;
QuestionChoice.js
import React from "react";
import clsx from "clsx";
const QuestionChoice = ({ letter, content, isSelected, handleClick }) => {
return (
<li
className={clsx("question-choice-container", {
"selected-choice": isSelected
})}
>
<input type={"checkbox"} value={content} onClick={handleClick} />
<label for={content} className={"question-choice-label"}>
<strong>{letter.toUpperCase()}. </strong>
<span>{content}</span>
</label>
</li>
);
};
export default QuestionChoice;
AnswerBox.js
import React, { PureComponent } from "react";
import QuestionChoice from "./QuestionChoice";
export default class AnswerBox extends PureComponent {
constructor(props) {
super(props);
this.choiceLetters = Array.from("abcdefghijklmnopqrstuvwxyz");
this.state = { activeChoices: [] };
}
_updateActiveChoices = index => {
let updatedList = [].concat(this.state.activeChoices);
if (this.state.activeChoices.indexOf(index) !== -1) {
updatedList.splice(updatedList.indexOf(index), 1);
} else {
updatedList.push(index);
}
return updatedList;
};
_handleChoiceSelect = choiceIndex => () => {
// an update to your component's state will
// make it re-run its render method
this.setState({ activeChoices: this._updateActiveChoices(choiceIndex) });
};
render() {
return (
<ul class={"answer-box"}>
{this.props.choices.map((choice, index) => (
<QuestionChoice
letter={this.choiceLetters[index]}
content={choice}
isSelected={this.state.activeChoices.indexOf(index) != -1}
handleClick={this._handleChoiceSelect(index)}
/>
))}
</ul>
);
}
}
For this type of interaction you can use the input checkbox's checked attribute to show that its checked. The check state should be derived from somewhere in your state. In the onClick function, you can look at the event for the name and checked state to update your state. Note you could add any attribute you want if name is too generic for you.
The interaction could look like this:
https://codesandbox.io/s/optimistic-archimedes-ozr72?file=/src/App.js

How to Programmatically Provide and Consume Context?

So my question is a simple one. In React js I want to pass some states and handlers from a parent to its 3rd grandchild using Context. I have implemented this within the jsx but I want to use the states within the javascript o that I have some logic before I completely output my states.
I have divided my question into 2 parts. 1.) What I have done so far. 2.) What I want to do essentially.
1.)
// this file just stores the Context
MyContext.js
import React, { Component } from 'react';
export const MyContext = React.createContext();
MyProvider.js // this class is used by the parent and the child to have access to the provider
import React, { Component } from 'react';
import {MyContext} from '../MyContext'
class MyProvider extends Component {
state = {
name: 'Wes',
age: 100,
cool: true
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: () => this.setState({
age: this.state.age + 1
})
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
export default MyProvider;
// Ok so now I am basically skipping the parent and showing you the consumer grand-child
Person.js
import React, { Component } from 'react';
// first we will make a new context
import { MyContext } from '../MyContext';
class Person extends Component {
render() {
return (
<div className="person">
<MyContext.Consumer>
{(context) => (
<React.Fragment>
<p>Age: {context.state.age}</p>
<p>Name: {context.state.name}</p>
<button onClick={context.growAYearOlder}>🍰🍥🎂</button>
</React.Fragment>
)}
</MyContext.Consumer>
</div>
)
}
}
export default Person;
2.)
// Ok so as you can see here I have had to immediately use the context.growAYearOlder. What I want to do instead is have control of it using javascript and modify it as desired; So something like this:
Child.js
const parentContext = MyContext.getContext();
if(somethingHappens){
parentContext().growAYearOlder();
}
return(
// The now rendered component
);
I tried something like this but it doesnt work:
MyContext.Consumer.context.growAYearOlder();
There are many similar questions with proper answers, docs, examples and so on - but this question kept popping up for me.
So, in case you want to get the context value and use it within your component's render() just import it (export context itself not only provider) and use _currentValue e.g.
const contextData = MyContext._currentValue;
Note that you still have to wrap your components with your given context provider.
Also note that for function components, you need to use useContext e.g.
const contextData = useContext(MyContext);
And for class components you can assign the context to a static var and then use it e.g.
class Main extends React.Component(){
static contextType = MyContext;
componentDidMount(){
const contextData = this.context;
}
render() {
return (
<p>Hey</p>
);
}
Note that the static var has to be called contextType otherwise this.context won't hold the MyContext data.
I've based my answer solely from the docs itself(https://reactjs.org/docs/context.html#updating-context-from-a-nested-component)
import React, { Component } from 'react';
import { MyContext } from '../MyContext'
class MyProvider extends Component {
constructor(props) {
super(props)
// I've moved the state declaration inside the constructor
this.state = {
name: 'Wes',
age: 100,
cool: true
}
// moved the function here and added prevState
this.growAYearOlder = () => {
this.setState(prevState => ({
age: prevState.age + 1,
}))
};
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: this.growAYearOlder,
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
export default MyProvider;

Passing state to a child component of another child component

I'm building a KanBan app with ReactJS, and I'm trying to pass state from a parent component to the furthest component in the parent-child tree. I have a Column component within my main App component, and within this Column component there is another component called 'Card'. I want to pass the data the gets added/updated in the state of the App component & successfully display it in the Card component. As its obvious by now, the Card component is the child of the Column component.
I tried doing so with this.props but this only works one level down - with the column component. I thought about declaring a variable and equating to this.props.details.cards, and then setting it as the new state of the column component so that I could pass it again as props to the card component, but I assume this is not best practice.
This is my App Component:
class App extends Component {
constructor(props) {
super(props)
this.state = {
columns: [
{
name: 'Todos',
cards: []
},
{
name: 'Onprogress',
cards: []
},
{
name: 'Done',
cards: []
},
]
};
};
addCard = card => {
console.log("Adding a Card");
const cards = { ...this.state.columns.cards };
const keyDate = `card${Date.now()}`
cards[keyDate] = card;
this.setState({
columns: [
{
name: 'Todos',
cards: cards
}
]
});
};
render() {
return (
<div className="App">
{Object.keys(this.state.columns).map(key => (
<Column key={key} details={this.state.columns[key]} />
))}
<AddCardForm addCard={this.addCard} />
</div>
);
}
}
export default App;
This is my Column Component:
import React, {Component} from "react";
import Card from "./Card"
class Column extends Component {
render() {
return (
<div className="column">
<h1 className="Title">{this.props.details.name}</h1>
<Card />
</div>
);
}
}
export default Column;
And this is my Card Component:
import React, {Component} from "react";
class Card extends Component {
render() {
return (
<div className="card">
<span className="title">I'm a Card!</span>
</div>
);
}
}
export default Card;
React will only get the properties that are injected into the child component.
Considering we have three component A , B and C.
If we want to pass a prop from A to C while B is in the middle, we do this:
The A component returns the B component and passes someProperty as follows:
return <B someProperty={someValue} />
Now, in the B component we can access the property by calling it this.props.someProperty however, it won't be available at the C component, if we want to do so, we do the following.
return <C someProperty={this.props.someProperty} />
What we did in component B is pass the someProperty that came from component A which we can access in B as this.props.someProperty, we pass it again the same way to the component C..
Read more about this: Passing props between react component
Full example as follows:
Component A:
render() {
return (
<B someProperty={'someString'} />
);
}
Component B:
render() {
return (
<C someProperty={this.props.someProperty} />
);
}
Compoent C: to access the property someProperty for example in the render method
render() {
return (
<p>{this.props.someProperty}</p>
);
}

Is it possible to deep traverse React Children without rendering?

Is there any way to grab all of the bar properties in <Wrapper/> below 'statically', e.g. without rendering?
import React from 'react';
import ReactDOM from 'react-dom';
class Foo extends React.Component {
render() {
return(
<div>
<span bar="1" /> // want to collect this 'bar'
<span bar="2" /> // want to collect this 'bar'
</div>;
);
}
}
class FooTuple extends React.Component {
render() {
return(
<div>
<Foo />
<Foo />
</div>;
);
}
}
class Wrapper extends React.Component {
render() {
React.Children.forEach(this.props.children, child => {
console.log(child.props); // can only see <FooTuple/> not <Foo/>
});
return(
<div>
{this.props.children}
</div>;
);
}
}
ReactDOM.render(
<Wrapper>
<FooTuple />
</Wrapper>,
document.getElementById('app'));
Here's a webpackbin with a naive attempt that tries to iterate over child.children which obviously doesn't work, but it's here if it's helpful:
http://www.webpackbin.com/EySeQ-ihg
TL;DR; Nope that's not possible.
--
I've once encountered the same problem trying to traverse a tree of deeply nested children. Here are my scoop outs:
Required knowledge
children are what's placed inside the jsx open and close tags, or injected directly in the children prop. other than that children prop would be undefined.
<div className="wrapper">
// Children
<img src="url" />
</div>
/* OR */
<div classname="wrapper" children={<img src="url" />}>
children are an opaque tree-like data structure that represents the react elements' tree, it's likely the output of React.createElement that the jsx implements when transpiling.
{
$$typeof: Symbol(react.element),
type: 'div',
key: null,
ref: null,
props: {
className: "wrapper",
children: {
$$typeof: Symbol(react.element),
type: 'img',
key: null,
ref: null,
props: { src: 'url' },
}
}
}
Creating React elements doesn't mean that they are instantiated, think of them like a descriptor that React uses to render those elements. in other words, instances are taken care off by React itself behind the scenes.
Traversing children
Let's take your example and try to traverse the whole tree.
<Wrapper>
<FooTuple />
</Wrapper>
The opaque children object of these elements would be something like this:
{
$$typeof: Symbol(react.element),
type: Wrapper,
key: null,
ref: null,
props: {
children: {
$$typeof: Symbol(react.element),
type: FooTuple,
key: null,
ref: null,
props: {},
}
}
}
As you can see FooTuple props are empty for the reason you should know by now. The only way to reach it's child elements is to instantiate the element using it's type to be able to call it's render method to grab it's underlying child elements, something like this:
class Wrapper extends React.Component {
render() {
React.Children.forEach(this.props.children, child => {
const nestedChildren = new child.type(child.props).render();
console.log(nestedChildren); // `FooTuple` children
});
return(
<div>
{this.props.children}
</div>;
);
}
}
This is obviously not something to consider at all.
Conclusion
There is no clean way to augment deeply nested children or grab something from them (like your case). Refactor your code to do that in a different manner. Maybe provide a setter function in the context to set the data you need from any deep child.

Categories

Resources